From d6bf940099e91853ee0628769512c30ff931f2d1 Mon Sep 17 00:00:00 2001 From: Charmik Sheth <101146970+charmik-redhat@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:08:04 -0700 Subject: [PATCH 001/638] ROX-36247: Proto changes for node CVE reports (#22333) Co-authored-by: Claude Opus 4.6 --- central/main.go | 9 + generated/api/v2/node_report_service.pb.go | 126 ++ generated/api/v2/node_report_service.pb.gw.go | 1243 ++++++++++++++ .../api/v2/node_report_service.swagger.json | 1454 +++++++++++++++++ .../api/v2/node_report_service_grpc.pb.go | 651 ++++++++ generated/api/v2/report_service.pb.go | 657 +++++--- generated/api/v2/report_service.swagger.json | 47 +- generated/api/v2/report_service_vtproto.pb.go | 928 ++++++++++- generated/storage/report_configuration.pb.go | 208 ++- .../report_configuration_vtproto.pb.go | 664 +++++++- generated/storage/report_snapshot.pb.go | 80 +- .../storage/report_snapshot_vtproto.pb.go | 153 ++ proto/api/v2/node_report_service.proto | 85 + proto/api/v2/report_service.proto | 21 +- proto/storage/proto.lock | 42 + proto/storage/report_configuration.proto | 12 +- proto/storage/report_snapshot.proto | 4 +- 17 files changed, 6052 insertions(+), 332 deletions(-) create mode 100644 generated/api/v2/node_report_service.pb.go create mode 100644 generated/api/v2/node_report_service.pb.gw.go create mode 100644 generated/api/v2/node_report_service.swagger.json create mode 100644 generated/api/v2/node_report_service_grpc.pb.go create mode 100644 proto/api/v2/node_report_service.proto diff --git a/central/main.go b/central/main.go index 79d9a2aedf853..ae7dcf43d3544 100755 --- a/central/main.go +++ b/central/main.go @@ -991,6 +991,15 @@ func customRoutes() (customRoutes []routes.CustomRoute) { Compression: true, }) + if features.NodeVulnerabilityReports.Enabled() { + customRoutes = append(customRoutes, routes.CustomRoute{ + Route: "/api/reports/node/jobs/download", + Authorizer: user.With(permissions.View(resources.Node), permissions.View(resources.Cluster)), + ServerHandler: v2Service.NewDownloadHandler(), + Compression: true, + }) + } + if features.ComplianceEnhancements.Enabled() && features.ComplianceReporting.Enabled() && features.ScanScheduleReportJobs.Enabled() { customRoutes = append(customRoutes, routes.CustomRoute{ Route: "/v2/compliance/scan/configurations/reports/download", diff --git a/generated/api/v2/node_report_service.pb.go b/generated/api/v2/node_report_service.pb.go new file mode 100644 index 0000000000000..47da5df35034e --- /dev/null +++ b/generated/api/v2/node_report_service.pb.go @@ -0,0 +1,126 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.32.1 +// source: api/v2/node_report_service.proto + +package v2 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +var File_api_v2_node_report_service_proto protoreflect.FileDescriptor + +const file_api_v2_node_report_service_proto_rawDesc = "" + + "\n" + + " api/v2/node_report_service.proto\x12\x02v2\x1a\x13api/v2/common.proto\x1a\x1bapi/v2/report_service.proto\x1a\x19api/v2/search_query.proto\x1a\x1cgoogle/api/annotations.proto2\xb3\x0e\n" + + "\x11NodeReportService\x12{\n" + + "\x1bPostNodeReportConfiguration\x12\x17.v2.ReportConfiguration\x1a\x17.v2.ReportConfiguration\"*\x82\xd3\xe4\x93\x02$:\x01*\"\x1f/v2/reports/node/configurations\x12t\n" + + "\x1dUpdateNodeReportConfiguration\x12\x17.v2.ReportConfiguration\x1a\t.v2.Empty\"/\x82\xd3\xe4\x93\x02):\x01*\x1a$/v2/reports/node/configurations/{id}\x12{\n" + + "\x1cListNodeReportConfigurations\x12\f.v2.RawQuery\x1a$.v2.ListReportConfigurationsResponse\"'\x82\xd3\xe4\x93\x02!\x12\x1f/v2/reports/node/configurations\x12\x82\x01\n" + + "\x1dCountNodeReportConfigurations\x12\f.v2.RawQuery\x1a%.v2.CountReportConfigurationsResponse\",\x82\xd3\xe4\x93\x02&\x12$/v2/reports/node/configuration-count\x12u\n" + + "\x1aGetNodeReportConfiguration\x12\x10.v2.ResourceByID\x1a\x17.v2.ReportConfiguration\",\x82\xd3\xe4\x93\x02&\x12$/v2/reports/node/configurations/{id}\x12j\n" + + "\x1dDeleteNodeReportConfiguration\x12\x10.v2.ResourceByID\x1a\t.v2.Empty\",\x82\xd3\xe4\x93\x02&*$/v2/reports/node/configurations/{id}\x12]\n" + + "\rRunNodeReport\x12\x14.v2.RunReportRequest\x1a\x15.v2.RunReportResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v2/reports/node/run\x12\x84\x01\n" + + "\x14GetNodeReportHistory\x12\x1b.v2.GetReportHistoryRequest\x1a\x19.v2.ReportHistoryResponse\"4\x82\xd3\xe4\x93\x02.\x12,/v2/reports/node/configurations/{id}/history\x12\x89\x01\n" + + "\x16GetMyNodeReportHistory\x12\x1b.v2.GetReportHistoryRequest\x1a\x19.v2.ReportHistoryResponse\"7\x82\xd3\xe4\x93\x021\x12//v2/reports/node/configurations/{id}/my-history\x12l\n" + + "\x13GetNodeReportStatus\x12\x10.v2.ResourceByID\x1a\x18.v2.ReportStatusResponse\")\x82\xd3\xe4\x93\x02#\x12!/v2/reports/node/jobs/{id}/status\x12Z\n" + + "\x10CancelNodeReport\x12\x10.v2.ResourceByID\x1a\t.v2.Empty\")\x82\xd3\xe4\x93\x02#*!/v2/reports/node/jobs/{id}/cancel\x12a\n" + + "\x10DeleteNodeReport\x12\x17.v2.DeleteReportRequest\x1a\t.v2.Empty\")\x82\xd3\xe4\x93\x02#*!/v2/reports/node/jobs/{id}/delete\x12\x81\x01\n" + + "\x17PostViewBasedNodeReport\x12\x1a.v2.ReportRequestViewBased\x1a\x1e.v2.RunReportResponseViewBased\"*\x82\xd3\xe4\x93\x02$:\x01*\"\x1f/v2/reports/node/view-based/run\x12\x8d\x01\n" + + "\x1dGetViewBasedNodeReportHistory\x12$.v2.GetViewBasedReportHistoryRequest\x1a\x19.v2.ReportHistoryResponse\"+\x82\xd3\xe4\x93\x02%\x12#/v2/reports/node/view-based/history\x12\x92\x01\n" + + "\x1fGetViewBasedMyNodeReportHistory\x12$.v2.GetViewBasedReportHistoryRequest\x1a\x19.v2.ReportHistoryResponse\".\x82\xd3\xe4\x93\x02(\x12&/v2/reports/node/view-based/my-historyB'\n" + + "\x18io.stackrox.proto.api.v2Z\v./api/v2;v2X\x03b\x06proto3" + +var file_api_v2_node_report_service_proto_goTypes = []any{ + (*ReportConfiguration)(nil), // 0: v2.ReportConfiguration + (*RawQuery)(nil), // 1: v2.RawQuery + (*ResourceByID)(nil), // 2: v2.ResourceByID + (*RunReportRequest)(nil), // 3: v2.RunReportRequest + (*GetReportHistoryRequest)(nil), // 4: v2.GetReportHistoryRequest + (*DeleteReportRequest)(nil), // 5: v2.DeleteReportRequest + (*ReportRequestViewBased)(nil), // 6: v2.ReportRequestViewBased + (*GetViewBasedReportHistoryRequest)(nil), // 7: v2.GetViewBasedReportHistoryRequest + (*Empty)(nil), // 8: v2.Empty + (*ListReportConfigurationsResponse)(nil), // 9: v2.ListReportConfigurationsResponse + (*CountReportConfigurationsResponse)(nil), // 10: v2.CountReportConfigurationsResponse + (*RunReportResponse)(nil), // 11: v2.RunReportResponse + (*ReportHistoryResponse)(nil), // 12: v2.ReportHistoryResponse + (*ReportStatusResponse)(nil), // 13: v2.ReportStatusResponse + (*RunReportResponseViewBased)(nil), // 14: v2.RunReportResponseViewBased +} +var file_api_v2_node_report_service_proto_depIdxs = []int32{ + 0, // 0: v2.NodeReportService.PostNodeReportConfiguration:input_type -> v2.ReportConfiguration + 0, // 1: v2.NodeReportService.UpdateNodeReportConfiguration:input_type -> v2.ReportConfiguration + 1, // 2: v2.NodeReportService.ListNodeReportConfigurations:input_type -> v2.RawQuery + 1, // 3: v2.NodeReportService.CountNodeReportConfigurations:input_type -> v2.RawQuery + 2, // 4: v2.NodeReportService.GetNodeReportConfiguration:input_type -> v2.ResourceByID + 2, // 5: v2.NodeReportService.DeleteNodeReportConfiguration:input_type -> v2.ResourceByID + 3, // 6: v2.NodeReportService.RunNodeReport:input_type -> v2.RunReportRequest + 4, // 7: v2.NodeReportService.GetNodeReportHistory:input_type -> v2.GetReportHistoryRequest + 4, // 8: v2.NodeReportService.GetMyNodeReportHistory:input_type -> v2.GetReportHistoryRequest + 2, // 9: v2.NodeReportService.GetNodeReportStatus:input_type -> v2.ResourceByID + 2, // 10: v2.NodeReportService.CancelNodeReport:input_type -> v2.ResourceByID + 5, // 11: v2.NodeReportService.DeleteNodeReport:input_type -> v2.DeleteReportRequest + 6, // 12: v2.NodeReportService.PostViewBasedNodeReport:input_type -> v2.ReportRequestViewBased + 7, // 13: v2.NodeReportService.GetViewBasedNodeReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest + 7, // 14: v2.NodeReportService.GetViewBasedMyNodeReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest + 0, // 15: v2.NodeReportService.PostNodeReportConfiguration:output_type -> v2.ReportConfiguration + 8, // 16: v2.NodeReportService.UpdateNodeReportConfiguration:output_type -> v2.Empty + 9, // 17: v2.NodeReportService.ListNodeReportConfigurations:output_type -> v2.ListReportConfigurationsResponse + 10, // 18: v2.NodeReportService.CountNodeReportConfigurations:output_type -> v2.CountReportConfigurationsResponse + 0, // 19: v2.NodeReportService.GetNodeReportConfiguration:output_type -> v2.ReportConfiguration + 8, // 20: v2.NodeReportService.DeleteNodeReportConfiguration:output_type -> v2.Empty + 11, // 21: v2.NodeReportService.RunNodeReport:output_type -> v2.RunReportResponse + 12, // 22: v2.NodeReportService.GetNodeReportHistory:output_type -> v2.ReportHistoryResponse + 12, // 23: v2.NodeReportService.GetMyNodeReportHistory:output_type -> v2.ReportHistoryResponse + 13, // 24: v2.NodeReportService.GetNodeReportStatus:output_type -> v2.ReportStatusResponse + 8, // 25: v2.NodeReportService.CancelNodeReport:output_type -> v2.Empty + 8, // 26: v2.NodeReportService.DeleteNodeReport:output_type -> v2.Empty + 14, // 27: v2.NodeReportService.PostViewBasedNodeReport:output_type -> v2.RunReportResponseViewBased + 12, // 28: v2.NodeReportService.GetViewBasedNodeReportHistory:output_type -> v2.ReportHistoryResponse + 12, // 29: v2.NodeReportService.GetViewBasedMyNodeReportHistory:output_type -> v2.ReportHistoryResponse + 15, // [15:30] is the sub-list for method output_type + 0, // [0:15] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_api_v2_node_report_service_proto_init() } +func file_api_v2_node_report_service_proto_init() { + if File_api_v2_node_report_service_proto != nil { + return + } + file_api_v2_common_proto_init() + file_api_v2_report_service_proto_init() + file_api_v2_search_query_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v2_node_report_service_proto_rawDesc), len(file_api_v2_node_report_service_proto_rawDesc)), + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_v2_node_report_service_proto_goTypes, + DependencyIndexes: file_api_v2_node_report_service_proto_depIdxs, + }.Build() + File_api_v2_node_report_service_proto = out.File + file_api_v2_node_report_service_proto_goTypes = nil + file_api_v2_node_report_service_proto_depIdxs = nil +} diff --git a/generated/api/v2/node_report_service.pb.gw.go b/generated/api/v2/node_report_service.pb.gw.go new file mode 100644 index 0000000000000..c1bad98935a47 --- /dev/null +++ b/generated/api/v2/node_report_service.pb.gw.go @@ -0,0 +1,1243 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: api/v2/node_report_service.proto + +/* +Package v2 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v2 + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_NodeReportService_PostNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportConfiguration + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.PostNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_PostNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportConfiguration + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.PostNodeReportConfiguration(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_UpdateNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportConfiguration + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.UpdateNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_UpdateNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportConfiguration + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.UpdateNodeReportConfiguration(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_ListNodeReportConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_NodeReportService_ListNodeReportConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RawQuery + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_ListNodeReportConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListNodeReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_ListNodeReportConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RawQuery + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_ListNodeReportConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListNodeReportConfigurations(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_CountNodeReportConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_NodeReportService_CountNodeReportConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RawQuery + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_CountNodeReportConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.CountNodeReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_CountNodeReportConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RawQuery + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_CountNodeReportConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CountNodeReportConfigurations(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_GetNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.GetNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.GetNodeReportConfiguration(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_DeleteNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.DeleteNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_DeleteNodeReportConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.DeleteNodeReportConfiguration(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_RunNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RunReportRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.RunNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_RunNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RunReportRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.RunNodeReport(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_GetNodeReportHistory_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + +func request_NodeReportService_GetNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetReportHistoryRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetReportHistoryRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetNodeReportHistory(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_GetMyNodeReportHistory_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + +func request_NodeReportService_GetMyNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetReportHistoryRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetMyNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetMyNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetMyNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetReportHistoryRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetMyNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetMyNodeReportHistory(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_GetNodeReportStatus_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.GetNodeReportStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetNodeReportStatus_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.GetNodeReportStatus(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_CancelNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.CancelNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_CancelNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.CancelNodeReport(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_DeleteNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteReportRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.DeleteNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_DeleteNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteReportRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.DeleteNodeReport(ctx, &protoReq) + return msg, metadata, err +} + +func request_NodeReportService_PostViewBasedNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportRequestViewBased + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.PostViewBasedNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_PostViewBasedNodeReport_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReportRequestViewBased + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.PostViewBasedNodeReport(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_GetViewBasedNodeReportHistory_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_NodeReportService_GetViewBasedNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetViewBasedReportHistoryRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetViewBasedNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetViewBasedNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetViewBasedReportHistoryRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetViewBasedNodeReportHistory(ctx, &protoReq) + return msg, metadata, err +} + +var filter_NodeReportService_GetViewBasedMyNodeReportHistory_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_NodeReportService_GetViewBasedMyNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, client NodeReportServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetViewBasedReportHistoryRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedMyNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetViewBasedMyNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_NodeReportService_GetViewBasedMyNodeReportHistory_0(ctx context.Context, marshaler runtime.Marshaler, server NodeReportServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetViewBasedReportHistoryRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedMyNodeReportHistory_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetViewBasedMyNodeReportHistory(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterNodeReportServiceHandlerServer registers the http handlers for service NodeReportService to "mux". +// UnaryRPC :call NodeReportServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterNodeReportServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterNodeReportServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server NodeReportServiceServer) error { + mux.Handle(http.MethodPost, pattern_NodeReportService_PostNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/PostNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_PostNodeReportConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_PostNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_NodeReportService_UpdateNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/UpdateNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_UpdateNodeReportConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_UpdateNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_ListNodeReportConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/ListNodeReportConfigurations", runtime.WithHTTPPathPattern("/v2/reports/node/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_ListNodeReportConfigurations_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_ListNodeReportConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_CountNodeReportConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/CountNodeReportConfigurations", runtime.WithHTTPPathPattern("/v2/reports/node/configuration-count")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_CountNodeReportConfigurations_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_CountNodeReportConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetNodeReportConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_DeleteNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/DeleteNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_DeleteNodeReportConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_DeleteNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_NodeReportService_RunNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/RunNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/run")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_RunNodeReport_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_RunNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}/history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetNodeReportHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetMyNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetMyNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}/my-history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetMyNodeReportHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetMyNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportStatus", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetNodeReportStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_CancelNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/CancelNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/cancel")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_CancelNodeReport_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_CancelNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_DeleteNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/DeleteNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/delete")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_DeleteNodeReport_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_DeleteNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_NodeReportService_PostViewBasedNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/PostViewBasedNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/run")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_PostViewBasedNodeReport_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_PostViewBasedNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetViewBasedNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetViewBasedNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetViewBasedNodeReportHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetViewBasedNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetViewBasedMyNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v2.NodeReportService/GetViewBasedMyNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/my-history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_NodeReportService_GetViewBasedMyNodeReportHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetViewBasedMyNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterNodeReportServiceHandlerFromEndpoint is same as RegisterNodeReportServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterNodeReportServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterNodeReportServiceHandler(ctx, mux, conn) +} + +// RegisterNodeReportServiceHandler registers the http handlers for service NodeReportService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterNodeReportServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterNodeReportServiceHandlerClient(ctx, mux, NewNodeReportServiceClient(conn)) +} + +// RegisterNodeReportServiceHandlerClient registers the http handlers for service NodeReportService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "NodeReportServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "NodeReportServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "NodeReportServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterNodeReportServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client NodeReportServiceClient) error { + mux.Handle(http.MethodPost, pattern_NodeReportService_PostNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/PostNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_PostNodeReportConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_PostNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_NodeReportService_UpdateNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/UpdateNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_UpdateNodeReportConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_UpdateNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_ListNodeReportConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/ListNodeReportConfigurations", runtime.WithHTTPPathPattern("/v2/reports/node/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_ListNodeReportConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_ListNodeReportConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_CountNodeReportConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/CountNodeReportConfigurations", runtime.WithHTTPPathPattern("/v2/reports/node/configuration-count")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_CountNodeReportConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_CountNodeReportConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetNodeReportConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_DeleteNodeReportConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/DeleteNodeReportConfiguration", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_DeleteNodeReportConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_DeleteNodeReportConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_NodeReportService_RunNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/RunNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/run")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_RunNodeReport_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_RunNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}/history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetNodeReportHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetMyNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetMyNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/configurations/{id}/my-history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetMyNodeReportHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetMyNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetNodeReportStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetNodeReportStatus", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetNodeReportStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetNodeReportStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_CancelNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/CancelNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/cancel")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_CancelNodeReport_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_CancelNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_NodeReportService_DeleteNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/DeleteNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/jobs/{id}/delete")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_DeleteNodeReport_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_DeleteNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_NodeReportService_PostViewBasedNodeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/PostViewBasedNodeReport", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/run")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_PostViewBasedNodeReport_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_PostViewBasedNodeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetViewBasedNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetViewBasedNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetViewBasedNodeReportHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetViewBasedNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_NodeReportService_GetViewBasedMyNodeReportHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v2.NodeReportService/GetViewBasedMyNodeReportHistory", runtime.WithHTTPPathPattern("/v2/reports/node/view-based/my-history")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_NodeReportService_GetViewBasedMyNodeReportHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_NodeReportService_GetViewBasedMyNodeReportHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_NodeReportService_PostNodeReportConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "reports", "node", "configurations"}, "")) + pattern_NodeReportService_UpdateNodeReportConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "reports", "node", "configurations", "id"}, "")) + pattern_NodeReportService_ListNodeReportConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "reports", "node", "configurations"}, "")) + pattern_NodeReportService_CountNodeReportConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "reports", "node", "configuration-count"}, "")) + pattern_NodeReportService_GetNodeReportConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "reports", "node", "configurations", "id"}, "")) + pattern_NodeReportService_DeleteNodeReportConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "reports", "node", "configurations", "id"}, "")) + pattern_NodeReportService_RunNodeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "reports", "node", "run"}, "")) + pattern_NodeReportService_GetNodeReportHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v2", "reports", "node", "configurations", "id", "history"}, "")) + pattern_NodeReportService_GetMyNodeReportHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v2", "reports", "node", "configurations", "id", "my-history"}, "")) + pattern_NodeReportService_GetNodeReportStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v2", "reports", "node", "jobs", "id", "status"}, "")) + pattern_NodeReportService_CancelNodeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v2", "reports", "node", "jobs", "id", "cancel"}, "")) + pattern_NodeReportService_DeleteNodeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v2", "reports", "node", "jobs", "id", "delete"}, "")) + pattern_NodeReportService_PostViewBasedNodeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"v2", "reports", "node", "view-based", "run"}, "")) + pattern_NodeReportService_GetViewBasedNodeReportHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"v2", "reports", "node", "view-based", "history"}, "")) + pattern_NodeReportService_GetViewBasedMyNodeReportHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"v2", "reports", "node", "view-based", "my-history"}, "")) +) + +var ( + forward_NodeReportService_PostNodeReportConfiguration_0 = runtime.ForwardResponseMessage + forward_NodeReportService_UpdateNodeReportConfiguration_0 = runtime.ForwardResponseMessage + forward_NodeReportService_ListNodeReportConfigurations_0 = runtime.ForwardResponseMessage + forward_NodeReportService_CountNodeReportConfigurations_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetNodeReportConfiguration_0 = runtime.ForwardResponseMessage + forward_NodeReportService_DeleteNodeReportConfiguration_0 = runtime.ForwardResponseMessage + forward_NodeReportService_RunNodeReport_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetNodeReportHistory_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetMyNodeReportHistory_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetNodeReportStatus_0 = runtime.ForwardResponseMessage + forward_NodeReportService_CancelNodeReport_0 = runtime.ForwardResponseMessage + forward_NodeReportService_DeleteNodeReport_0 = runtime.ForwardResponseMessage + forward_NodeReportService_PostViewBasedNodeReport_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetViewBasedNodeReportHistory_0 = runtime.ForwardResponseMessage + forward_NodeReportService_GetViewBasedMyNodeReportHistory_0 = runtime.ForwardResponseMessage +) diff --git a/generated/api/v2/node_report_service.swagger.json b/generated/api/v2/node_report_service.swagger.json new file mode 100644 index 0000000000000..7af235e1fb798 --- /dev/null +++ b/generated/api/v2/node_report_service.swagger.json @@ -0,0 +1,1454 @@ +{ + "swagger": "2.0", + "info": { + "title": "api/v2/node_report_service.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "NodeReportService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v2/reports/node/configuration-count": { + "get": { + "operationId": "NodeReportService_CountNodeReportConfigurations", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2CountReportConfigurationsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/configurations": { + "get": { + "operationId": "NodeReportService_ListNodeReportConfigurations", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ListReportConfigurationsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + }, + "post": { + "operationId": "NodeReportService_PostNodeReportConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v2ReportConfiguration" + } + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/configurations/{id}": { + "get": { + "operationId": "NodeReportService_GetNodeReportConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NodeReportService" + ] + }, + "delete": { + "operationId": "NodeReportService_DeleteNodeReportConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2Empty" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NodeReportService" + ] + }, + "put": { + "operationId": "NodeReportService_UpdateNodeReportConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2Empty" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/NodeReportServiceUpdateNodeReportConfigurationBody" + } + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/configurations/{id}/history": { + "get": { + "operationId": "NodeReportService_GetNodeReportHistory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportHistoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "reportParamQuery.query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/configurations/{id}/my-history": { + "get": { + "operationId": "NodeReportService_GetMyNodeReportHistory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportHistoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "reportParamQuery.query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/jobs/{id}/cancel": { + "delete": { + "operationId": "NodeReportService_CancelNodeReport", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2Empty" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/jobs/{id}/delete": { + "delete": { + "operationId": "NodeReportService_DeleteNodeReport", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2Empty" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/jobs/{id}/status": { + "get": { + "operationId": "NodeReportService_GetNodeReportStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/run": { + "post": { + "operationId": "NodeReportService_RunNodeReport", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2RunReportResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v2RunReportRequest" + } + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/view-based/history": { + "get": { + "operationId": "NodeReportService_GetViewBasedNodeReportHistory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportHistoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "reportParamQuery.query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/view-based/my-history": { + "get": { + "operationId": "NodeReportService_GetViewBasedMyNodeReportHistory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2ReportHistoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "reportParamQuery.query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "reportParamQuery.pagination.sortOption.field", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "reportParamQuery.pagination.sortOption.reversed", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.aggrFunc", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + { + "name": "reportParamQuery.pagination.sortOption.aggregateBy.distinct", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "NodeReportService" + ] + } + }, + "/v2/reports/node/view-based/run": { + "post": { + "operationId": "NodeReportService_PostViewBasedNodeReport", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v2RunReportResponseViewBased" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v2ReportRequestViewBased" + } + } + ], + "tags": [ + "NodeReportService" + ] + } + } + }, + "definitions": { + "NodeReportServiceUpdateNodeReportConfigurationBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/v2ReportConfigurationReportType" + }, + "vulnReportFilters": { + "$ref": "#/definitions/v2VulnerabilityReportFilters" + }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, + "schedule": { + "$ref": "#/definitions/v2ReportSchedule" + }, + "resourceScope": { + "$ref": "#/definitions/v2ResourceScope" + }, + "notifiers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2NotifierConfiguration" + } + } + } + }, + "VulnerabilityReportFiltersFixability": { + "type": "string", + "enum": [ + "BOTH", + "FIXABLE", + "NOT_FIXABLE" + ], + "default": "BOTH" + }, + "VulnerabilityReportFiltersImageType": { + "type": "string", + "enum": [ + "DEPLOYED", + "WATCHED" + ], + "default": "DEPLOYED" + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v2AggregateBy": { + "type": "object", + "properties": { + "aggrFunc": { + "$ref": "#/definitions/v2Aggregation" + }, + "distinct": { + "type": "boolean" + } + } + }, + "v2Aggregation": { + "type": "string", + "enum": [ + "UNSET", + "COUNT", + "MIN", + "MAX" + ], + "default": "UNSET" + }, + "v2CollectionReference": { + "type": "object", + "properties": { + "collectionId": { + "type": "string" + }, + "collectionName": { + "type": "string" + } + } + }, + "v2CollectionSnapshot": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "v2CountReportConfigurationsResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "format": "int32" + } + } + }, + "v2EmailNotifierConfiguration": { + "type": "object", + "properties": { + "notifierId": { + "type": "string" + }, + "mailingLists": { + "type": "array", + "items": { + "type": "string" + } + }, + "customSubject": { + "type": "string" + }, + "customBody": { + "type": "string" + } + } + }, + "v2Empty": { + "type": "object" + }, + "v2EntityScope": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2EntityScopeRule" + } + } + } + }, + "v2EntityScopeRule": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/definitions/v2ScopeEntity" + }, + "field": { + "$ref": "#/definitions/v2ScopeField" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2RuleValue" + } + } + } + }, + "v2ListReportConfigurationsResponse": { + "type": "object", + "properties": { + "reportConfigs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2ReportConfiguration" + } + } + } + }, + "v2MatchType": { + "type": "string", + "enum": [ + "EXACT", + "REGEX" + ], + "default": "EXACT" + }, + "v2NodeVulnerabilityReportFilters": { + "type": "object", + "properties": { + "allVuln": { + "type": "boolean" + }, + "query": { + "type": "string" + } + } + }, + "v2NotificationMethod": { + "type": "string", + "enum": [ + "EMAIL", + "DOWNLOAD" + ], + "default": "EMAIL" + }, + "v2NotifierConfiguration": { + "type": "object", + "properties": { + "emailConfig": { + "$ref": "#/definitions/v2EmailNotifierConfiguration" + }, + "notifierName": { + "type": "string" + } + } + }, + "v2Pagination": { + "type": "object", + "properties": { + "limit": { + "type": "integer", + "format": "int32" + }, + "offset": { + "type": "integer", + "format": "int32" + }, + "sortOption": { + "$ref": "#/definitions/v2SortOption" + }, + "sortOptions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2SortOption" + }, + "description": "This field is under development. It is not supported on any REST APIs." + } + } + }, + "v2RawQuery": { + "type": "object", + "properties": { + "query": { + "type": "string" + }, + "pagination": { + "$ref": "#/definitions/v2Pagination" + } + }, + "description": "RawQuery represents the search query string.\nThe format of the query string is \":+:+...\"\nFor example:\nTo search for deployments named \"central\" and \"sensor\" in the namespace \"stackrox\", the query string would be\n\"Deployment:central,sensor+Namespace:stackrox\"\nRawQuery is used in ListAPIs to search for a particular object." + }, + "v2ReportConfiguration": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/v2ReportConfigurationReportType" + }, + "vulnReportFilters": { + "$ref": "#/definitions/v2VulnerabilityReportFilters" + }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, + "schedule": { + "$ref": "#/definitions/v2ReportSchedule" + }, + "resourceScope": { + "$ref": "#/definitions/v2ResourceScope" + }, + "notifiers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2NotifierConfiguration" + } + } + } + }, + "v2ReportConfigurationReportType": { + "type": "string", + "enum": [ + "VULNERABILITY", + "NODE_VULNERABILITY" + ], + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + }, + "v2ReportHistoryResponse": { + "type": "object", + "properties": { + "reportSnapshots": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2ReportSnapshot" + } + } + } + }, + "v2ReportRequestViewBased": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/v2ReportRequestViewBasedReportType" + }, + "viewBasedVulnReportFilters": { + "$ref": "#/definitions/v2ViewBasedVulnerabilityReportFilters" + }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, + "areaOfConcern": { + "type": "string" + } + } + }, + "v2ReportRequestViewBasedReportType": { + "type": "string", + "enum": [ + "VULNERABILITY", + "NODE_VULNERABILITY" + ], + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + }, + "v2ReportSchedule": { + "type": "object", + "properties": { + "intervalType": { + "$ref": "#/definitions/v2ReportScheduleIntervalType" + }, + "hour": { + "type": "integer", + "format": "int32" + }, + "minute": { + "type": "integer", + "format": "int32" + }, + "daysOfWeek": { + "$ref": "#/definitions/v2ReportScheduleDaysOfWeek" + }, + "daysOfMonth": { + "$ref": "#/definitions/v2ReportScheduleDaysOfMonth" + } + } + }, + "v2ReportScheduleDaysOfMonth": { + "type": "object", + "properties": { + "days": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "title": "1 for 1st, 2 for 2nd .... 31 for 31st" + }, + "v2ReportScheduleDaysOfWeek": { + "type": "object", + "properties": { + "days": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "title": "Sunday = 0, Monday = 1, .... Saturday = 6" + }, + "v2ReportScheduleIntervalType": { + "type": "string", + "enum": [ + "UNSET", + "WEEKLY", + "MONTHLY", + "DAILY" + ], + "default": "UNSET" + }, + "v2ReportSnapshot": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/v2ReportSnapshotReportType" + }, + "reportConfigId": { + "type": "string" + }, + "reportJobId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "vulnReportFilters": { + "$ref": "#/definitions/v2VulnerabilityReportFilters" + }, + "viewBasedVulnReportFilters": { + "$ref": "#/definitions/v2ViewBasedVulnerabilityReportFilters" + }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, + "collectionSnapshot": { + "$ref": "#/definitions/v2CollectionSnapshot" + }, + "schedule": { + "$ref": "#/definitions/v2ReportSchedule" + }, + "reportStatus": { + "$ref": "#/definitions/v2ReportStatus" + }, + "notifiers": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v2NotifierConfiguration" + } + }, + "user": { + "$ref": "#/definitions/v2SlimUser" + }, + "isDownloadAvailable": { + "type": "boolean" + }, + "areaOfConcern": { + "type": "string" + }, + "resourceScope": { + "$ref": "#/definitions/v2ResourceScope" + } + } + }, + "v2ReportSnapshotReportType": { + "type": "string", + "enum": [ + "VULNERABILITY", + "NODE_VULNERABILITY" + ], + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + }, + "v2ReportStatus": { + "type": "object", + "properties": { + "runState": { + "$ref": "#/definitions/v2ReportStatusRunState" + }, + "completedAt": { + "type": "string", + "format": "date-time" + }, + "errorMsg": { + "type": "string" + }, + "reportRequestType": { + "$ref": "#/definitions/v2ReportStatusReportMethod" + }, + "reportNotificationMethod": { + "$ref": "#/definitions/v2NotificationMethod" + } + } + }, + "v2ReportStatusReportMethod": { + "type": "string", + "enum": [ + "ON_DEMAND", + "SCHEDULED" + ], + "default": "ON_DEMAND" + }, + "v2ReportStatusResponse": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/v2ReportStatus" + } + } + }, + "v2ReportStatusRunState": { + "type": "string", + "enum": [ + "WAITING", + "PREPARING", + "GENERATED", + "DELIVERED", + "FAILURE" + ], + "default": "WAITING" + }, + "v2ResourceScope": { + "type": "object", + "properties": { + "collectionScope": { + "$ref": "#/definitions/v2CollectionReference" + }, + "entityScope": { + "$ref": "#/definitions/v2EntityScope", + "title": "scope for deployment, namespace, cluster" + } + }, + "description": "Reports are transitioning to a modernized architecture in a future release.\nTo prepare for future updates, we recommend transitioning your collections-based reports to the “Entity Scope” method.\nWhen the “Collection scope” option is removed in a future release, existing reports will not automatically convert to the “Entity scope” method." + }, + "v2RuleValue": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "matchType": { + "$ref": "#/definitions/v2MatchType" + } + } + }, + "v2RunReportRequest": { + "type": "object", + "properties": { + "reportConfigId": { + "type": "string" + }, + "reportNotificationMethod": { + "$ref": "#/definitions/v2NotificationMethod" + } + } + }, + "v2RunReportResponse": { + "type": "object", + "properties": { + "reportConfigId": { + "type": "string" + }, + "reportId": { + "type": "string" + } + } + }, + "v2RunReportResponseViewBased": { + "type": "object", + "properties": { + "reportID": { + "type": "string" + }, + "requestName": { + "type": "string" + } + } + }, + "v2ScopeEntity": { + "type": "string", + "enum": [ + "SCOPE_ENTITY_UNSET", + "SCOPE_ENTITY_DEPLOYMENT", + "SCOPE_ENTITY_NAMESPACE", + "SCOPE_ENTITY_CLUSTER" + ], + "default": "SCOPE_ENTITY_UNSET" + }, + "v2ScopeField": { + "type": "string", + "enum": [ + "FIELD_UNSET", + "FIELD_ID", + "FIELD_NAME", + "FIELD_LABEL", + "FIELD_ANNOTATION" + ], + "default": "FIELD_UNSET" + }, + "v2SlimUser": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "v2SortOption": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "reversed": { + "type": "boolean" + }, + "aggregateBy": { + "$ref": "#/definitions/v2AggregateBy", + "description": "This field is under development. It is not supported on any REST APIs." + } + } + }, + "v2ViewBasedVulnerabilityReportFilters": { + "type": "object", + "properties": { + "query": { + "type": "string" + } + }, + "title": "filter for ondemand view based reports" + }, + "v2VulnerabilityReportFilters": { + "type": "object", + "properties": { + "fixability": { + "$ref": "#/definitions/VulnerabilityReportFiltersFixability" + }, + "severities": { + "type": "array", + "items": { + "$ref": "#/definitions/v2VulnerabilityReportFiltersVulnerabilitySeverity" + } + }, + "imageTypes": { + "type": "array", + "items": { + "$ref": "#/definitions/VulnerabilityReportFiltersImageType" + } + }, + "allVuln": { + "type": "boolean" + }, + "sinceLastSentScheduledReport": { + "type": "boolean" + }, + "sinceStartDate": { + "type": "string", + "format": "date-time" + }, + "includeNvdCvss": { + "type": "boolean", + "description": "Deprecated: These fields are no longer used.\nNVD CVSS, EPSS Probability and Advisory columns are always included in reports." + }, + "includeEpssProbability": { + "type": "boolean" + }, + "includeAdvisory": { + "type": "boolean" + }, + "query": { + "type": "string", + "title": "Filters related to image, cve etc for non-collection-based scopes" + } + } + }, + "v2VulnerabilityReportFiltersVulnerabilitySeverity": { + "type": "string", + "enum": [ + "UNKNOWN_VULNERABILITY_SEVERITY", + "LOW_VULNERABILITY_SEVERITY", + "MODERATE_VULNERABILITY_SEVERITY", + "IMPORTANT_VULNERABILITY_SEVERITY", + "CRITICAL_VULNERABILITY_SEVERITY" + ], + "default": "UNKNOWN_VULNERABILITY_SEVERITY" + } + } +} diff --git a/generated/api/v2/node_report_service_grpc.pb.go b/generated/api/v2/node_report_service_grpc.pb.go new file mode 100644 index 0000000000000..bd8f96d000832 --- /dev/null +++ b/generated/api/v2/node_report_service_grpc.pb.go @@ -0,0 +1,651 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc v6.32.1 +// source: api/v2/node_report_service.proto + +package v2 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + NodeReportService_PostNodeReportConfiguration_FullMethodName = "/v2.NodeReportService/PostNodeReportConfiguration" + NodeReportService_UpdateNodeReportConfiguration_FullMethodName = "/v2.NodeReportService/UpdateNodeReportConfiguration" + NodeReportService_ListNodeReportConfigurations_FullMethodName = "/v2.NodeReportService/ListNodeReportConfigurations" + NodeReportService_CountNodeReportConfigurations_FullMethodName = "/v2.NodeReportService/CountNodeReportConfigurations" + NodeReportService_GetNodeReportConfiguration_FullMethodName = "/v2.NodeReportService/GetNodeReportConfiguration" + NodeReportService_DeleteNodeReportConfiguration_FullMethodName = "/v2.NodeReportService/DeleteNodeReportConfiguration" + NodeReportService_RunNodeReport_FullMethodName = "/v2.NodeReportService/RunNodeReport" + NodeReportService_GetNodeReportHistory_FullMethodName = "/v2.NodeReportService/GetNodeReportHistory" + NodeReportService_GetMyNodeReportHistory_FullMethodName = "/v2.NodeReportService/GetMyNodeReportHistory" + NodeReportService_GetNodeReportStatus_FullMethodName = "/v2.NodeReportService/GetNodeReportStatus" + NodeReportService_CancelNodeReport_FullMethodName = "/v2.NodeReportService/CancelNodeReport" + NodeReportService_DeleteNodeReport_FullMethodName = "/v2.NodeReportService/DeleteNodeReport" + NodeReportService_PostViewBasedNodeReport_FullMethodName = "/v2.NodeReportService/PostViewBasedNodeReport" + NodeReportService_GetViewBasedNodeReportHistory_FullMethodName = "/v2.NodeReportService/GetViewBasedNodeReportHistory" + NodeReportService_GetViewBasedMyNodeReportHistory_FullMethodName = "/v2.NodeReportService/GetViewBasedMyNodeReportHistory" +) + +// NodeReportServiceClient is the client API for NodeReportService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NodeReportServiceClient interface { + PostNodeReportConfiguration(ctx context.Context, in *ReportConfiguration, opts ...grpc.CallOption) (*ReportConfiguration, error) + UpdateNodeReportConfiguration(ctx context.Context, in *ReportConfiguration, opts ...grpc.CallOption) (*Empty, error) + ListNodeReportConfigurations(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*ListReportConfigurationsResponse, error) + CountNodeReportConfigurations(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*CountReportConfigurationsResponse, error) + GetNodeReportConfiguration(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*ReportConfiguration, error) + DeleteNodeReportConfiguration(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*Empty, error) + RunNodeReport(ctx context.Context, in *RunReportRequest, opts ...grpc.CallOption) (*RunReportResponse, error) + GetNodeReportHistory(ctx context.Context, in *GetReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) + GetMyNodeReportHistory(ctx context.Context, in *GetReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) + GetNodeReportStatus(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*ReportStatusResponse, error) + CancelNodeReport(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*Empty, error) + DeleteNodeReport(ctx context.Context, in *DeleteReportRequest, opts ...grpc.CallOption) (*Empty, error) + PostViewBasedNodeReport(ctx context.Context, in *ReportRequestViewBased, opts ...grpc.CallOption) (*RunReportResponseViewBased, error) + GetViewBasedNodeReportHistory(ctx context.Context, in *GetViewBasedReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) + GetViewBasedMyNodeReportHistory(ctx context.Context, in *GetViewBasedReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) +} + +type nodeReportServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewNodeReportServiceClient(cc grpc.ClientConnInterface) NodeReportServiceClient { + return &nodeReportServiceClient{cc} +} + +func (c *nodeReportServiceClient) PostNodeReportConfiguration(ctx context.Context, in *ReportConfiguration, opts ...grpc.CallOption) (*ReportConfiguration, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportConfiguration) + err := c.cc.Invoke(ctx, NodeReportService_PostNodeReportConfiguration_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) UpdateNodeReportConfiguration(ctx context.Context, in *ReportConfiguration, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, NodeReportService_UpdateNodeReportConfiguration_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) ListNodeReportConfigurations(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*ListReportConfigurationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListReportConfigurationsResponse) + err := c.cc.Invoke(ctx, NodeReportService_ListNodeReportConfigurations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) CountNodeReportConfigurations(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*CountReportConfigurationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CountReportConfigurationsResponse) + err := c.cc.Invoke(ctx, NodeReportService_CountNodeReportConfigurations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetNodeReportConfiguration(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*ReportConfiguration, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportConfiguration) + err := c.cc.Invoke(ctx, NodeReportService_GetNodeReportConfiguration_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) DeleteNodeReportConfiguration(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, NodeReportService_DeleteNodeReportConfiguration_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) RunNodeReport(ctx context.Context, in *RunReportRequest, opts ...grpc.CallOption) (*RunReportResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RunReportResponse) + err := c.cc.Invoke(ctx, NodeReportService_RunNodeReport_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetNodeReportHistory(ctx context.Context, in *GetReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportHistoryResponse) + err := c.cc.Invoke(ctx, NodeReportService_GetNodeReportHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetMyNodeReportHistory(ctx context.Context, in *GetReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportHistoryResponse) + err := c.cc.Invoke(ctx, NodeReportService_GetMyNodeReportHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetNodeReportStatus(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*ReportStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportStatusResponse) + err := c.cc.Invoke(ctx, NodeReportService_GetNodeReportStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) CancelNodeReport(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, NodeReportService_CancelNodeReport_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) DeleteNodeReport(ctx context.Context, in *DeleteReportRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, NodeReportService_DeleteNodeReport_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) PostViewBasedNodeReport(ctx context.Context, in *ReportRequestViewBased, opts ...grpc.CallOption) (*RunReportResponseViewBased, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RunReportResponseViewBased) + err := c.cc.Invoke(ctx, NodeReportService_PostViewBasedNodeReport_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetViewBasedNodeReportHistory(ctx context.Context, in *GetViewBasedReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportHistoryResponse) + err := c.cc.Invoke(ctx, NodeReportService_GetViewBasedNodeReportHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeReportServiceClient) GetViewBasedMyNodeReportHistory(ctx context.Context, in *GetViewBasedReportHistoryRequest, opts ...grpc.CallOption) (*ReportHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportHistoryResponse) + err := c.cc.Invoke(ctx, NodeReportService_GetViewBasedMyNodeReportHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NodeReportServiceServer is the server API for NodeReportService service. +// All implementations should embed UnimplementedNodeReportServiceServer +// for forward compatibility. +type NodeReportServiceServer interface { + PostNodeReportConfiguration(context.Context, *ReportConfiguration) (*ReportConfiguration, error) + UpdateNodeReportConfiguration(context.Context, *ReportConfiguration) (*Empty, error) + ListNodeReportConfigurations(context.Context, *RawQuery) (*ListReportConfigurationsResponse, error) + CountNodeReportConfigurations(context.Context, *RawQuery) (*CountReportConfigurationsResponse, error) + GetNodeReportConfiguration(context.Context, *ResourceByID) (*ReportConfiguration, error) + DeleteNodeReportConfiguration(context.Context, *ResourceByID) (*Empty, error) + RunNodeReport(context.Context, *RunReportRequest) (*RunReportResponse, error) + GetNodeReportHistory(context.Context, *GetReportHistoryRequest) (*ReportHistoryResponse, error) + GetMyNodeReportHistory(context.Context, *GetReportHistoryRequest) (*ReportHistoryResponse, error) + GetNodeReportStatus(context.Context, *ResourceByID) (*ReportStatusResponse, error) + CancelNodeReport(context.Context, *ResourceByID) (*Empty, error) + DeleteNodeReport(context.Context, *DeleteReportRequest) (*Empty, error) + PostViewBasedNodeReport(context.Context, *ReportRequestViewBased) (*RunReportResponseViewBased, error) + GetViewBasedNodeReportHistory(context.Context, *GetViewBasedReportHistoryRequest) (*ReportHistoryResponse, error) + GetViewBasedMyNodeReportHistory(context.Context, *GetViewBasedReportHistoryRequest) (*ReportHistoryResponse, error) +} + +// UnimplementedNodeReportServiceServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedNodeReportServiceServer struct{} + +func (UnimplementedNodeReportServiceServer) PostNodeReportConfiguration(context.Context, *ReportConfiguration) (*ReportConfiguration, error) { + return nil, status.Error(codes.Unimplemented, "method PostNodeReportConfiguration not implemented") +} +func (UnimplementedNodeReportServiceServer) UpdateNodeReportConfiguration(context.Context, *ReportConfiguration) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateNodeReportConfiguration not implemented") +} +func (UnimplementedNodeReportServiceServer) ListNodeReportConfigurations(context.Context, *RawQuery) (*ListReportConfigurationsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListNodeReportConfigurations not implemented") +} +func (UnimplementedNodeReportServiceServer) CountNodeReportConfigurations(context.Context, *RawQuery) (*CountReportConfigurationsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CountNodeReportConfigurations not implemented") +} +func (UnimplementedNodeReportServiceServer) GetNodeReportConfiguration(context.Context, *ResourceByID) (*ReportConfiguration, error) { + return nil, status.Error(codes.Unimplemented, "method GetNodeReportConfiguration not implemented") +} +func (UnimplementedNodeReportServiceServer) DeleteNodeReportConfiguration(context.Context, *ResourceByID) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteNodeReportConfiguration not implemented") +} +func (UnimplementedNodeReportServiceServer) RunNodeReport(context.Context, *RunReportRequest) (*RunReportResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RunNodeReport not implemented") +} +func (UnimplementedNodeReportServiceServer) GetNodeReportHistory(context.Context, *GetReportHistoryRequest) (*ReportHistoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetNodeReportHistory not implemented") +} +func (UnimplementedNodeReportServiceServer) GetMyNodeReportHistory(context.Context, *GetReportHistoryRequest) (*ReportHistoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetMyNodeReportHistory not implemented") +} +func (UnimplementedNodeReportServiceServer) GetNodeReportStatus(context.Context, *ResourceByID) (*ReportStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetNodeReportStatus not implemented") +} +func (UnimplementedNodeReportServiceServer) CancelNodeReport(context.Context, *ResourceByID) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method CancelNodeReport not implemented") +} +func (UnimplementedNodeReportServiceServer) DeleteNodeReport(context.Context, *DeleteReportRequest) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteNodeReport not implemented") +} +func (UnimplementedNodeReportServiceServer) PostViewBasedNodeReport(context.Context, *ReportRequestViewBased) (*RunReportResponseViewBased, error) { + return nil, status.Error(codes.Unimplemented, "method PostViewBasedNodeReport not implemented") +} +func (UnimplementedNodeReportServiceServer) GetViewBasedNodeReportHistory(context.Context, *GetViewBasedReportHistoryRequest) (*ReportHistoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetViewBasedNodeReportHistory not implemented") +} +func (UnimplementedNodeReportServiceServer) GetViewBasedMyNodeReportHistory(context.Context, *GetViewBasedReportHistoryRequest) (*ReportHistoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetViewBasedMyNodeReportHistory not implemented") +} +func (UnimplementedNodeReportServiceServer) testEmbeddedByValue() {} + +// UnsafeNodeReportServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NodeReportServiceServer will +// result in compilation errors. +type UnsafeNodeReportServiceServer interface { + mustEmbedUnimplementedNodeReportServiceServer() +} + +func RegisterNodeReportServiceServer(s grpc.ServiceRegistrar, srv NodeReportServiceServer) { + // If the following call panics, it indicates UnimplementedNodeReportServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&NodeReportService_ServiceDesc, srv) +} + +func _NodeReportService_PostNodeReportConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportConfiguration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).PostNodeReportConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_PostNodeReportConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).PostNodeReportConfiguration(ctx, req.(*ReportConfiguration)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_UpdateNodeReportConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportConfiguration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).UpdateNodeReportConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_UpdateNodeReportConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).UpdateNodeReportConfiguration(ctx, req.(*ReportConfiguration)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_ListNodeReportConfigurations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RawQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).ListNodeReportConfigurations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_ListNodeReportConfigurations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).ListNodeReportConfigurations(ctx, req.(*RawQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_CountNodeReportConfigurations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RawQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).CountNodeReportConfigurations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_CountNodeReportConfigurations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).CountNodeReportConfigurations(ctx, req.(*RawQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetNodeReportConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResourceByID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetNodeReportConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetNodeReportConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetNodeReportConfiguration(ctx, req.(*ResourceByID)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_DeleteNodeReportConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResourceByID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).DeleteNodeReportConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_DeleteNodeReportConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).DeleteNodeReportConfiguration(ctx, req.(*ResourceByID)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_RunNodeReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RunReportRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).RunNodeReport(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_RunNodeReport_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).RunNodeReport(ctx, req.(*RunReportRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetNodeReportHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReportHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetNodeReportHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetNodeReportHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetNodeReportHistory(ctx, req.(*GetReportHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetMyNodeReportHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReportHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetMyNodeReportHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetMyNodeReportHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetMyNodeReportHistory(ctx, req.(*GetReportHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetNodeReportStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResourceByID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetNodeReportStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetNodeReportStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetNodeReportStatus(ctx, req.(*ResourceByID)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_CancelNodeReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResourceByID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).CancelNodeReport(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_CancelNodeReport_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).CancelNodeReport(ctx, req.(*ResourceByID)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_DeleteNodeReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteReportRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).DeleteNodeReport(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_DeleteNodeReport_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).DeleteNodeReport(ctx, req.(*DeleteReportRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_PostViewBasedNodeReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportRequestViewBased) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).PostViewBasedNodeReport(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_PostViewBasedNodeReport_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).PostViewBasedNodeReport(ctx, req.(*ReportRequestViewBased)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetViewBasedNodeReportHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetViewBasedReportHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetViewBasedNodeReportHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetViewBasedNodeReportHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetViewBasedNodeReportHistory(ctx, req.(*GetViewBasedReportHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeReportService_GetViewBasedMyNodeReportHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetViewBasedReportHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeReportServiceServer).GetViewBasedMyNodeReportHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NodeReportService_GetViewBasedMyNodeReportHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeReportServiceServer).GetViewBasedMyNodeReportHistory(ctx, req.(*GetViewBasedReportHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// NodeReportService_ServiceDesc is the grpc.ServiceDesc for NodeReportService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NodeReportService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "v2.NodeReportService", + HandlerType: (*NodeReportServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PostNodeReportConfiguration", + Handler: _NodeReportService_PostNodeReportConfiguration_Handler, + }, + { + MethodName: "UpdateNodeReportConfiguration", + Handler: _NodeReportService_UpdateNodeReportConfiguration_Handler, + }, + { + MethodName: "ListNodeReportConfigurations", + Handler: _NodeReportService_ListNodeReportConfigurations_Handler, + }, + { + MethodName: "CountNodeReportConfigurations", + Handler: _NodeReportService_CountNodeReportConfigurations_Handler, + }, + { + MethodName: "GetNodeReportConfiguration", + Handler: _NodeReportService_GetNodeReportConfiguration_Handler, + }, + { + MethodName: "DeleteNodeReportConfiguration", + Handler: _NodeReportService_DeleteNodeReportConfiguration_Handler, + }, + { + MethodName: "RunNodeReport", + Handler: _NodeReportService_RunNodeReport_Handler, + }, + { + MethodName: "GetNodeReportHistory", + Handler: _NodeReportService_GetNodeReportHistory_Handler, + }, + { + MethodName: "GetMyNodeReportHistory", + Handler: _NodeReportService_GetMyNodeReportHistory_Handler, + }, + { + MethodName: "GetNodeReportStatus", + Handler: _NodeReportService_GetNodeReportStatus_Handler, + }, + { + MethodName: "CancelNodeReport", + Handler: _NodeReportService_CancelNodeReport_Handler, + }, + { + MethodName: "DeleteNodeReport", + Handler: _NodeReportService_DeleteNodeReport_Handler, + }, + { + MethodName: "PostViewBasedNodeReport", + Handler: _NodeReportService_PostViewBasedNodeReport_Handler, + }, + { + MethodName: "GetViewBasedNodeReportHistory", + Handler: _NodeReportService_GetViewBasedNodeReportHistory_Handler, + }, + { + MethodName: "GetViewBasedMyNodeReportHistory", + Handler: _NodeReportService_GetViewBasedMyNodeReportHistory_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/v2/node_report_service.proto", +} diff --git a/generated/api/v2/report_service.pb.go b/generated/api/v2/report_service.pb.go index 5813d7bdbc563..f33e6d1df9466 100644 --- a/generated/api/v2/report_service.pb.go +++ b/generated/api/v2/report_service.pb.go @@ -225,16 +225,19 @@ func (NotificationMethod) EnumDescriptor() ([]byte, []int) { type ReportConfiguration_ReportType int32 const ( - ReportConfiguration_VULNERABILITY ReportConfiguration_ReportType = 0 + ReportConfiguration_VULNERABILITY ReportConfiguration_ReportType = 0 // for historical reasons, this short form was used to mean "Image Vulnerability" + ReportConfiguration_NODE_VULNERABILITY ReportConfiguration_ReportType = 1 ) // Enum value maps for ReportConfiguration_ReportType. var ( ReportConfiguration_ReportType_name = map[int32]string{ 0: "VULNERABILITY", + 1: "NODE_VULNERABILITY", } ReportConfiguration_ReportType_value = map[string]int32{ - "VULNERABILITY": 0, + "VULNERABILITY": 0, + "NODE_VULNERABILITY": 1, } ) @@ -464,7 +467,53 @@ func (x ReportSchedule_IntervalType) Number() protoreflect.EnumNumber { // Deprecated: Use ReportSchedule_IntervalType.Descriptor instead. func (ReportSchedule_IntervalType) EnumDescriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{3, 0} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{4, 0} +} + +type ReportSnapshot_ReportType int32 + +const ( + ReportSnapshot_VULNERABILITY ReportSnapshot_ReportType = 0 // for historical reasons, this short form was used to mean "Image Vulnerability" + ReportSnapshot_NODE_VULNERABILITY ReportSnapshot_ReportType = 1 +) + +// Enum value maps for ReportSnapshot_ReportType. +var ( + ReportSnapshot_ReportType_name = map[int32]string{ + 0: "VULNERABILITY", + 1: "NODE_VULNERABILITY", + } + ReportSnapshot_ReportType_value = map[string]int32{ + "VULNERABILITY": 0, + "NODE_VULNERABILITY": 1, + } +) + +func (x ReportSnapshot_ReportType) Enum() *ReportSnapshot_ReportType { + p := new(ReportSnapshot_ReportType) + *p = x + return p +} + +func (x ReportSnapshot_ReportType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReportSnapshot_ReportType) Descriptor() protoreflect.EnumDescriptor { + return file_api_v2_report_service_proto_enumTypes[9].Descriptor() +} + +func (ReportSnapshot_ReportType) Type() protoreflect.EnumType { + return &file_api_v2_report_service_proto_enumTypes[9] +} + +func (x ReportSnapshot_ReportType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReportSnapshot_ReportType.Descriptor instead. +func (ReportSnapshot_ReportType) EnumDescriptor() ([]byte, []int) { + return file_api_v2_report_service_proto_rawDescGZIP(), []int{19, 0} } type ReportStatus_RunState int32 @@ -506,11 +555,11 @@ func (x ReportStatus_RunState) String() string { } func (ReportStatus_RunState) Descriptor() protoreflect.EnumDescriptor { - return file_api_v2_report_service_proto_enumTypes[9].Descriptor() + return file_api_v2_report_service_proto_enumTypes[10].Descriptor() } func (ReportStatus_RunState) Type() protoreflect.EnumType { - return &file_api_v2_report_service_proto_enumTypes[9] + return &file_api_v2_report_service_proto_enumTypes[10] } func (x ReportStatus_RunState) Number() protoreflect.EnumNumber { @@ -519,7 +568,7 @@ func (x ReportStatus_RunState) Number() protoreflect.EnumNumber { // Deprecated: Use ReportStatus_RunState.Descriptor instead. func (ReportStatus_RunState) EnumDescriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{19, 0} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{20, 0} } type ReportStatus_ReportMethod int32 @@ -552,11 +601,11 @@ func (x ReportStatus_ReportMethod) String() string { } func (ReportStatus_ReportMethod) Descriptor() protoreflect.EnumDescriptor { - return file_api_v2_report_service_proto_enumTypes[10].Descriptor() + return file_api_v2_report_service_proto_enumTypes[11].Descriptor() } func (ReportStatus_ReportMethod) Type() protoreflect.EnumType { - return &file_api_v2_report_service_proto_enumTypes[10] + return &file_api_v2_report_service_proto_enumTypes[11] } func (x ReportStatus_ReportMethod) Number() protoreflect.EnumNumber { @@ -565,22 +614,25 @@ func (x ReportStatus_ReportMethod) Number() protoreflect.EnumNumber { // Deprecated: Use ReportStatus_ReportMethod.Descriptor instead. func (ReportStatus_ReportMethod) EnumDescriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{19, 1} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{20, 1} } type ReportRequestViewBased_ReportType int32 const ( - ReportRequestViewBased_VULNERABILITY ReportRequestViewBased_ReportType = 0 + ReportRequestViewBased_VULNERABILITY ReportRequestViewBased_ReportType = 0 // for historical reasons, this short form was used to mean "Image Vulnerability" + ReportRequestViewBased_NODE_VULNERABILITY ReportRequestViewBased_ReportType = 1 ) // Enum value maps for ReportRequestViewBased_ReportType. var ( ReportRequestViewBased_ReportType_name = map[int32]string{ 0: "VULNERABILITY", + 1: "NODE_VULNERABILITY", } ReportRequestViewBased_ReportType_value = map[string]int32{ - "VULNERABILITY": 0, + "VULNERABILITY": 0, + "NODE_VULNERABILITY": 1, } ) @@ -595,11 +647,11 @@ func (x ReportRequestViewBased_ReportType) String() string { } func (ReportRequestViewBased_ReportType) Descriptor() protoreflect.EnumDescriptor { - return file_api_v2_report_service_proto_enumTypes[11].Descriptor() + return file_api_v2_report_service_proto_enumTypes[12].Descriptor() } func (ReportRequestViewBased_ReportType) Type() protoreflect.EnumType { - return &file_api_v2_report_service_proto_enumTypes[11] + return &file_api_v2_report_service_proto_enumTypes[12] } func (x ReportRequestViewBased_ReportType) Number() protoreflect.EnumNumber { @@ -608,7 +660,7 @@ func (x ReportRequestViewBased_ReportType) Number() protoreflect.EnumNumber { // Deprecated: Use ReportRequestViewBased_ReportType.Descriptor instead. func (ReportRequestViewBased_ReportType) EnumDescriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{23, 0} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{24, 0} } type ReportConfiguration struct { @@ -620,6 +672,7 @@ type ReportConfiguration struct { // Types that are valid to be assigned to Filter: // // *ReportConfiguration_VulnReportFilters + // *ReportConfiguration_NodeVulnReportFilters Filter isReportConfiguration_Filter `protobuf_oneof:"filter"` Schedule *ReportSchedule `protobuf:"bytes,6,opt,name=schedule,proto3" json:"schedule,omitempty"` ResourceScope *ResourceScope `protobuf:"bytes,7,opt,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"` @@ -702,6 +755,15 @@ func (x *ReportConfiguration) GetVulnReportFilters() *VulnerabilityReportFilters return nil } +func (x *ReportConfiguration) GetNodeVulnReportFilters() *NodeVulnerabilityReportFilters { + if x != nil { + if x, ok := x.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + return x.NodeVulnReportFilters + } + } + return nil +} + func (x *ReportConfiguration) GetSchedule() *ReportSchedule { if x != nil { return x.Schedule @@ -731,8 +793,14 @@ type ReportConfiguration_VulnReportFilters struct { VulnReportFilters *VulnerabilityReportFilters `protobuf:"bytes,5,opt,name=vuln_report_filters,json=vulnReportFilters,proto3,oneof"` } +type ReportConfiguration_NodeVulnReportFilters struct { + NodeVulnReportFilters *NodeVulnerabilityReportFilters `protobuf:"bytes,9,opt,name=node_vuln_report_filters,json=nodeVulnReportFilters,proto3,oneof"` +} + func (*ReportConfiguration_VulnReportFilters) isReportConfiguration_Filter() {} +func (*ReportConfiguration_NodeVulnReportFilters) isReportConfiguration_Filter() {} + type VulnerabilityReportFilters struct { state protoimpl.MessageState `protogen:"open.v1"` Fixability VulnerabilityReportFilters_Fixability `protobuf:"varint,1,opt,name=fixability,proto3,enum=v2.VulnerabilityReportFilters_Fixability" json:"fixability,omitempty"` @@ -898,6 +966,80 @@ func (*VulnerabilityReportFilters_SinceLastSentScheduledReport) isVulnerabilityR func (*VulnerabilityReportFilters_SinceStartDate) isVulnerabilityReportFilters_CvesSince() {} +type NodeVulnerabilityReportFilters struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to CvesSince: + // + // *NodeVulnerabilityReportFilters_AllVuln + CvesSince isNodeVulnerabilityReportFilters_CvesSince `protobuf_oneof:"cves_since"` + Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NodeVulnerabilityReportFilters) Reset() { + *x = NodeVulnerabilityReportFilters{} + mi := &file_api_v2_report_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NodeVulnerabilityReportFilters) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeVulnerabilityReportFilters) ProtoMessage() {} + +func (x *NodeVulnerabilityReportFilters) ProtoReflect() protoreflect.Message { + mi := &file_api_v2_report_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeVulnerabilityReportFilters.ProtoReflect.Descriptor instead. +func (*NodeVulnerabilityReportFilters) Descriptor() ([]byte, []int) { + return file_api_v2_report_service_proto_rawDescGZIP(), []int{2} +} + +func (x *NodeVulnerabilityReportFilters) GetCvesSince() isNodeVulnerabilityReportFilters_CvesSince { + if x != nil { + return x.CvesSince + } + return nil +} + +func (x *NodeVulnerabilityReportFilters) GetAllVuln() bool { + if x != nil { + if x, ok := x.CvesSince.(*NodeVulnerabilityReportFilters_AllVuln); ok { + return x.AllVuln + } + } + return false +} + +func (x *NodeVulnerabilityReportFilters) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type isNodeVulnerabilityReportFilters_CvesSince interface { + isNodeVulnerabilityReportFilters_CvesSince() +} + +type NodeVulnerabilityReportFilters_AllVuln struct { + AllVuln bool `protobuf:"varint,1,opt,name=all_vuln,json=allVuln,proto3,oneof"` +} + +func (*NodeVulnerabilityReportFilters_AllVuln) isNodeVulnerabilityReportFilters_CvesSince() {} + // filter for ondemand view based reports type ViewBasedVulnerabilityReportFilters struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -908,7 +1050,7 @@ type ViewBasedVulnerabilityReportFilters struct { func (x *ViewBasedVulnerabilityReportFilters) Reset() { *x = ViewBasedVulnerabilityReportFilters{} - mi := &file_api_v2_report_service_proto_msgTypes[2] + mi := &file_api_v2_report_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -920,7 +1062,7 @@ func (x *ViewBasedVulnerabilityReportFilters) String() string { func (*ViewBasedVulnerabilityReportFilters) ProtoMessage() {} func (x *ViewBasedVulnerabilityReportFilters) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[2] + mi := &file_api_v2_report_service_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -933,7 +1075,7 @@ func (x *ViewBasedVulnerabilityReportFilters) ProtoReflect() protoreflect.Messag // Deprecated: Use ViewBasedVulnerabilityReportFilters.ProtoReflect.Descriptor instead. func (*ViewBasedVulnerabilityReportFilters) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{2} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{3} } func (x *ViewBasedVulnerabilityReportFilters) GetQuery() string { @@ -959,7 +1101,7 @@ type ReportSchedule struct { func (x *ReportSchedule) Reset() { *x = ReportSchedule{} - mi := &file_api_v2_report_service_proto_msgTypes[3] + mi := &file_api_v2_report_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -971,7 +1113,7 @@ func (x *ReportSchedule) String() string { func (*ReportSchedule) ProtoMessage() {} func (x *ReportSchedule) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[3] + mi := &file_api_v2_report_service_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -984,7 +1126,7 @@ func (x *ReportSchedule) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportSchedule.ProtoReflect.Descriptor instead. func (*ReportSchedule) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{3} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{4} } func (x *ReportSchedule) GetIntervalType() ReportSchedule_IntervalType { @@ -1065,7 +1207,7 @@ type ResourceScope struct { func (x *ResourceScope) Reset() { *x = ResourceScope{} - mi := &file_api_v2_report_service_proto_msgTypes[4] + mi := &file_api_v2_report_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1077,7 +1219,7 @@ func (x *ResourceScope) String() string { func (*ResourceScope) ProtoMessage() {} func (x *ResourceScope) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[4] + mi := &file_api_v2_report_service_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1090,7 +1232,7 @@ func (x *ResourceScope) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceScope.ProtoReflect.Descriptor instead. func (*ResourceScope) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{4} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{5} } func (x *ResourceScope) GetScopeReference() isResourceScope_ScopeReference { @@ -1144,7 +1286,7 @@ type CollectionReference struct { func (x *CollectionReference) Reset() { *x = CollectionReference{} - mi := &file_api_v2_report_service_proto_msgTypes[5] + mi := &file_api_v2_report_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1156,7 +1298,7 @@ func (x *CollectionReference) String() string { func (*CollectionReference) ProtoMessage() {} func (x *CollectionReference) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[5] + mi := &file_api_v2_report_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1169,7 +1311,7 @@ func (x *CollectionReference) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectionReference.ProtoReflect.Descriptor instead. func (*CollectionReference) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{5} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{6} } func (x *CollectionReference) GetCollectionId() string { @@ -1195,7 +1337,7 @@ type EntityScope struct { func (x *EntityScope) Reset() { *x = EntityScope{} - mi := &file_api_v2_report_service_proto_msgTypes[6] + mi := &file_api_v2_report_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1207,7 +1349,7 @@ func (x *EntityScope) String() string { func (*EntityScope) ProtoMessage() {} func (x *EntityScope) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[6] + mi := &file_api_v2_report_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1220,7 +1362,7 @@ func (x *EntityScope) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityScope.ProtoReflect.Descriptor instead. func (*EntityScope) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{6} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{7} } func (x *EntityScope) GetRules() []*EntityScopeRule { @@ -1240,7 +1382,7 @@ type RuleValue struct { func (x *RuleValue) Reset() { *x = RuleValue{} - mi := &file_api_v2_report_service_proto_msgTypes[7] + mi := &file_api_v2_report_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1252,7 +1394,7 @@ func (x *RuleValue) String() string { func (*RuleValue) ProtoMessage() {} func (x *RuleValue) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[7] + mi := &file_api_v2_report_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1265,7 +1407,7 @@ func (x *RuleValue) ProtoReflect() protoreflect.Message { // Deprecated: Use RuleValue.ProtoReflect.Descriptor instead. func (*RuleValue) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{7} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{8} } func (x *RuleValue) GetValue() string { @@ -1293,7 +1435,7 @@ type EntityScopeRule struct { func (x *EntityScopeRule) Reset() { *x = EntityScopeRule{} - mi := &file_api_v2_report_service_proto_msgTypes[8] + mi := &file_api_v2_report_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1305,7 +1447,7 @@ func (x *EntityScopeRule) String() string { func (*EntityScopeRule) ProtoMessage() {} func (x *EntityScopeRule) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[8] + mi := &file_api_v2_report_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1318,7 +1460,7 @@ func (x *EntityScopeRule) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityScopeRule.ProtoReflect.Descriptor instead. func (*EntityScopeRule) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{8} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{9} } func (x *EntityScopeRule) GetEntity() ScopeEntity { @@ -1355,7 +1497,7 @@ type NotifierConfiguration struct { func (x *NotifierConfiguration) Reset() { *x = NotifierConfiguration{} - mi := &file_api_v2_report_service_proto_msgTypes[9] + mi := &file_api_v2_report_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1367,7 +1509,7 @@ func (x *NotifierConfiguration) String() string { func (*NotifierConfiguration) ProtoMessage() {} func (x *NotifierConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[9] + mi := &file_api_v2_report_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1380,7 +1522,7 @@ func (x *NotifierConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use NotifierConfiguration.ProtoReflect.Descriptor instead. func (*NotifierConfiguration) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{9} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{10} } func (x *NotifierConfiguration) GetNotifierConfig() isNotifierConfiguration_NotifierConfig { @@ -1428,7 +1570,7 @@ type EmailNotifierConfiguration struct { func (x *EmailNotifierConfiguration) Reset() { *x = EmailNotifierConfiguration{} - mi := &file_api_v2_report_service_proto_msgTypes[10] + mi := &file_api_v2_report_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1440,7 +1582,7 @@ func (x *EmailNotifierConfiguration) String() string { func (*EmailNotifierConfiguration) ProtoMessage() {} func (x *EmailNotifierConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[10] + mi := &file_api_v2_report_service_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1453,7 +1595,7 @@ func (x *EmailNotifierConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use EmailNotifierConfiguration.ProtoReflect.Descriptor instead. func (*EmailNotifierConfiguration) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{10} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{11} } func (x *EmailNotifierConfiguration) GetNotifierId() string { @@ -1493,7 +1635,7 @@ type ListReportConfigurationsResponse struct { func (x *ListReportConfigurationsResponse) Reset() { *x = ListReportConfigurationsResponse{} - mi := &file_api_v2_report_service_proto_msgTypes[11] + mi := &file_api_v2_report_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1505,7 +1647,7 @@ func (x *ListReportConfigurationsResponse) String() string { func (*ListReportConfigurationsResponse) ProtoMessage() {} func (x *ListReportConfigurationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[11] + mi := &file_api_v2_report_service_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1518,7 +1660,7 @@ func (x *ListReportConfigurationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListReportConfigurationsResponse.ProtoReflect.Descriptor instead. func (*ListReportConfigurationsResponse) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{11} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{12} } func (x *ListReportConfigurationsResponse) GetReportConfigs() []*ReportConfiguration { @@ -1537,7 +1679,7 @@ type CountReportConfigurationsResponse struct { func (x *CountReportConfigurationsResponse) Reset() { *x = CountReportConfigurationsResponse{} - mi := &file_api_v2_report_service_proto_msgTypes[12] + mi := &file_api_v2_report_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1549,7 +1691,7 @@ func (x *CountReportConfigurationsResponse) String() string { func (*CountReportConfigurationsResponse) ProtoMessage() {} func (x *CountReportConfigurationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[12] + mi := &file_api_v2_report_service_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1562,7 +1704,7 @@ func (x *CountReportConfigurationsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use CountReportConfigurationsResponse.ProtoReflect.Descriptor instead. func (*CountReportConfigurationsResponse) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{12} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{13} } func (x *CountReportConfigurationsResponse) GetCount() int32 { @@ -1582,7 +1724,7 @@ type GetReportHistoryRequest struct { func (x *GetReportHistoryRequest) Reset() { *x = GetReportHistoryRequest{} - mi := &file_api_v2_report_service_proto_msgTypes[13] + mi := &file_api_v2_report_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1594,7 +1736,7 @@ func (x *GetReportHistoryRequest) String() string { func (*GetReportHistoryRequest) ProtoMessage() {} func (x *GetReportHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[13] + mi := &file_api_v2_report_service_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1607,7 +1749,7 @@ func (x *GetReportHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetReportHistoryRequest.ProtoReflect.Descriptor instead. func (*GetReportHistoryRequest) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{13} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{14} } func (x *GetReportHistoryRequest) GetId() string { @@ -1633,7 +1775,7 @@ type GetViewBasedReportHistoryRequest struct { func (x *GetViewBasedReportHistoryRequest) Reset() { *x = GetViewBasedReportHistoryRequest{} - mi := &file_api_v2_report_service_proto_msgTypes[14] + mi := &file_api_v2_report_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1645,7 +1787,7 @@ func (x *GetViewBasedReportHistoryRequest) String() string { func (*GetViewBasedReportHistoryRequest) ProtoMessage() {} func (x *GetViewBasedReportHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[14] + mi := &file_api_v2_report_service_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1658,7 +1800,7 @@ func (x *GetViewBasedReportHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetViewBasedReportHistoryRequest.ProtoReflect.Descriptor instead. func (*GetViewBasedReportHistoryRequest) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{14} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{15} } func (x *GetViewBasedReportHistoryRequest) GetReportParamQuery() *RawQuery { @@ -1677,7 +1819,7 @@ type ReportHistoryResponse struct { func (x *ReportHistoryResponse) Reset() { *x = ReportHistoryResponse{} - mi := &file_api_v2_report_service_proto_msgTypes[15] + mi := &file_api_v2_report_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1689,7 +1831,7 @@ func (x *ReportHistoryResponse) String() string { func (*ReportHistoryResponse) ProtoMessage() {} func (x *ReportHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[15] + mi := &file_api_v2_report_service_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1702,7 +1844,7 @@ func (x *ReportHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportHistoryResponse.ProtoReflect.Descriptor instead. func (*ReportHistoryResponse) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{15} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{16} } func (x *ReportHistoryResponse) GetReportSnapshots() []*ReportSnapshot { @@ -1721,7 +1863,7 @@ type ReportStatusResponse struct { func (x *ReportStatusResponse) Reset() { *x = ReportStatusResponse{} - mi := &file_api_v2_report_service_proto_msgTypes[16] + mi := &file_api_v2_report_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1733,7 +1875,7 @@ func (x *ReportStatusResponse) String() string { func (*ReportStatusResponse) ProtoMessage() {} func (x *ReportStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[16] + mi := &file_api_v2_report_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1746,7 +1888,7 @@ func (x *ReportStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportStatusResponse.ProtoReflect.Descriptor instead. func (*ReportStatusResponse) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{16} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{17} } func (x *ReportStatusResponse) GetStatus() *ReportStatus { @@ -1766,7 +1908,7 @@ type CollectionSnapshot struct { func (x *CollectionSnapshot) Reset() { *x = CollectionSnapshot{} - mi := &file_api_v2_report_service_proto_msgTypes[17] + mi := &file_api_v2_report_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1778,7 +1920,7 @@ func (x *CollectionSnapshot) String() string { func (*CollectionSnapshot) ProtoMessage() {} func (x *CollectionSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[17] + mi := &file_api_v2_report_service_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1791,7 +1933,7 @@ func (x *CollectionSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectionSnapshot.ProtoReflect.Descriptor instead. func (*CollectionSnapshot) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{17} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{18} } func (x *CollectionSnapshot) GetId() string { @@ -1809,15 +1951,17 @@ func (x *CollectionSnapshot) GetName() string { } type ReportSnapshot struct { - state protoimpl.MessageState `protogen:"open.v1"` - ReportConfigId string `protobuf:"bytes,1,opt,name=report_config_id,json=reportConfigId,proto3" json:"report_config_id,omitempty"` - ReportJobId string `protobuf:"bytes,2,opt,name=report_job_id,json=reportJobId,proto3" json:"report_job_id,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type ReportSnapshot_ReportType `protobuf:"varint,16,opt,name=type,proto3,enum=v2.ReportSnapshot_ReportType" json:"type,omitempty"` + ReportConfigId string `protobuf:"bytes,1,opt,name=report_config_id,json=reportConfigId,proto3" json:"report_config_id,omitempty"` + ReportJobId string `protobuf:"bytes,2,opt,name=report_job_id,json=reportJobId,proto3" json:"report_job_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` // Types that are valid to be assigned to Filter: // // *ReportSnapshot_VulnReportFilters // *ReportSnapshot_ViewBasedVulnReportFilters + // *ReportSnapshot_NodeVulnReportFilters Filter isReportSnapshot_Filter `protobuf_oneof:"filter"` CollectionSnapshot *CollectionSnapshot `protobuf:"bytes,6,opt,name=collection_snapshot,json=collectionSnapshot,proto3" json:"collection_snapshot,omitempty"` Schedule *ReportSchedule `protobuf:"bytes,7,opt,name=schedule,proto3" json:"schedule,omitempty"` @@ -1833,7 +1977,7 @@ type ReportSnapshot struct { func (x *ReportSnapshot) Reset() { *x = ReportSnapshot{} - mi := &file_api_v2_report_service_proto_msgTypes[18] + mi := &file_api_v2_report_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1845,7 +1989,7 @@ func (x *ReportSnapshot) String() string { func (*ReportSnapshot) ProtoMessage() {} func (x *ReportSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[18] + mi := &file_api_v2_report_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1858,7 +2002,14 @@ func (x *ReportSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportSnapshot.ProtoReflect.Descriptor instead. func (*ReportSnapshot) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{18} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{19} +} + +func (x *ReportSnapshot) GetType() ReportSnapshot_ReportType { + if x != nil { + return x.Type + } + return ReportSnapshot_VULNERABILITY } func (x *ReportSnapshot) GetReportConfigId() string { @@ -1914,6 +2065,15 @@ func (x *ReportSnapshot) GetViewBasedVulnReportFilters() *ViewBasedVulnerability return nil } +func (x *ReportSnapshot) GetNodeVulnReportFilters() *NodeVulnerabilityReportFilters { + if x != nil { + if x, ok := x.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + return x.NodeVulnReportFilters + } + } + return nil +} + func (x *ReportSnapshot) GetCollectionSnapshot() *CollectionSnapshot { if x != nil { return x.CollectionSnapshot @@ -1982,10 +2142,16 @@ type ReportSnapshot_ViewBasedVulnReportFilters struct { ViewBasedVulnReportFilters *ViewBasedVulnerabilityReportFilters `protobuf:"bytes,12,opt,name=view_based_vuln_report_filters,json=viewBasedVulnReportFilters,proto3,oneof"` } +type ReportSnapshot_NodeVulnReportFilters struct { + NodeVulnReportFilters *NodeVulnerabilityReportFilters `protobuf:"bytes,15,opt,name=node_vuln_report_filters,json=nodeVulnReportFilters,proto3,oneof"` +} + func (*ReportSnapshot_VulnReportFilters) isReportSnapshot_Filter() {} func (*ReportSnapshot_ViewBasedVulnReportFilters) isReportSnapshot_Filter() {} +func (*ReportSnapshot_NodeVulnReportFilters) isReportSnapshot_Filter() {} + type ReportStatus struct { state protoimpl.MessageState `protogen:"open.v1"` RunState ReportStatus_RunState `protobuf:"varint,1,opt,name=run_state,json=runState,proto3,enum=v2.ReportStatus_RunState" json:"run_state,omitempty"` @@ -1999,7 +2165,7 @@ type ReportStatus struct { func (x *ReportStatus) Reset() { *x = ReportStatus{} - mi := &file_api_v2_report_service_proto_msgTypes[19] + mi := &file_api_v2_report_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2011,7 +2177,7 @@ func (x *ReportStatus) String() string { func (*ReportStatus) ProtoMessage() {} func (x *ReportStatus) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[19] + mi := &file_api_v2_report_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2024,7 +2190,7 @@ func (x *ReportStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportStatus.ProtoReflect.Descriptor instead. func (*ReportStatus) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{19} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{20} } func (x *ReportStatus) GetRunState() ReportStatus_RunState { @@ -2072,7 +2238,7 @@ type RunReportRequest struct { func (x *RunReportRequest) Reset() { *x = RunReportRequest{} - mi := &file_api_v2_report_service_proto_msgTypes[20] + mi := &file_api_v2_report_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2084,7 +2250,7 @@ func (x *RunReportRequest) String() string { func (*RunReportRequest) ProtoMessage() {} func (x *RunReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[20] + mi := &file_api_v2_report_service_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2097,7 +2263,7 @@ func (x *RunReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RunReportRequest.ProtoReflect.Descriptor instead. func (*RunReportRequest) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{20} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{21} } func (x *RunReportRequest) GetReportConfigId() string { @@ -2124,7 +2290,7 @@ type RunReportResponse struct { func (x *RunReportResponse) Reset() { *x = RunReportResponse{} - mi := &file_api_v2_report_service_proto_msgTypes[21] + mi := &file_api_v2_report_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2136,7 +2302,7 @@ func (x *RunReportResponse) String() string { func (*RunReportResponse) ProtoMessage() {} func (x *RunReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[21] + mi := &file_api_v2_report_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2149,7 +2315,7 @@ func (x *RunReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RunReportResponse.ProtoReflect.Descriptor instead. func (*RunReportResponse) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{21} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{22} } func (x *RunReportResponse) GetReportConfigId() string { @@ -2175,7 +2341,7 @@ type DeleteReportRequest struct { func (x *DeleteReportRequest) Reset() { *x = DeleteReportRequest{} - mi := &file_api_v2_report_service_proto_msgTypes[22] + mi := &file_api_v2_report_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2187,7 +2353,7 @@ func (x *DeleteReportRequest) String() string { func (*DeleteReportRequest) ProtoMessage() {} func (x *DeleteReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[22] + mi := &file_api_v2_report_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2200,7 +2366,7 @@ func (x *DeleteReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteReportRequest.ProtoReflect.Descriptor instead. func (*DeleteReportRequest) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{22} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{23} } func (x *DeleteReportRequest) GetId() string { @@ -2216,6 +2382,7 @@ type ReportRequestViewBased struct { // Types that are valid to be assigned to Filter: // // *ReportRequestViewBased_ViewBasedVulnReportFilters + // *ReportRequestViewBased_NodeVulnReportFilters Filter isReportRequestViewBased_Filter `protobuf_oneof:"filter"` AreaOfConcern string `protobuf:"bytes,3,opt,name=area_of_concern,json=areaOfConcern,proto3" json:"area_of_concern,omitempty"` unknownFields protoimpl.UnknownFields @@ -2224,7 +2391,7 @@ type ReportRequestViewBased struct { func (x *ReportRequestViewBased) Reset() { *x = ReportRequestViewBased{} - mi := &file_api_v2_report_service_proto_msgTypes[23] + mi := &file_api_v2_report_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2236,7 +2403,7 @@ func (x *ReportRequestViewBased) String() string { func (*ReportRequestViewBased) ProtoMessage() {} func (x *ReportRequestViewBased) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[23] + mi := &file_api_v2_report_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2249,7 +2416,7 @@ func (x *ReportRequestViewBased) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportRequestViewBased.ProtoReflect.Descriptor instead. func (*ReportRequestViewBased) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{23} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{24} } func (x *ReportRequestViewBased) GetType() ReportRequestViewBased_ReportType { @@ -2275,6 +2442,15 @@ func (x *ReportRequestViewBased) GetViewBasedVulnReportFilters() *ViewBasedVulne return nil } +func (x *ReportRequestViewBased) GetNodeVulnReportFilters() *NodeVulnerabilityReportFilters { + if x != nil { + if x, ok := x.Filter.(*ReportRequestViewBased_NodeVulnReportFilters); ok { + return x.NodeVulnReportFilters + } + } + return nil +} + func (x *ReportRequestViewBased) GetAreaOfConcern() string { if x != nil { return x.AreaOfConcern @@ -2290,8 +2466,14 @@ type ReportRequestViewBased_ViewBasedVulnReportFilters struct { ViewBasedVulnReportFilters *ViewBasedVulnerabilityReportFilters `protobuf:"bytes,2,opt,name=view_based_vuln_report_filters,json=viewBasedVulnReportFilters,proto3,oneof"` } +type ReportRequestViewBased_NodeVulnReportFilters struct { + NodeVulnReportFilters *NodeVulnerabilityReportFilters `protobuf:"bytes,4,opt,name=node_vuln_report_filters,json=nodeVulnReportFilters,proto3,oneof"` +} + func (*ReportRequestViewBased_ViewBasedVulnReportFilters) isReportRequestViewBased_Filter() {} +func (*ReportRequestViewBased_NodeVulnReportFilters) isReportRequestViewBased_Filter() {} + type RunReportResponseViewBased struct { state protoimpl.MessageState `protogen:"open.v1"` ReportID string `protobuf:"bytes,1,opt,name=reportID,proto3" json:"reportID,omitempty"` @@ -2302,7 +2484,7 @@ type RunReportResponseViewBased struct { func (x *RunReportResponseViewBased) Reset() { *x = RunReportResponseViewBased{} - mi := &file_api_v2_report_service_proto_msgTypes[24] + mi := &file_api_v2_report_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2314,7 +2496,7 @@ func (x *RunReportResponseViewBased) String() string { func (*RunReportResponseViewBased) ProtoMessage() {} func (x *RunReportResponseViewBased) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[24] + mi := &file_api_v2_report_service_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2327,7 +2509,7 @@ func (x *RunReportResponseViewBased) ProtoReflect() protoreflect.Message { // Deprecated: Use RunReportResponseViewBased.ProtoReflect.Descriptor instead. func (*RunReportResponseViewBased) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{24} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{25} } func (x *RunReportResponseViewBased) GetReportID() string { @@ -2354,7 +2536,7 @@ type ReportSchedule_DaysOfWeek struct { func (x *ReportSchedule_DaysOfWeek) Reset() { *x = ReportSchedule_DaysOfWeek{} - mi := &file_api_v2_report_service_proto_msgTypes[25] + mi := &file_api_v2_report_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2366,7 +2548,7 @@ func (x *ReportSchedule_DaysOfWeek) String() string { func (*ReportSchedule_DaysOfWeek) ProtoMessage() {} func (x *ReportSchedule_DaysOfWeek) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[25] + mi := &file_api_v2_report_service_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2379,7 +2561,7 @@ func (x *ReportSchedule_DaysOfWeek) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportSchedule_DaysOfWeek.ProtoReflect.Descriptor instead. func (*ReportSchedule_DaysOfWeek) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{3, 0} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{4, 0} } func (x *ReportSchedule_DaysOfWeek) GetDays() []int32 { @@ -2399,7 +2581,7 @@ type ReportSchedule_DaysOfMonth struct { func (x *ReportSchedule_DaysOfMonth) Reset() { *x = ReportSchedule_DaysOfMonth{} - mi := &file_api_v2_report_service_proto_msgTypes[26] + mi := &file_api_v2_report_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2411,7 +2593,7 @@ func (x *ReportSchedule_DaysOfMonth) String() string { func (*ReportSchedule_DaysOfMonth) ProtoMessage() {} func (x *ReportSchedule_DaysOfMonth) ProtoReflect() protoreflect.Message { - mi := &file_api_v2_report_service_proto_msgTypes[26] + mi := &file_api_v2_report_service_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2424,7 +2606,7 @@ func (x *ReportSchedule_DaysOfMonth) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportSchedule_DaysOfMonth.ProtoReflect.Descriptor instead. func (*ReportSchedule_DaysOfMonth) Descriptor() ([]byte, []int) { - return file_api_v2_report_service_proto_rawDescGZIP(), []int{3, 1} + return file_api_v2_report_service_proto_rawDescGZIP(), []int{4, 1} } func (x *ReportSchedule_DaysOfMonth) GetDays() []int32 { @@ -2438,19 +2620,21 @@ var File_api_v2_report_service_proto protoreflect.FileDescriptor const file_api_v2_report_service_proto_rawDesc = "" + "\n" + - "\x1bapi/v2/report_service.proto\x12\x02v2\x1a\x13api/v2/common.proto\x1a\x19api/v2/search_query.proto\x1a\x11api/v2/user.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb3\x03\n" + + "\x1bapi/v2/report_service.proto\x12\x02v2\x1a\x13api/v2/common.proto\x1a\x19api/v2/search_query.proto\x1a\x11api/v2/user.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xaa\x04\n" + "\x13ReportConfiguration\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x126\n" + "\x04type\x18\x04 \x01(\x0e2\".v2.ReportConfiguration.ReportTypeR\x04type\x12P\n" + - "\x13vuln_report_filters\x18\x05 \x01(\v2\x1e.v2.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12.\n" + + "\x13vuln_report_filters\x18\x05 \x01(\v2\x1e.v2.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12]\n" + + "\x18node_vuln_report_filters\x18\t \x01(\v2\".v2.NodeVulnerabilityReportFiltersH\x00R\x15nodeVulnReportFilters\x12.\n" + "\bschedule\x18\x06 \x01(\v2\x12.v2.ReportScheduleR\bschedule\x128\n" + "\x0eresource_scope\x18\a \x01(\v2\x11.v2.ResourceScopeR\rresourceScope\x127\n" + - "\tnotifiers\x18\b \x03(\v2\x19.v2.NotifierConfigurationR\tnotifiers\"\x1f\n" + + "\tnotifiers\x18\b \x03(\v2\x19.v2.NotifierConfigurationR\tnotifiers\"7\n" + "\n" + "ReportType\x12\x11\n" + - "\rVULNERABILITY\x10\x00B\b\n" + + "\rVULNERABILITY\x10\x00\x12\x16\n" + + "\x12NODE_VULNERABILITY\x10\x01B\b\n" + "\x06filter\"\xa2\a\n" + "\x1aVulnerabilityReportFilters\x12I\n" + "\n" + @@ -2483,6 +2667,11 @@ const file_api_v2_report_service_proto_rawDesc = "" + "\bDEPLOYED\x10\x00\x12\v\n" + "\aWATCHED\x10\x01B\f\n" + "\n" + + "cves_since\"a\n" + + "\x1eNodeVulnerabilityReportFilters\x12\x1b\n" + + "\ball_vuln\x18\x01 \x01(\bH\x00R\aallVuln\x12\x14\n" + + "\x05query\x18\x02 \x01(\tR\x05queryB\f\n" + + "\n" + "cves_since\";\n" + "#ViewBasedVulnerabilityReportFilters\x12\x14\n" + "\x05query\x18\x05 \x01(\tR\x05query\"\x9b\x03\n" + @@ -2549,14 +2738,16 @@ const file_api_v2_report_service_proto_rawDesc = "" + "\x06status\x18\x01 \x01(\v2\x10.v2.ReportStatusR\x06status\"8\n" + "\x12CollectionSnapshot\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\"\x80\x06\n" + - "\x0eReportSnapshot\x12(\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\xcb\a\n" + + "\x0eReportSnapshot\x121\n" + + "\x04type\x18\x10 \x01(\x0e2\x1d.v2.ReportSnapshot.ReportTypeR\x04type\x12(\n" + "\x10report_config_id\x18\x01 \x01(\tR\x0ereportConfigId\x12\"\n" + "\rreport_job_id\x18\x02 \x01(\tR\vreportJobId\x12\x12\n" + "\x04name\x18\x03 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x04 \x01(\tR\vdescription\x12P\n" + "\x13vuln_report_filters\x18\x05 \x01(\v2\x1e.v2.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12m\n" + - "\x1eview_based_vuln_report_filters\x18\f \x01(\v2'.v2.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12G\n" + + "\x1eview_based_vuln_report_filters\x18\f \x01(\v2'.v2.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12]\n" + + "\x18node_vuln_report_filters\x18\x0f \x01(\v2\".v2.NodeVulnerabilityReportFiltersH\x00R\x15nodeVulnReportFilters\x12G\n" + "\x13collection_snapshot\x18\x06 \x01(\v2\x16.v2.CollectionSnapshotR\x12collectionSnapshot\x12.\n" + "\bschedule\x18\a \x01(\v2\x12.v2.ReportScheduleR\bschedule\x125\n" + "\rreport_status\x18\b \x01(\v2\x10.v2.ReportStatusR\freportStatus\x127\n" + @@ -2565,7 +2756,11 @@ const file_api_v2_report_service_proto_rawDesc = "" + " \x01(\v2\f.v2.SlimUserR\x04user\x122\n" + "\x15is_download_available\x18\v \x01(\bR\x13isDownloadAvailable\x12&\n" + "\x0farea_of_concern\x18\r \x01(\tR\rareaOfConcern\x128\n" + - "\x0eresource_scope\x18\x0e \x01(\v2\x11.v2.ResourceScopeR\rresourceScopeB\b\n" + + "\x0eresource_scope\x18\x0e \x01(\v2\x11.v2.ResourceScopeR\rresourceScope\"7\n" + + "\n" + + "ReportType\x12\x11\n" + + "\rVULNERABILITY\x10\x00\x12\x16\n" + + "\x12NODE_VULNERABILITY\x10\x01B\b\n" + "\x06filter\"\xc8\x03\n" + "\fReportStatus\x126\n" + "\trun_state\x18\x01 \x01(\x0e2\x19.v2.ReportStatus.RunStateR\brunState\x12=\n" + @@ -2589,14 +2784,16 @@ const file_api_v2_report_service_proto_rawDesc = "" + "\x10report_config_id\x18\x01 \x01(\tR\x0ereportConfigId\x12\x1b\n" + "\treport_id\x18\x02 \x01(\tR\breportId\"%\n" + "\x13DeleteReportRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\"\x95\x02\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x8c\x03\n" + "\x16ReportRequestViewBased\x129\n" + "\x04type\x18\x01 \x01(\x0e2%.v2.ReportRequestViewBased.ReportTypeR\x04type\x12m\n" + - "\x1eview_based_vuln_report_filters\x18\x02 \x01(\v2'.v2.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12&\n" + - "\x0farea_of_concern\x18\x03 \x01(\tR\rareaOfConcern\"\x1f\n" + + "\x1eview_based_vuln_report_filters\x18\x02 \x01(\v2'.v2.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12]\n" + + "\x18node_vuln_report_filters\x18\x04 \x01(\v2\".v2.NodeVulnerabilityReportFiltersH\x00R\x15nodeVulnReportFilters\x12&\n" + + "\x0farea_of_concern\x18\x03 \x01(\tR\rareaOfConcern\"7\n" + "\n" + "ReportType\x12\x11\n" + - "\rVULNERABILITY\x10\x00B\b\n" + + "\rVULNERABILITY\x10\x00\x12\x16\n" + + "\x12NODE_VULNERABILITY\x10\x01B\b\n" + "\x06filter\"Z\n" + "\x1aRunReportResponseViewBased\x12\x1a\n" + "\breportID\x18\x01 \x01(\tR\breportID\x12 \n" + @@ -2650,8 +2847,8 @@ func file_api_v2_report_service_proto_rawDescGZIP() []byte { return file_api_v2_report_service_proto_rawDescData } -var file_api_v2_report_service_proto_enumTypes = make([]protoimpl.EnumInfo, 12) -var file_api_v2_report_service_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_api_v2_report_service_proto_enumTypes = make([]protoimpl.EnumInfo, 13) +var file_api_v2_report_service_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_api_v2_report_service_proto_goTypes = []any{ (MatchType)(0), // 0: v2.MatchType (ScopeEntity)(0), // 1: v2.ScopeEntity @@ -2662,118 +2859,124 @@ var file_api_v2_report_service_proto_goTypes = []any{ (VulnerabilityReportFilters_VulnerabilitySeverity)(0), // 6: v2.VulnerabilityReportFilters.VulnerabilitySeverity (VulnerabilityReportFilters_ImageType)(0), // 7: v2.VulnerabilityReportFilters.ImageType (ReportSchedule_IntervalType)(0), // 8: v2.ReportSchedule.IntervalType - (ReportStatus_RunState)(0), // 9: v2.ReportStatus.RunState - (ReportStatus_ReportMethod)(0), // 10: v2.ReportStatus.ReportMethod - (ReportRequestViewBased_ReportType)(0), // 11: v2.ReportRequestViewBased.ReportType - (*ReportConfiguration)(nil), // 12: v2.ReportConfiguration - (*VulnerabilityReportFilters)(nil), // 13: v2.VulnerabilityReportFilters - (*ViewBasedVulnerabilityReportFilters)(nil), // 14: v2.ViewBasedVulnerabilityReportFilters - (*ReportSchedule)(nil), // 15: v2.ReportSchedule - (*ResourceScope)(nil), // 16: v2.ResourceScope - (*CollectionReference)(nil), // 17: v2.CollectionReference - (*EntityScope)(nil), // 18: v2.EntityScope - (*RuleValue)(nil), // 19: v2.RuleValue - (*EntityScopeRule)(nil), // 20: v2.EntityScopeRule - (*NotifierConfiguration)(nil), // 21: v2.NotifierConfiguration - (*EmailNotifierConfiguration)(nil), // 22: v2.EmailNotifierConfiguration - (*ListReportConfigurationsResponse)(nil), // 23: v2.ListReportConfigurationsResponse - (*CountReportConfigurationsResponse)(nil), // 24: v2.CountReportConfigurationsResponse - (*GetReportHistoryRequest)(nil), // 25: v2.GetReportHistoryRequest - (*GetViewBasedReportHistoryRequest)(nil), // 26: v2.GetViewBasedReportHistoryRequest - (*ReportHistoryResponse)(nil), // 27: v2.ReportHistoryResponse - (*ReportStatusResponse)(nil), // 28: v2.ReportStatusResponse - (*CollectionSnapshot)(nil), // 29: v2.CollectionSnapshot - (*ReportSnapshot)(nil), // 30: v2.ReportSnapshot - (*ReportStatus)(nil), // 31: v2.ReportStatus - (*RunReportRequest)(nil), // 32: v2.RunReportRequest - (*RunReportResponse)(nil), // 33: v2.RunReportResponse - (*DeleteReportRequest)(nil), // 34: v2.DeleteReportRequest - (*ReportRequestViewBased)(nil), // 35: v2.ReportRequestViewBased - (*RunReportResponseViewBased)(nil), // 36: v2.RunReportResponseViewBased - (*ReportSchedule_DaysOfWeek)(nil), // 37: v2.ReportSchedule.DaysOfWeek - (*ReportSchedule_DaysOfMonth)(nil), // 38: v2.ReportSchedule.DaysOfMonth - (*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp - (*RawQuery)(nil), // 40: v2.RawQuery - (*SlimUser)(nil), // 41: v2.SlimUser - (*ResourceByID)(nil), // 42: v2.ResourceByID - (*Empty)(nil), // 43: v2.Empty + (ReportSnapshot_ReportType)(0), // 9: v2.ReportSnapshot.ReportType + (ReportStatus_RunState)(0), // 10: v2.ReportStatus.RunState + (ReportStatus_ReportMethod)(0), // 11: v2.ReportStatus.ReportMethod + (ReportRequestViewBased_ReportType)(0), // 12: v2.ReportRequestViewBased.ReportType + (*ReportConfiguration)(nil), // 13: v2.ReportConfiguration + (*VulnerabilityReportFilters)(nil), // 14: v2.VulnerabilityReportFilters + (*NodeVulnerabilityReportFilters)(nil), // 15: v2.NodeVulnerabilityReportFilters + (*ViewBasedVulnerabilityReportFilters)(nil), // 16: v2.ViewBasedVulnerabilityReportFilters + (*ReportSchedule)(nil), // 17: v2.ReportSchedule + (*ResourceScope)(nil), // 18: v2.ResourceScope + (*CollectionReference)(nil), // 19: v2.CollectionReference + (*EntityScope)(nil), // 20: v2.EntityScope + (*RuleValue)(nil), // 21: v2.RuleValue + (*EntityScopeRule)(nil), // 22: v2.EntityScopeRule + (*NotifierConfiguration)(nil), // 23: v2.NotifierConfiguration + (*EmailNotifierConfiguration)(nil), // 24: v2.EmailNotifierConfiguration + (*ListReportConfigurationsResponse)(nil), // 25: v2.ListReportConfigurationsResponse + (*CountReportConfigurationsResponse)(nil), // 26: v2.CountReportConfigurationsResponse + (*GetReportHistoryRequest)(nil), // 27: v2.GetReportHistoryRequest + (*GetViewBasedReportHistoryRequest)(nil), // 28: v2.GetViewBasedReportHistoryRequest + (*ReportHistoryResponse)(nil), // 29: v2.ReportHistoryResponse + (*ReportStatusResponse)(nil), // 30: v2.ReportStatusResponse + (*CollectionSnapshot)(nil), // 31: v2.CollectionSnapshot + (*ReportSnapshot)(nil), // 32: v2.ReportSnapshot + (*ReportStatus)(nil), // 33: v2.ReportStatus + (*RunReportRequest)(nil), // 34: v2.RunReportRequest + (*RunReportResponse)(nil), // 35: v2.RunReportResponse + (*DeleteReportRequest)(nil), // 36: v2.DeleteReportRequest + (*ReportRequestViewBased)(nil), // 37: v2.ReportRequestViewBased + (*RunReportResponseViewBased)(nil), // 38: v2.RunReportResponseViewBased + (*ReportSchedule_DaysOfWeek)(nil), // 39: v2.ReportSchedule.DaysOfWeek + (*ReportSchedule_DaysOfMonth)(nil), // 40: v2.ReportSchedule.DaysOfMonth + (*timestamppb.Timestamp)(nil), // 41: google.protobuf.Timestamp + (*RawQuery)(nil), // 42: v2.RawQuery + (*SlimUser)(nil), // 43: v2.SlimUser + (*ResourceByID)(nil), // 44: v2.ResourceByID + (*Empty)(nil), // 45: v2.Empty } var file_api_v2_report_service_proto_depIdxs = []int32{ 4, // 0: v2.ReportConfiguration.type:type_name -> v2.ReportConfiguration.ReportType - 13, // 1: v2.ReportConfiguration.vuln_report_filters:type_name -> v2.VulnerabilityReportFilters - 15, // 2: v2.ReportConfiguration.schedule:type_name -> v2.ReportSchedule - 16, // 3: v2.ReportConfiguration.resource_scope:type_name -> v2.ResourceScope - 21, // 4: v2.ReportConfiguration.notifiers:type_name -> v2.NotifierConfiguration - 5, // 5: v2.VulnerabilityReportFilters.fixability:type_name -> v2.VulnerabilityReportFilters.Fixability - 6, // 6: v2.VulnerabilityReportFilters.severities:type_name -> v2.VulnerabilityReportFilters.VulnerabilitySeverity - 7, // 7: v2.VulnerabilityReportFilters.image_types:type_name -> v2.VulnerabilityReportFilters.ImageType - 39, // 8: v2.VulnerabilityReportFilters.since_start_date:type_name -> google.protobuf.Timestamp - 8, // 9: v2.ReportSchedule.interval_type:type_name -> v2.ReportSchedule.IntervalType - 37, // 10: v2.ReportSchedule.days_of_week:type_name -> v2.ReportSchedule.DaysOfWeek - 38, // 11: v2.ReportSchedule.days_of_month:type_name -> v2.ReportSchedule.DaysOfMonth - 17, // 12: v2.ResourceScope.collection_scope:type_name -> v2.CollectionReference - 18, // 13: v2.ResourceScope.entity_scope:type_name -> v2.EntityScope - 20, // 14: v2.EntityScope.rules:type_name -> v2.EntityScopeRule - 0, // 15: v2.RuleValue.match_type:type_name -> v2.MatchType - 1, // 16: v2.EntityScopeRule.entity:type_name -> v2.ScopeEntity - 2, // 17: v2.EntityScopeRule.field:type_name -> v2.ScopeField - 19, // 18: v2.EntityScopeRule.values:type_name -> v2.RuleValue - 22, // 19: v2.NotifierConfiguration.email_config:type_name -> v2.EmailNotifierConfiguration - 12, // 20: v2.ListReportConfigurationsResponse.report_configs:type_name -> v2.ReportConfiguration - 40, // 21: v2.GetReportHistoryRequest.report_param_query:type_name -> v2.RawQuery - 40, // 22: v2.GetViewBasedReportHistoryRequest.report_param_query:type_name -> v2.RawQuery - 30, // 23: v2.ReportHistoryResponse.report_snapshots:type_name -> v2.ReportSnapshot - 31, // 24: v2.ReportStatusResponse.status:type_name -> v2.ReportStatus - 13, // 25: v2.ReportSnapshot.vuln_report_filters:type_name -> v2.VulnerabilityReportFilters - 14, // 26: v2.ReportSnapshot.view_based_vuln_report_filters:type_name -> v2.ViewBasedVulnerabilityReportFilters - 29, // 27: v2.ReportSnapshot.collection_snapshot:type_name -> v2.CollectionSnapshot - 15, // 28: v2.ReportSnapshot.schedule:type_name -> v2.ReportSchedule - 31, // 29: v2.ReportSnapshot.report_status:type_name -> v2.ReportStatus - 21, // 30: v2.ReportSnapshot.notifiers:type_name -> v2.NotifierConfiguration - 41, // 31: v2.ReportSnapshot.user:type_name -> v2.SlimUser - 16, // 32: v2.ReportSnapshot.resource_scope:type_name -> v2.ResourceScope - 9, // 33: v2.ReportStatus.run_state:type_name -> v2.ReportStatus.RunState - 39, // 34: v2.ReportStatus.completed_at:type_name -> google.protobuf.Timestamp - 10, // 35: v2.ReportStatus.report_request_type:type_name -> v2.ReportStatus.ReportMethod - 3, // 36: v2.ReportStatus.report_notification_method:type_name -> v2.NotificationMethod - 3, // 37: v2.RunReportRequest.report_notification_method:type_name -> v2.NotificationMethod - 11, // 38: v2.ReportRequestViewBased.type:type_name -> v2.ReportRequestViewBased.ReportType - 14, // 39: v2.ReportRequestViewBased.view_based_vuln_report_filters:type_name -> v2.ViewBasedVulnerabilityReportFilters - 12, // 40: v2.ReportService.PostReportConfiguration:input_type -> v2.ReportConfiguration - 12, // 41: v2.ReportService.UpdateReportConfiguration:input_type -> v2.ReportConfiguration - 40, // 42: v2.ReportService.ListReportConfigurations:input_type -> v2.RawQuery - 40, // 43: v2.ReportService.CountReportConfigurations:input_type -> v2.RawQuery - 42, // 44: v2.ReportService.GetReportConfiguration:input_type -> v2.ResourceByID - 42, // 45: v2.ReportService.DeleteReportConfiguration:input_type -> v2.ResourceByID - 42, // 46: v2.ReportService.GetReportStatus:input_type -> v2.ResourceByID - 25, // 47: v2.ReportService.GetReportHistory:input_type -> v2.GetReportHistoryRequest - 25, // 48: v2.ReportService.GetMyReportHistory:input_type -> v2.GetReportHistoryRequest - 32, // 49: v2.ReportService.RunReport:input_type -> v2.RunReportRequest - 42, // 50: v2.ReportService.CancelReport:input_type -> v2.ResourceByID - 34, // 51: v2.ReportService.DeleteReport:input_type -> v2.DeleteReportRequest - 35, // 52: v2.ReportService.PostViewBasedReport:input_type -> v2.ReportRequestViewBased - 26, // 53: v2.ReportService.GetViewBasedMyReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest - 26, // 54: v2.ReportService.GetViewBasedReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest - 12, // 55: v2.ReportService.PostReportConfiguration:output_type -> v2.ReportConfiguration - 43, // 56: v2.ReportService.UpdateReportConfiguration:output_type -> v2.Empty - 23, // 57: v2.ReportService.ListReportConfigurations:output_type -> v2.ListReportConfigurationsResponse - 24, // 58: v2.ReportService.CountReportConfigurations:output_type -> v2.CountReportConfigurationsResponse - 12, // 59: v2.ReportService.GetReportConfiguration:output_type -> v2.ReportConfiguration - 43, // 60: v2.ReportService.DeleteReportConfiguration:output_type -> v2.Empty - 28, // 61: v2.ReportService.GetReportStatus:output_type -> v2.ReportStatusResponse - 27, // 62: v2.ReportService.GetReportHistory:output_type -> v2.ReportHistoryResponse - 27, // 63: v2.ReportService.GetMyReportHistory:output_type -> v2.ReportHistoryResponse - 33, // 64: v2.ReportService.RunReport:output_type -> v2.RunReportResponse - 43, // 65: v2.ReportService.CancelReport:output_type -> v2.Empty - 43, // 66: v2.ReportService.DeleteReport:output_type -> v2.Empty - 36, // 67: v2.ReportService.PostViewBasedReport:output_type -> v2.RunReportResponseViewBased - 27, // 68: v2.ReportService.GetViewBasedMyReportHistory:output_type -> v2.ReportHistoryResponse - 27, // 69: v2.ReportService.GetViewBasedReportHistory:output_type -> v2.ReportHistoryResponse - 55, // [55:70] is the sub-list for method output_type - 40, // [40:55] is the sub-list for method input_type - 40, // [40:40] is the sub-list for extension type_name - 40, // [40:40] is the sub-list for extension extendee - 0, // [0:40] is the sub-list for field type_name + 14, // 1: v2.ReportConfiguration.vuln_report_filters:type_name -> v2.VulnerabilityReportFilters + 15, // 2: v2.ReportConfiguration.node_vuln_report_filters:type_name -> v2.NodeVulnerabilityReportFilters + 17, // 3: v2.ReportConfiguration.schedule:type_name -> v2.ReportSchedule + 18, // 4: v2.ReportConfiguration.resource_scope:type_name -> v2.ResourceScope + 23, // 5: v2.ReportConfiguration.notifiers:type_name -> v2.NotifierConfiguration + 5, // 6: v2.VulnerabilityReportFilters.fixability:type_name -> v2.VulnerabilityReportFilters.Fixability + 6, // 7: v2.VulnerabilityReportFilters.severities:type_name -> v2.VulnerabilityReportFilters.VulnerabilitySeverity + 7, // 8: v2.VulnerabilityReportFilters.image_types:type_name -> v2.VulnerabilityReportFilters.ImageType + 41, // 9: v2.VulnerabilityReportFilters.since_start_date:type_name -> google.protobuf.Timestamp + 8, // 10: v2.ReportSchedule.interval_type:type_name -> v2.ReportSchedule.IntervalType + 39, // 11: v2.ReportSchedule.days_of_week:type_name -> v2.ReportSchedule.DaysOfWeek + 40, // 12: v2.ReportSchedule.days_of_month:type_name -> v2.ReportSchedule.DaysOfMonth + 19, // 13: v2.ResourceScope.collection_scope:type_name -> v2.CollectionReference + 20, // 14: v2.ResourceScope.entity_scope:type_name -> v2.EntityScope + 22, // 15: v2.EntityScope.rules:type_name -> v2.EntityScopeRule + 0, // 16: v2.RuleValue.match_type:type_name -> v2.MatchType + 1, // 17: v2.EntityScopeRule.entity:type_name -> v2.ScopeEntity + 2, // 18: v2.EntityScopeRule.field:type_name -> v2.ScopeField + 21, // 19: v2.EntityScopeRule.values:type_name -> v2.RuleValue + 24, // 20: v2.NotifierConfiguration.email_config:type_name -> v2.EmailNotifierConfiguration + 13, // 21: v2.ListReportConfigurationsResponse.report_configs:type_name -> v2.ReportConfiguration + 42, // 22: v2.GetReportHistoryRequest.report_param_query:type_name -> v2.RawQuery + 42, // 23: v2.GetViewBasedReportHistoryRequest.report_param_query:type_name -> v2.RawQuery + 32, // 24: v2.ReportHistoryResponse.report_snapshots:type_name -> v2.ReportSnapshot + 33, // 25: v2.ReportStatusResponse.status:type_name -> v2.ReportStatus + 9, // 26: v2.ReportSnapshot.type:type_name -> v2.ReportSnapshot.ReportType + 14, // 27: v2.ReportSnapshot.vuln_report_filters:type_name -> v2.VulnerabilityReportFilters + 16, // 28: v2.ReportSnapshot.view_based_vuln_report_filters:type_name -> v2.ViewBasedVulnerabilityReportFilters + 15, // 29: v2.ReportSnapshot.node_vuln_report_filters:type_name -> v2.NodeVulnerabilityReportFilters + 31, // 30: v2.ReportSnapshot.collection_snapshot:type_name -> v2.CollectionSnapshot + 17, // 31: v2.ReportSnapshot.schedule:type_name -> v2.ReportSchedule + 33, // 32: v2.ReportSnapshot.report_status:type_name -> v2.ReportStatus + 23, // 33: v2.ReportSnapshot.notifiers:type_name -> v2.NotifierConfiguration + 43, // 34: v2.ReportSnapshot.user:type_name -> v2.SlimUser + 18, // 35: v2.ReportSnapshot.resource_scope:type_name -> v2.ResourceScope + 10, // 36: v2.ReportStatus.run_state:type_name -> v2.ReportStatus.RunState + 41, // 37: v2.ReportStatus.completed_at:type_name -> google.protobuf.Timestamp + 11, // 38: v2.ReportStatus.report_request_type:type_name -> v2.ReportStatus.ReportMethod + 3, // 39: v2.ReportStatus.report_notification_method:type_name -> v2.NotificationMethod + 3, // 40: v2.RunReportRequest.report_notification_method:type_name -> v2.NotificationMethod + 12, // 41: v2.ReportRequestViewBased.type:type_name -> v2.ReportRequestViewBased.ReportType + 16, // 42: v2.ReportRequestViewBased.view_based_vuln_report_filters:type_name -> v2.ViewBasedVulnerabilityReportFilters + 15, // 43: v2.ReportRequestViewBased.node_vuln_report_filters:type_name -> v2.NodeVulnerabilityReportFilters + 13, // 44: v2.ReportService.PostReportConfiguration:input_type -> v2.ReportConfiguration + 13, // 45: v2.ReportService.UpdateReportConfiguration:input_type -> v2.ReportConfiguration + 42, // 46: v2.ReportService.ListReportConfigurations:input_type -> v2.RawQuery + 42, // 47: v2.ReportService.CountReportConfigurations:input_type -> v2.RawQuery + 44, // 48: v2.ReportService.GetReportConfiguration:input_type -> v2.ResourceByID + 44, // 49: v2.ReportService.DeleteReportConfiguration:input_type -> v2.ResourceByID + 44, // 50: v2.ReportService.GetReportStatus:input_type -> v2.ResourceByID + 27, // 51: v2.ReportService.GetReportHistory:input_type -> v2.GetReportHistoryRequest + 27, // 52: v2.ReportService.GetMyReportHistory:input_type -> v2.GetReportHistoryRequest + 34, // 53: v2.ReportService.RunReport:input_type -> v2.RunReportRequest + 44, // 54: v2.ReportService.CancelReport:input_type -> v2.ResourceByID + 36, // 55: v2.ReportService.DeleteReport:input_type -> v2.DeleteReportRequest + 37, // 56: v2.ReportService.PostViewBasedReport:input_type -> v2.ReportRequestViewBased + 28, // 57: v2.ReportService.GetViewBasedMyReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest + 28, // 58: v2.ReportService.GetViewBasedReportHistory:input_type -> v2.GetViewBasedReportHistoryRequest + 13, // 59: v2.ReportService.PostReportConfiguration:output_type -> v2.ReportConfiguration + 45, // 60: v2.ReportService.UpdateReportConfiguration:output_type -> v2.Empty + 25, // 61: v2.ReportService.ListReportConfigurations:output_type -> v2.ListReportConfigurationsResponse + 26, // 62: v2.ReportService.CountReportConfigurations:output_type -> v2.CountReportConfigurationsResponse + 13, // 63: v2.ReportService.GetReportConfiguration:output_type -> v2.ReportConfiguration + 45, // 64: v2.ReportService.DeleteReportConfiguration:output_type -> v2.Empty + 30, // 65: v2.ReportService.GetReportStatus:output_type -> v2.ReportStatusResponse + 29, // 66: v2.ReportService.GetReportHistory:output_type -> v2.ReportHistoryResponse + 29, // 67: v2.ReportService.GetMyReportHistory:output_type -> v2.ReportHistoryResponse + 35, // 68: v2.ReportService.RunReport:output_type -> v2.RunReportResponse + 45, // 69: v2.ReportService.CancelReport:output_type -> v2.Empty + 45, // 70: v2.ReportService.DeleteReport:output_type -> v2.Empty + 38, // 71: v2.ReportService.PostViewBasedReport:output_type -> v2.RunReportResponseViewBased + 29, // 72: v2.ReportService.GetViewBasedMyReportHistory:output_type -> v2.ReportHistoryResponse + 29, // 73: v2.ReportService.GetViewBasedReportHistory:output_type -> v2.ReportHistoryResponse + 59, // [59:74] is the sub-list for method output_type + 44, // [44:59] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name } func init() { file_api_v2_report_service_proto_init() } @@ -2786,37 +2989,43 @@ func file_api_v2_report_service_proto_init() { file_api_v2_user_proto_init() file_api_v2_report_service_proto_msgTypes[0].OneofWrappers = []any{ (*ReportConfiguration_VulnReportFilters)(nil), + (*ReportConfiguration_NodeVulnReportFilters)(nil), } file_api_v2_report_service_proto_msgTypes[1].OneofWrappers = []any{ (*VulnerabilityReportFilters_AllVuln)(nil), (*VulnerabilityReportFilters_SinceLastSentScheduledReport)(nil), (*VulnerabilityReportFilters_SinceStartDate)(nil), } - file_api_v2_report_service_proto_msgTypes[3].OneofWrappers = []any{ + file_api_v2_report_service_proto_msgTypes[2].OneofWrappers = []any{ + (*NodeVulnerabilityReportFilters_AllVuln)(nil), + } + file_api_v2_report_service_proto_msgTypes[4].OneofWrappers = []any{ (*ReportSchedule_DaysOfWeek_)(nil), (*ReportSchedule_DaysOfMonth_)(nil), } - file_api_v2_report_service_proto_msgTypes[4].OneofWrappers = []any{ + file_api_v2_report_service_proto_msgTypes[5].OneofWrappers = []any{ (*ResourceScope_CollectionScope)(nil), (*ResourceScope_EntityScope)(nil), } - file_api_v2_report_service_proto_msgTypes[9].OneofWrappers = []any{ + file_api_v2_report_service_proto_msgTypes[10].OneofWrappers = []any{ (*NotifierConfiguration_EmailConfig)(nil), } - file_api_v2_report_service_proto_msgTypes[18].OneofWrappers = []any{ + file_api_v2_report_service_proto_msgTypes[19].OneofWrappers = []any{ (*ReportSnapshot_VulnReportFilters)(nil), (*ReportSnapshot_ViewBasedVulnReportFilters)(nil), + (*ReportSnapshot_NodeVulnReportFilters)(nil), } - file_api_v2_report_service_proto_msgTypes[23].OneofWrappers = []any{ + file_api_v2_report_service_proto_msgTypes[24].OneofWrappers = []any{ (*ReportRequestViewBased_ViewBasedVulnReportFilters)(nil), + (*ReportRequestViewBased_NodeVulnReportFilters)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v2_report_service_proto_rawDesc), len(file_api_v2_report_service_proto_rawDesc)), - NumEnums: 12, - NumMessages: 27, + NumEnums: 13, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, diff --git a/generated/api/v2/report_service.swagger.json b/generated/api/v2/report_service.swagger.json index ecff107b9da62..c1dc856396903 100644 --- a/generated/api/v2/report_service.swagger.json +++ b/generated/api/v2/report_service.swagger.json @@ -785,6 +785,9 @@ "vulnReportFilters": { "$ref": "#/definitions/v2VulnerabilityReportFilters" }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, "schedule": { "$ref": "#/definitions/v2ReportSchedule" }, @@ -970,6 +973,17 @@ ], "default": "EXACT" }, + "v2NodeVulnerabilityReportFilters": { + "type": "object", + "properties": { + "allVuln": { + "type": "boolean" + }, + "query": { + "type": "string" + } + } + }, "v2NotificationMethod": { "type": "string", "enum": [ @@ -1043,6 +1057,9 @@ "vulnReportFilters": { "$ref": "#/definitions/v2VulnerabilityReportFilters" }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, "schedule": { "$ref": "#/definitions/v2ReportSchedule" }, @@ -1061,9 +1078,11 @@ "v2ReportConfigurationReportType": { "type": "string", "enum": [ - "VULNERABILITY" + "VULNERABILITY", + "NODE_VULNERABILITY" ], - "default": "VULNERABILITY" + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportHistoryResponse": { "type": "object", @@ -1086,6 +1105,9 @@ "viewBasedVulnReportFilters": { "$ref": "#/definitions/v2ViewBasedVulnerabilityReportFilters" }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, "areaOfConcern": { "type": "string" } @@ -1094,9 +1116,11 @@ "v2ReportRequestViewBasedReportType": { "type": "string", "enum": [ - "VULNERABILITY" + "VULNERABILITY", + "NODE_VULNERABILITY" ], - "default": "VULNERABILITY" + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportSchedule": { "type": "object", @@ -1159,6 +1183,9 @@ "v2ReportSnapshot": { "type": "object", "properties": { + "type": { + "$ref": "#/definitions/v2ReportSnapshotReportType" + }, "reportConfigId": { "type": "string" }, @@ -1177,6 +1204,9 @@ "viewBasedVulnReportFilters": { "$ref": "#/definitions/v2ViewBasedVulnerabilityReportFilters" }, + "nodeVulnReportFilters": { + "$ref": "#/definitions/v2NodeVulnerabilityReportFilters" + }, "collectionSnapshot": { "$ref": "#/definitions/v2CollectionSnapshot" }, @@ -1207,6 +1237,15 @@ } } }, + "v2ReportSnapshotReportType": { + "type": "string", + "enum": [ + "VULNERABILITY", + "NODE_VULNERABILITY" + ], + "default": "VULNERABILITY", + "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + }, "v2ReportStatus": { "type": "object", "properties": { diff --git a/generated/api/v2/report_service_vtproto.pb.go b/generated/api/v2/report_service_vtproto.pb.go index 7e29192d12e97..09cdc5697b797 100644 --- a/generated/api/v2/report_service_vtproto.pb.go +++ b/generated/api/v2/report_service_vtproto.pb.go @@ -65,6 +65,15 @@ func (m *ReportConfiguration_VulnReportFilters) CloneVT() isReportConfiguration_ return r } +func (m *ReportConfiguration_NodeVulnReportFilters) CloneVT() isReportConfiguration_Filter { + if m == nil { + return (*ReportConfiguration_NodeVulnReportFilters)(nil) + } + r := new(ReportConfiguration_NodeVulnReportFilters) + r.NodeVulnReportFilters = m.NodeVulnReportFilters.CloneVT() + return r +} + func (m *VulnerabilityReportFilters) CloneVT() *VulnerabilityReportFilters { if m == nil { return (*VulnerabilityReportFilters)(nil) @@ -128,6 +137,37 @@ func (m *VulnerabilityReportFilters_SinceStartDate) CloneVT() isVulnerabilityRep return r } +func (m *NodeVulnerabilityReportFilters) CloneVT() *NodeVulnerabilityReportFilters { + if m == nil { + return (*NodeVulnerabilityReportFilters)(nil) + } + r := new(NodeVulnerabilityReportFilters) + r.Query = m.Query + if m.CvesSince != nil { + r.CvesSince = m.CvesSince.(interface { + CloneVT() isNodeVulnerabilityReportFilters_CvesSince + }).CloneVT() + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *NodeVulnerabilityReportFilters) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) CloneVT() isNodeVulnerabilityReportFilters_CvesSince { + if m == nil { + return (*NodeVulnerabilityReportFilters_AllVuln)(nil) + } + r := new(NodeVulnerabilityReportFilters_AllVuln) + r.AllVuln = m.AllVuln + return r +} + func (m *ViewBasedVulnerabilityReportFilters) CloneVT() *ViewBasedVulnerabilityReportFilters { if m == nil { return (*ViewBasedVulnerabilityReportFilters)(nil) @@ -545,6 +585,7 @@ func (m *ReportSnapshot) CloneVT() *ReportSnapshot { return (*ReportSnapshot)(nil) } r := new(ReportSnapshot) + r.Type = m.Type r.ReportConfigId = m.ReportConfigId r.ReportJobId = m.ReportJobId r.Name = m.Name @@ -597,6 +638,15 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) CloneVT() isReportSnapshot_F return r } +func (m *ReportSnapshot_NodeVulnReportFilters) CloneVT() isReportSnapshot_Filter { + if m == nil { + return (*ReportSnapshot_NodeVulnReportFilters)(nil) + } + r := new(ReportSnapshot_NodeVulnReportFilters) + r.NodeVulnReportFilters = m.NodeVulnReportFilters.CloneVT() + return r +} + func (m *ReportStatus) CloneVT() *ReportStatus { if m == nil { return (*ReportStatus)(nil) @@ -703,6 +753,15 @@ func (m *ReportRequestViewBased_ViewBasedVulnReportFilters) CloneVT() isReportRe return r } +func (m *ReportRequestViewBased_NodeVulnReportFilters) CloneVT() isReportRequestViewBased_Filter { + if m == nil { + return (*ReportRequestViewBased_NodeVulnReportFilters)(nil) + } + r := new(ReportRequestViewBased_NodeVulnReportFilters) + r.NodeVulnReportFilters = m.NodeVulnReportFilters.CloneVT() + return r +} + func (m *RunReportResponseViewBased) CloneVT() *RunReportResponseViewBased { if m == nil { return (*RunReportResponseViewBased)(nil) @@ -809,6 +868,31 @@ func (this *ReportConfiguration_VulnReportFilters) EqualVT(thatIface isReportCon return true } +func (this *ReportConfiguration_NodeVulnReportFilters) EqualVT(thatIface isReportConfiguration_Filter) bool { + that, ok := thatIface.(*ReportConfiguration_NodeVulnReportFilters) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.NodeVulnReportFilters, that.NodeVulnReportFilters; p != q { + if p == nil { + p = &NodeVulnerabilityReportFilters{} + } + if q == nil { + q = &NodeVulnerabilityReportFilters{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *VulnerabilityReportFilters) EqualVT(that *VulnerabilityReportFilters) bool { if this == that { return true @@ -929,6 +1013,54 @@ func (this *VulnerabilityReportFilters_SinceStartDate) EqualVT(thatIface isVulne return true } +func (this *NodeVulnerabilityReportFilters) EqualVT(that *NodeVulnerabilityReportFilters) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.CvesSince == nil && that.CvesSince != nil { + return false + } else if this.CvesSince != nil { + if that.CvesSince == nil { + return false + } + if !this.CvesSince.(interface { + EqualVT(isNodeVulnerabilityReportFilters_CvesSince) bool + }).EqualVT(that.CvesSince) { + return false + } + } + if this.Query != that.Query { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *NodeVulnerabilityReportFilters) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*NodeVulnerabilityReportFilters) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *NodeVulnerabilityReportFilters_AllVuln) EqualVT(thatIface isNodeVulnerabilityReportFilters_CvesSince) bool { + that, ok := thatIface.(*NodeVulnerabilityReportFilters_AllVuln) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if this.AllVuln != that.AllVuln { + return false + } + return true +} + func (this *ViewBasedVulnerabilityReportFilters) EqualVT(that *ViewBasedVulnerabilityReportFilters) bool { if this == that { return true @@ -1604,6 +1736,9 @@ func (this *ReportSnapshot) EqualVT(that *ReportSnapshot) bool { if !this.ResourceScope.EqualVT(that.ResourceScope) { return false } + if this.Type != that.Type { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1664,6 +1799,31 @@ func (this *ReportSnapshot_ViewBasedVulnReportFilters) EqualVT(thatIface isRepor return true } +func (this *ReportSnapshot_NodeVulnReportFilters) EqualVT(thatIface isReportSnapshot_Filter) bool { + that, ok := thatIface.(*ReportSnapshot_NodeVulnReportFilters) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.NodeVulnReportFilters, that.NodeVulnReportFilters; p != q { + if p == nil { + p = &NodeVulnerabilityReportFilters{} + } + if q == nil { + q = &NodeVulnerabilityReportFilters{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *ReportStatus) EqualVT(that *ReportStatus) bool { if this == that { return true @@ -1817,6 +1977,31 @@ func (this *ReportRequestViewBased_ViewBasedVulnReportFilters) EqualVT(thatIface return true } +func (this *ReportRequestViewBased_NodeVulnReportFilters) EqualVT(thatIface isReportRequestViewBased_Filter) bool { + that, ok := thatIface.(*ReportRequestViewBased_NodeVulnReportFilters) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.NodeVulnReportFilters, that.NodeVulnReportFilters; p != q { + if p == nil { + p = &NodeVulnerabilityReportFilters{} + } + if q == nil { + q = &NodeVulnerabilityReportFilters{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *RunReportResponseViewBased) EqualVT(that *RunReportResponseViewBased) bool { if this == that { return true @@ -1962,6 +2147,29 @@ func (m *ReportConfiguration_VulnReportFilters) MarshalToSizedBufferVT(dAtA []by } return len(dAtA) - i, nil } +func (m *ReportConfiguration_NodeVulnReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReportConfiguration_NodeVulnReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NodeVulnReportFilters != nil { + size, err := m.NodeVulnReportFilters.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x4a + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x4a + } + return len(dAtA) - i, nil +} func (m *VulnerabilityReportFilters) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -2145,6 +2353,72 @@ func (m *VulnerabilityReportFilters_SinceStartDate) MarshalToSizedBufferVT(dAtA } return len(dAtA) - i, nil } +func (m *NodeVulnerabilityReportFilters) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeVulnerabilityReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *NodeVulnerabilityReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if vtmsg, ok := m.CvesSince.(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + } + if len(m.Query) > 0 { + i -= len(m.Query) + copy(dAtA[i:], m.Query) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Query))) + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + i-- + if m.AllVuln { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} func (m *ViewBasedVulnerabilityReportFilters) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -3161,6 +3435,13 @@ func (m *ReportSnapshot) MarshalToSizedBufferVT(dAtA []byte) (int, error) { } i -= size } + if m.Type != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x80 + } if m.ResourceScope != nil { size, err := m.ResourceScope.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -3317,6 +3598,29 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) MarshalToSizedBufferVT(dAtA } return len(dAtA) - i, nil } +func (m *ReportSnapshot_NodeVulnReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReportSnapshot_NodeVulnReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NodeVulnReportFilters != nil { + size, err := m.NodeVulnReportFilters.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x7a + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x7a + } + return len(dAtA) - i, nil +} func (m *ReportStatus) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -3591,6 +3895,29 @@ func (m *ReportRequestViewBased_ViewBasedVulnReportFilters) MarshalToSizedBuffer } return len(dAtA) - i, nil } +func (m *ReportRequestViewBased_NodeVulnReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReportRequestViewBased_NodeVulnReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NodeVulnReportFilters != nil { + size, err := m.NodeVulnReportFilters.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x22 + } + return len(dAtA) - i, nil +} func (m *RunReportResponseViewBased) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -3694,6 +4021,20 @@ func (m *ReportConfiguration_VulnReportFilters) SizeVT() (n int) { } return n } +func (m *ReportConfiguration_NodeVulnReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NodeVulnReportFilters != nil { + l = m.NodeVulnReportFilters.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *VulnerabilityReportFilters) SizeVT() (n int) { if m == nil { return 0 @@ -3746,27 +4087,53 @@ func (m *VulnerabilityReportFilters_AllVuln) SizeVT() (n int) { n += 2 return n } -func (m *VulnerabilityReportFilters_SinceLastSentScheduledReport) SizeVT() (n int) { +func (m *VulnerabilityReportFilters_SinceLastSentScheduledReport) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 2 + return n +} +func (m *VulnerabilityReportFilters_SinceStartDate) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SinceStartDate != nil { + l = (*timestamppb1.Timestamp)(m.SinceStartDate).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} +func (m *NodeVulnerabilityReportFilters) SizeVT() (n int) { if m == nil { return 0 } var l int _ = l - n += 2 + if vtmsg, ok := m.CvesSince.(interface{ SizeVT() int }); ok { + n += vtmsg.SizeVT() + } + l = len(m.Query) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) return n } -func (m *VulnerabilityReportFilters_SinceStartDate) SizeVT() (n int) { + +func (m *NodeVulnerabilityReportFilters_AllVuln) SizeVT() (n int) { if m == nil { return 0 } var l int _ = l - if m.SinceStartDate != nil { - l = (*timestamppb1.Timestamp)(m.SinceStartDate).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } else { - n += 2 - } + n += 2 return n } func (m *ViewBasedVulnerabilityReportFilters) SizeVT() (n int) { @@ -4207,6 +4574,9 @@ func (m *ReportSnapshot) SizeVT() (n int) { l = m.ResourceScope.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.Type != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.Type)) + } n += len(m.unknownFields) return n } @@ -4239,6 +4609,20 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) SizeVT() (n int) { } return n } +func (m *ReportSnapshot_NodeVulnReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NodeVulnReportFilters != nil { + l = m.NodeVulnReportFilters.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *ReportStatus) SizeVT() (n int) { if m == nil { return 0 @@ -4349,6 +4733,20 @@ func (m *ReportRequestViewBased_ViewBasedVulnReportFilters) SizeVT() (n int) { } return n } +func (m *ReportRequestViewBased_NodeVulnReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NodeVulnReportFilters != nil { + l = m.NodeVulnReportFilters.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *RunReportResponseViewBased) SizeVT() (n int) { if m == nil { return 0 @@ -4658,6 +5056,47 @@ func (m *ReportConfiguration) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportConfiguration_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -5063,6 +5502,110 @@ func (m *VulnerabilityReportFilters) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *NodeVulnerabilityReportFilters) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllVuln", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.CvesSince = &NodeVulnerabilityReportFilters_AllVuln{AllVuln: b} + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Query", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Query = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ViewBasedVulnerabilityReportFilters) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -7604,6 +8147,66 @@ func (m *ReportSnapshot) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportSnapshot_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= ReportSnapshot_ReportType(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -8191,11 +8794,43 @@ func (m *ReportRequestViewBased) UnmarshalVT(dAtA []byte) error { m.Filter = &ReportRequestViewBased_ViewBasedVulnReportFilters{ViewBasedVulnReportFilters: v} } iNdEx = postIndex - case 3: + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AreaOfConcern", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AreaOfConcern = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AreaOfConcern", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -8205,23 +8840,32 @@ func (m *ReportRequestViewBased) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.AreaOfConcern = string(dAtA[iNdEx:postIndex]) + if oneof, ok := m.Filter.(*ReportRequestViewBased_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportRequestViewBased_NodeVulnReportFilters{NodeVulnReportFilters: v} + } iNdEx = postIndex default: iNdEx = preIndex @@ -8663,6 +9307,47 @@ func (m *ReportConfiguration) UnmarshalVTUnsafe(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportConfiguration_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -9072,6 +9757,114 @@ func (m *VulnerabilityReportFilters) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *NodeVulnerabilityReportFilters) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllVuln", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.CvesSince = &NodeVulnerabilityReportFilters_AllVuln{AllVuln: b} + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Query", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.Query = stringValue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ViewBasedVulnerabilityReportFilters) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -11681,6 +12474,66 @@ func (m *ReportSnapshot) UnmarshalVTUnsafe(dAtA []byte) error { return err } iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportSnapshot_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= ReportSnapshot_ReportType(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -12324,6 +13177,47 @@ func (m *ReportRequestViewBased) UnmarshalVTUnsafe(dAtA []byte) error { } m.AreaOfConcern = stringValue iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportRequestViewBased_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportRequestViewBased_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/generated/storage/report_configuration.pb.go b/generated/storage/report_configuration.pb.go index 6b916dde2269d..72ad19441b0a3 100644 --- a/generated/storage/report_configuration.pb.go +++ b/generated/storage/report_configuration.pb.go @@ -132,16 +132,19 @@ func (EntityField) EnumDescriptor() ([]byte, []int) { type ReportConfiguration_ReportType int32 const ( - ReportConfiguration_VULNERABILITY ReportConfiguration_ReportType = 0 + ReportConfiguration_VULNERABILITY ReportConfiguration_ReportType = 0 // for historical reasons, this short form was used to mean "Image Vulnerability" + ReportConfiguration_NODE_VULNERABILITY ReportConfiguration_ReportType = 1 ) // Enum value maps for ReportConfiguration_ReportType. var ( ReportConfiguration_ReportType_name = map[int32]string{ 0: "VULNERABILITY", + 1: "NODE_VULNERABILITY", } ReportConfiguration_ReportType_value = map[string]int32{ - "VULNERABILITY": 0, + "VULNERABILITY": 0, + "NODE_VULNERABILITY": 1, } ) @@ -322,6 +325,7 @@ type ReportConfiguration struct { // Types that are valid to be assigned to Filter: // // *ReportConfiguration_VulnReportFilters + // *ReportConfiguration_NodeVulnReportFilters Filter isReportConfiguration_Filter `protobuf_oneof:"filter"` // Deprecated: V1 field. Use resource_scope instead. // @@ -424,6 +428,15 @@ func (x *ReportConfiguration) GetVulnReportFilters() *VulnerabilityReportFilters return nil } +func (x *ReportConfiguration) GetNodeVulnReportFilters() *NodeVulnerabilityReportFilters { + if x != nil { + if x, ok := x.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + return x.NodeVulnReportFilters + } + } + return nil +} + // Deprecated: Marked as deprecated in storage/report_configuration.proto. func (x *ReportConfiguration) GetScopeId() string { if x != nil { @@ -508,8 +521,14 @@ type ReportConfiguration_VulnReportFilters struct { VulnReportFilters *VulnerabilityReportFilters `protobuf:"bytes,5,opt,name=vuln_report_filters,json=vulnReportFilters,proto3,oneof"` } +type ReportConfiguration_NodeVulnReportFilters struct { + NodeVulnReportFilters *NodeVulnerabilityReportFilters `protobuf:"bytes,15,opt,name=node_vuln_report_filters,json=nodeVulnReportFilters,proto3,oneof"` +} + func (*ReportConfiguration_VulnReportFilters) isReportConfiguration_Filter() {} +func (*ReportConfiguration_NodeVulnReportFilters) isReportConfiguration_Filter() {} + type isReportConfiguration_NotifierConfig interface { isReportConfiguration_NotifierConfig() } @@ -951,6 +970,88 @@ func (x *EntityScopeRule) GetValues() []*RuleValue { return nil } +type NodeVulnerabilityReportFilters struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to CvesSince: + // + // *NodeVulnerabilityReportFilters_AllVuln + CvesSince isNodeVulnerabilityReportFilters_CvesSince `protobuf_oneof:"cves_since"` + AccessScopeRules []*SimpleAccessScope_Rules `protobuf:"bytes,2,rep,name=access_scope_rules,json=accessScopeRules,proto3" json:"access_scope_rules,omitempty"` + Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NodeVulnerabilityReportFilters) Reset() { + *x = NodeVulnerabilityReportFilters{} + mi := &file_storage_report_configuration_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NodeVulnerabilityReportFilters) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeVulnerabilityReportFilters) ProtoMessage() {} + +func (x *NodeVulnerabilityReportFilters) ProtoReflect() protoreflect.Message { + mi := &file_storage_report_configuration_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeVulnerabilityReportFilters.ProtoReflect.Descriptor instead. +func (*NodeVulnerabilityReportFilters) Descriptor() ([]byte, []int) { + return file_storage_report_configuration_proto_rawDescGZIP(), []int{6} +} + +func (x *NodeVulnerabilityReportFilters) GetCvesSince() isNodeVulnerabilityReportFilters_CvesSince { + if x != nil { + return x.CvesSince + } + return nil +} + +func (x *NodeVulnerabilityReportFilters) GetAllVuln() bool { + if x != nil { + if x, ok := x.CvesSince.(*NodeVulnerabilityReportFilters_AllVuln); ok { + return x.AllVuln + } + } + return false +} + +func (x *NodeVulnerabilityReportFilters) GetAccessScopeRules() []*SimpleAccessScope_Rules { + if x != nil { + return x.AccessScopeRules + } + return nil +} + +func (x *NodeVulnerabilityReportFilters) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +type isNodeVulnerabilityReportFilters_CvesSince interface { + isNodeVulnerabilityReportFilters_CvesSince() +} + +type NodeVulnerabilityReportFilters_AllVuln struct { + AllVuln bool `protobuf:"varint,1,opt,name=all_vuln,json=allVuln,proto3,oneof"` +} + +func (*NodeVulnerabilityReportFilters_AllVuln) isNodeVulnerabilityReportFilters_CvesSince() {} + // filter for view based reports type ViewBasedVulnerabilityReportFilters struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -962,7 +1063,7 @@ type ViewBasedVulnerabilityReportFilters struct { func (x *ViewBasedVulnerabilityReportFilters) Reset() { *x = ViewBasedVulnerabilityReportFilters{} - mi := &file_storage_report_configuration_proto_msgTypes[6] + mi := &file_storage_report_configuration_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -974,7 +1075,7 @@ func (x *ViewBasedVulnerabilityReportFilters) String() string { func (*ViewBasedVulnerabilityReportFilters) ProtoMessage() {} func (x *ViewBasedVulnerabilityReportFilters) ProtoReflect() protoreflect.Message { - mi := &file_storage_report_configuration_proto_msgTypes[6] + mi := &file_storage_report_configuration_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -987,7 +1088,7 @@ func (x *ViewBasedVulnerabilityReportFilters) ProtoReflect() protoreflect.Messag // Deprecated: Use ViewBasedVulnerabilityReportFilters.ProtoReflect.Descriptor instead. func (*ViewBasedVulnerabilityReportFilters) Descriptor() ([]byte, []int) { - return file_storage_report_configuration_proto_rawDescGZIP(), []int{6} + return file_storage_report_configuration_proto_rawDescGZIP(), []int{7} } func (x *ViewBasedVulnerabilityReportFilters) GetQuery() string { @@ -1008,13 +1109,14 @@ var File_storage_report_configuration_proto protoreflect.FileDescriptor const file_storage_report_configuration_proto_rawDesc = "" + "\n" + - "\"storage/report_configuration.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x11storage/cve.proto\x1a+storage/report_notifier_configuration.proto\x1a!storage/resource_collection.proto\x1a\x12storage/role.proto\x1a\x16storage/schedule.proto\x1a\x12storage/user.proto\"\xb0\x06\n" + + "\"storage/report_configuration.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x11storage/cve.proto\x1a+storage/report_notifier_configuration.proto\x1a!storage/resource_collection.proto\x1a\x12storage/role.proto\x1a\x16storage/schedule.proto\x1a\x12storage/user.proto\"\xac\a\n" + "\x13ReportConfiguration\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x03 \x01(\tR\vdescription\x12;\n" + "\x04type\x18\x04 \x01(\x0e2'.storage.ReportConfiguration.ReportTypeR\x04type\x12U\n" + - "\x13vuln_report_filters\x18\x05 \x01(\v2#.storage.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12\x1d\n" + + "\x13vuln_report_filters\x18\x05 \x01(\v2#.storage.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12b\n" + + "\x18node_vuln_report_filters\x18\x0f \x01(\v2'.storage.NodeVulnerabilityReportFiltersH\x00R\x15nodeVulnReportFilters\x12\x1d\n" + "\bscope_id\x18\x06 \x01(\tB\x02\x18\x01R\ascopeId\x12L\n" + "\femail_config\x18\a \x01(\v2#.storage.EmailNotifierConfigurationB\x02\x18\x01H\x01R\vemailConfig\x12-\n" + "\bschedule\x18\b \x01(\v2\x11.storage.ScheduleR\bschedule\x12H\n" + @@ -1024,10 +1126,11 @@ const file_storage_report_configuration_proto_rawDesc = "" + "\x0eresource_scope\x18\v \x01(\v2\x16.storage.ResourceScopeR\rresourceScope\x12<\n" + "\tnotifiers\x18\f \x03(\v2\x1e.storage.NotifierConfigurationR\tnotifiers\x12+\n" + "\acreator\x18\r \x01(\v2\x11.storage.SlimUserR\acreator\x12\x18\n" + - "\aversion\x18\x0e \x01(\x05R\aversion\"\x1f\n" + + "\aversion\x18\x0e \x01(\x05R\aversion\"7\n" + "\n" + "ReportType\x12\x11\n" + - "\rVULNERABILITY\x10\x00B\b\n" + + "\rVULNERABILITY\x10\x00\x12\x16\n" + + "\x12NODE_VULNERABILITY\x10\x01B\b\n" + "\x06filterB\x11\n" + "\x0fnotifier_config\"\xe6\x01\n" + "\x13ReportLastRunStatus\x12K\n" + @@ -1075,7 +1178,13 @@ const file_storage_report_configuration_proto_rawDesc = "" + "\x0fEntityScopeRule\x12+\n" + "\x06entity\x18\x01 \x01(\x0e2\x13.storage.EntityTypeR\x06entity\x12*\n" + "\x05field\x18\x02 \x01(\x0e2\x14.storage.EntityFieldR\x05field\x12*\n" + - "\x06values\x18\x03 \x03(\v2\x12.storage.RuleValueR\x06values\"\x8b\x01\n" + + "\x06values\x18\x03 \x03(\v2\x12.storage.RuleValueR\x06values\"\xb1\x01\n" + + "\x1eNodeVulnerabilityReportFilters\x12\x1b\n" + + "\ball_vuln\x18\x01 \x01(\bH\x00R\aallVuln\x12N\n" + + "\x12access_scope_rules\x18\x02 \x03(\v2 .storage.SimpleAccessScope.RulesR\x10accessScopeRules\x12\x14\n" + + "\x05query\x18\x03 \x01(\tR\x05queryB\f\n" + + "\n" + + "cves_since\"\x8b\x01\n" + "#ViewBasedVulnerabilityReportFilters\x12\x14\n" + "\x05query\x18\x01 \x01(\tR\x05query\x12N\n" + "\x12access_scope_rules\x18\x02 \x03(\v2 .storage.SimpleAccessScope.RulesR\x10accessScopeRules*s\n" + @@ -1107,7 +1216,7 @@ func file_storage_report_configuration_proto_rawDescGZIP() []byte { } var file_storage_report_configuration_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_storage_report_configuration_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_storage_report_configuration_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_storage_report_configuration_proto_goTypes = []any{ (EntityType)(0), // 0: storage.EntityType (EntityField)(0), // 1: storage.EntityField @@ -1121,44 +1230,47 @@ var file_storage_report_configuration_proto_goTypes = []any{ (*ResourceScope)(nil), // 9: storage.ResourceScope (*EntityScope)(nil), // 10: storage.EntityScope (*EntityScopeRule)(nil), // 11: storage.EntityScopeRule - (*ViewBasedVulnerabilityReportFilters)(nil), // 12: storage.ViewBasedVulnerabilityReportFilters - (*EmailNotifierConfiguration)(nil), // 13: storage.EmailNotifierConfiguration - (*Schedule)(nil), // 14: storage.Schedule - (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp - (*NotifierConfiguration)(nil), // 16: storage.NotifierConfiguration - (*SlimUser)(nil), // 17: storage.SlimUser - (VulnerabilitySeverity)(0), // 18: storage.VulnerabilitySeverity - (*SimpleAccessScope_Rules)(nil), // 19: storage.SimpleAccessScope.Rules - (*RuleValue)(nil), // 20: storage.RuleValue + (*NodeVulnerabilityReportFilters)(nil), // 12: storage.NodeVulnerabilityReportFilters + (*ViewBasedVulnerabilityReportFilters)(nil), // 13: storage.ViewBasedVulnerabilityReportFilters + (*EmailNotifierConfiguration)(nil), // 14: storage.EmailNotifierConfiguration + (*Schedule)(nil), // 15: storage.Schedule + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp + (*NotifierConfiguration)(nil), // 17: storage.NotifierConfiguration + (*SlimUser)(nil), // 18: storage.SlimUser + (VulnerabilitySeverity)(0), // 19: storage.VulnerabilitySeverity + (*SimpleAccessScope_Rules)(nil), // 20: storage.SimpleAccessScope.Rules + (*RuleValue)(nil), // 21: storage.RuleValue } var file_storage_report_configuration_proto_depIdxs = []int32{ 2, // 0: storage.ReportConfiguration.type:type_name -> storage.ReportConfiguration.ReportType 8, // 1: storage.ReportConfiguration.vuln_report_filters:type_name -> storage.VulnerabilityReportFilters - 13, // 2: storage.ReportConfiguration.email_config:type_name -> storage.EmailNotifierConfiguration - 14, // 3: storage.ReportConfiguration.schedule:type_name -> storage.Schedule - 7, // 4: storage.ReportConfiguration.last_run_status:type_name -> storage.ReportLastRunStatus - 15, // 5: storage.ReportConfiguration.last_successful_run_time:type_name -> google.protobuf.Timestamp - 9, // 6: storage.ReportConfiguration.resource_scope:type_name -> storage.ResourceScope - 16, // 7: storage.ReportConfiguration.notifiers:type_name -> storage.NotifierConfiguration - 17, // 8: storage.ReportConfiguration.creator:type_name -> storage.SlimUser - 3, // 9: storage.ReportLastRunStatus.report_status:type_name -> storage.ReportLastRunStatus.RunStatus - 15, // 10: storage.ReportLastRunStatus.last_run_time:type_name -> google.protobuf.Timestamp - 4, // 11: storage.VulnerabilityReportFilters.fixability:type_name -> storage.VulnerabilityReportFilters.Fixability - 18, // 12: storage.VulnerabilityReportFilters.severities:type_name -> storage.VulnerabilitySeverity - 5, // 13: storage.VulnerabilityReportFilters.image_types:type_name -> storage.VulnerabilityReportFilters.ImageType - 15, // 14: storage.VulnerabilityReportFilters.since_start_date:type_name -> google.protobuf.Timestamp - 19, // 15: storage.VulnerabilityReportFilters.access_scope_rules:type_name -> storage.SimpleAccessScope.Rules - 10, // 16: storage.ResourceScope.entity_scope:type_name -> storage.EntityScope - 11, // 17: storage.EntityScope.rules:type_name -> storage.EntityScopeRule - 0, // 18: storage.EntityScopeRule.entity:type_name -> storage.EntityType - 1, // 19: storage.EntityScopeRule.field:type_name -> storage.EntityField - 20, // 20: storage.EntityScopeRule.values:type_name -> storage.RuleValue - 19, // 21: storage.ViewBasedVulnerabilityReportFilters.access_scope_rules:type_name -> storage.SimpleAccessScope.Rules - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 12, // 2: storage.ReportConfiguration.node_vuln_report_filters:type_name -> storage.NodeVulnerabilityReportFilters + 14, // 3: storage.ReportConfiguration.email_config:type_name -> storage.EmailNotifierConfiguration + 15, // 4: storage.ReportConfiguration.schedule:type_name -> storage.Schedule + 7, // 5: storage.ReportConfiguration.last_run_status:type_name -> storage.ReportLastRunStatus + 16, // 6: storage.ReportConfiguration.last_successful_run_time:type_name -> google.protobuf.Timestamp + 9, // 7: storage.ReportConfiguration.resource_scope:type_name -> storage.ResourceScope + 17, // 8: storage.ReportConfiguration.notifiers:type_name -> storage.NotifierConfiguration + 18, // 9: storage.ReportConfiguration.creator:type_name -> storage.SlimUser + 3, // 10: storage.ReportLastRunStatus.report_status:type_name -> storage.ReportLastRunStatus.RunStatus + 16, // 11: storage.ReportLastRunStatus.last_run_time:type_name -> google.protobuf.Timestamp + 4, // 12: storage.VulnerabilityReportFilters.fixability:type_name -> storage.VulnerabilityReportFilters.Fixability + 19, // 13: storage.VulnerabilityReportFilters.severities:type_name -> storage.VulnerabilitySeverity + 5, // 14: storage.VulnerabilityReportFilters.image_types:type_name -> storage.VulnerabilityReportFilters.ImageType + 16, // 15: storage.VulnerabilityReportFilters.since_start_date:type_name -> google.protobuf.Timestamp + 20, // 16: storage.VulnerabilityReportFilters.access_scope_rules:type_name -> storage.SimpleAccessScope.Rules + 10, // 17: storage.ResourceScope.entity_scope:type_name -> storage.EntityScope + 11, // 18: storage.EntityScope.rules:type_name -> storage.EntityScopeRule + 0, // 19: storage.EntityScopeRule.entity:type_name -> storage.EntityType + 1, // 20: storage.EntityScopeRule.field:type_name -> storage.EntityField + 21, // 21: storage.EntityScopeRule.values:type_name -> storage.RuleValue + 20, // 22: storage.NodeVulnerabilityReportFilters.access_scope_rules:type_name -> storage.SimpleAccessScope.Rules + 20, // 23: storage.ViewBasedVulnerabilityReportFilters.access_scope_rules:type_name -> storage.SimpleAccessScope.Rules + 24, // [24:24] is the sub-list for method output_type + 24, // [24:24] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name } func init() { file_storage_report_configuration_proto_init() } @@ -1174,6 +1286,7 @@ func file_storage_report_configuration_proto_init() { file_storage_user_proto_init() file_storage_report_configuration_proto_msgTypes[0].OneofWrappers = []any{ (*ReportConfiguration_VulnReportFilters)(nil), + (*ReportConfiguration_NodeVulnReportFilters)(nil), (*ReportConfiguration_EmailConfig)(nil), } file_storage_report_configuration_proto_msgTypes[2].OneofWrappers = []any{ @@ -1185,13 +1298,16 @@ func file_storage_report_configuration_proto_init() { (*ResourceScope_CollectionId)(nil), (*ResourceScope_EntityScope)(nil), } + file_storage_report_configuration_proto_msgTypes[6].OneofWrappers = []any{ + (*NodeVulnerabilityReportFilters_AllVuln)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_storage_report_configuration_proto_rawDesc), len(file_storage_report_configuration_proto_rawDesc)), NumEnums: 6, - NumMessages: 7, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/generated/storage/report_configuration_vtproto.pb.go b/generated/storage/report_configuration_vtproto.pb.go index 9aa70cafd97d0..91e2555199854 100644 --- a/generated/storage/report_configuration_vtproto.pb.go +++ b/generated/storage/report_configuration_vtproto.pb.go @@ -75,6 +75,15 @@ func (m *ReportConfiguration_VulnReportFilters) CloneVT() isReportConfiguration_ return r } +func (m *ReportConfiguration_NodeVulnReportFilters) CloneVT() isReportConfiguration_Filter { + if m == nil { + return (*ReportConfiguration_NodeVulnReportFilters)(nil) + } + r := new(ReportConfiguration_NodeVulnReportFilters) + r.NodeVulnReportFilters = m.NodeVulnReportFilters.CloneVT() + return r +} + func (m *ReportConfiguration_EmailConfig) CloneVT() isReportConfiguration_NotifierConfig { if m == nil { return (*ReportConfiguration_EmailConfig)(nil) @@ -261,6 +270,44 @@ func (m *EntityScopeRule) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *NodeVulnerabilityReportFilters) CloneVT() *NodeVulnerabilityReportFilters { + if m == nil { + return (*NodeVulnerabilityReportFilters)(nil) + } + r := new(NodeVulnerabilityReportFilters) + r.Query = m.Query + if m.CvesSince != nil { + r.CvesSince = m.CvesSince.(interface { + CloneVT() isNodeVulnerabilityReportFilters_CvesSince + }).CloneVT() + } + if rhs := m.AccessScopeRules; rhs != nil { + tmpContainer := make([]*SimpleAccessScope_Rules, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.AccessScopeRules = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *NodeVulnerabilityReportFilters) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) CloneVT() isNodeVulnerabilityReportFilters_CvesSince { + if m == nil { + return (*NodeVulnerabilityReportFilters_AllVuln)(nil) + } + r := new(NodeVulnerabilityReportFilters_AllVuln) + r.AllVuln = m.AllVuln + return r +} + func (m *ViewBasedVulnerabilityReportFilters) CloneVT() *ViewBasedVulnerabilityReportFilters { if m == nil { return (*ViewBasedVulnerabilityReportFilters)(nil) @@ -425,6 +472,31 @@ func (this *ReportConfiguration_EmailConfig) EqualVT(thatIface isReportConfigura return true } +func (this *ReportConfiguration_NodeVulnReportFilters) EqualVT(thatIface isReportConfiguration_Filter) bool { + that, ok := thatIface.(*ReportConfiguration_NodeVulnReportFilters) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.NodeVulnReportFilters, that.NodeVulnReportFilters; p != q { + if p == nil { + p = &NodeVulnerabilityReportFilters{} + } + if q == nil { + q = &NodeVulnerabilityReportFilters{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *ReportLastRunStatus) EqualVT(that *ReportLastRunStatus) bool { if this == that { return true @@ -732,6 +804,71 @@ func (this *EntityScopeRule) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *NodeVulnerabilityReportFilters) EqualVT(that *NodeVulnerabilityReportFilters) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.CvesSince == nil && that.CvesSince != nil { + return false + } else if this.CvesSince != nil { + if that.CvesSince == nil { + return false + } + if !this.CvesSince.(interface { + EqualVT(isNodeVulnerabilityReportFilters_CvesSince) bool + }).EqualVT(that.CvesSince) { + return false + } + } + if len(this.AccessScopeRules) != len(that.AccessScopeRules) { + return false + } + for i, vx := range this.AccessScopeRules { + vy := that.AccessScopeRules[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &SimpleAccessScope_Rules{} + } + if q == nil { + q = &SimpleAccessScope_Rules{} + } + if !p.EqualVT(q) { + return false + } + } + } + if this.Query != that.Query { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *NodeVulnerabilityReportFilters) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*NodeVulnerabilityReportFilters) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *NodeVulnerabilityReportFilters_AllVuln) EqualVT(thatIface isNodeVulnerabilityReportFilters_CvesSince) bool { + that, ok := thatIface.(*NodeVulnerabilityReportFilters_AllVuln) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if this.AllVuln != that.AllVuln { + return false + } + return true +} + func (this *ViewBasedVulnerabilityReportFilters) EqualVT(that *ViewBasedVulnerabilityReportFilters) bool { if this == that { return true @@ -798,7 +935,7 @@ func (m *ReportConfiguration) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if vtmsg, ok := m.NotifierConfig.(interface { + if vtmsg, ok := m.Filter.(interface { MarshalToSizedBufferVT([]byte) (int, error) }); ok { size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) @@ -807,7 +944,7 @@ func (m *ReportConfiguration) MarshalToSizedBufferVT(dAtA []byte) (int, error) { } i -= size } - if vtmsg, ok := m.Filter.(interface { + if vtmsg, ok := m.NotifierConfig.(interface { MarshalToSizedBufferVT([]byte) (int, error) }); ok { size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) @@ -965,6 +1102,29 @@ func (m *ReportConfiguration_EmailConfig) MarshalToSizedBufferVT(dAtA []byte) (i } return len(dAtA) - i, nil } +func (m *ReportConfiguration_NodeVulnReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReportConfiguration_NodeVulnReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NodeVulnReportFilters != nil { + size, err := m.NodeVulnReportFilters.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x7a + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x7a + } + return len(dAtA) - i, nil +} func (m *ReportLastRunStatus) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1404,6 +1564,84 @@ func (m *EntityScopeRule) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *NodeVulnerabilityReportFilters) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NodeVulnerabilityReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *NodeVulnerabilityReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if vtmsg, ok := m.CvesSince.(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + } + if len(m.Query) > 0 { + i -= len(m.Query) + copy(dAtA[i:], m.Query) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Query))) + i-- + dAtA[i] = 0x1a + } + if len(m.AccessScopeRules) > 0 { + for iNdEx := len(m.AccessScopeRules) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.AccessScopeRules[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + return len(dAtA) - i, nil +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + i-- + if m.AllVuln { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} func (m *ViewBasedVulnerabilityReportFilters) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1548,6 +1786,20 @@ func (m *ReportConfiguration_EmailConfig) SizeVT() (n int) { } return n } +func (m *ReportConfiguration_NodeVulnReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NodeVulnReportFilters != nil { + l = m.NodeVulnReportFilters.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *ReportLastRunStatus) SizeVT() (n int) { if m == nil { return 0 @@ -1728,6 +1980,38 @@ func (m *EntityScopeRule) SizeVT() (n int) { return n } +func (m *NodeVulnerabilityReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if vtmsg, ok := m.CvesSince.(interface{ SizeVT() int }); ok { + n += vtmsg.SizeVT() + } + if len(m.AccessScopeRules) > 0 { + for _, e := range m.AccessScopeRules { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + l = len(m.Query) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *NodeVulnerabilityReportFilters_AllVuln) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 2 + return n +} func (m *ViewBasedVulnerabilityReportFilters) SizeVT() (n int) { if m == nil { return 0 @@ -2239,14 +2523,55 @@ func (m *ReportConfiguration) UnmarshalVT(dAtA []byte) error { break } } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportConfiguration_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF @@ -3168,6 +3493,144 @@ func (m *EntityScopeRule) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *NodeVulnerabilityReportFilters) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllVuln", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.CvesSince = &NodeVulnerabilityReportFilters_AllVuln{AllVuln: b} + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccessScopeRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccessScopeRules = append(m.AccessScopeRules, &SimpleAccessScope_Rules{}) + if err := m.AccessScopeRules[len(m.AccessScopeRules)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Query", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Query = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ViewBasedVulnerabilityReportFilters) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -3792,6 +4255,47 @@ func (m *ReportConfiguration) UnmarshalVTUnsafe(dAtA []byte) error { break } } + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportConfiguration_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportConfiguration_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4733,6 +5237,148 @@ func (m *EntityScopeRule) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *NodeVulnerabilityReportFilters) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NodeVulnerabilityReportFilters: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllVuln", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.CvesSince = &NodeVulnerabilityReportFilters_AllVuln{AllVuln: b} + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccessScopeRules", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccessScopeRules = append(m.AccessScopeRules, &SimpleAccessScope_Rules{}) + if err := m.AccessScopeRules[len(m.AccessScopeRules)-1].UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Query", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.Query = stringValue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ViewBasedVulnerabilityReportFilters) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/generated/storage/report_snapshot.pb.go b/generated/storage/report_snapshot.pb.go index 462aa59f8203d..1f4e4f2cedd82 100644 --- a/generated/storage/report_snapshot.pb.go +++ b/generated/storage/report_snapshot.pb.go @@ -25,16 +25,19 @@ const ( type ReportSnapshot_ReportType int32 const ( - ReportSnapshot_VULNERABILITY ReportSnapshot_ReportType = 0 + ReportSnapshot_VULNERABILITY ReportSnapshot_ReportType = 0 // for historical reasons, this short form was used to mean "Image Vulnerability" + ReportSnapshot_NODE_VULNERABILITY ReportSnapshot_ReportType = 1 ) // Enum value maps for ReportSnapshot_ReportType. var ( ReportSnapshot_ReportType_name = map[int32]string{ 0: "VULNERABILITY", + 1: "NODE_VULNERABILITY", } ReportSnapshot_ReportType_value = map[string]int32{ - "VULNERABILITY": 0, + "VULNERABILITY": 0, + "NODE_VULNERABILITY": 1, } ) @@ -228,6 +231,7 @@ type ReportSnapshot struct { // // *ReportSnapshot_VulnReportFilters // *ReportSnapshot_ViewBasedVulnReportFilters + // *ReportSnapshot_NodeVulnReportFilters Filter isReportSnapshot_Filter `protobuf_oneof:"filter"` Collection *CollectionSnapshot `protobuf:"bytes,7,opt,name=collection,proto3" json:"collection,omitempty"` Schedule *Schedule `protobuf:"bytes,8,opt,name=schedule,proto3" json:"schedule,omitempty"` @@ -333,6 +337,15 @@ func (x *ReportSnapshot) GetViewBasedVulnReportFilters() *ViewBasedVulnerability return nil } +func (x *ReportSnapshot) GetNodeVulnReportFilters() *NodeVulnerabilityReportFilters { + if x != nil { + if x, ok := x.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + return x.NodeVulnReportFilters + } + } + return nil +} + func (x *ReportSnapshot) GetCollection() *CollectionSnapshot { if x != nil { return x.Collection @@ -394,10 +407,16 @@ type ReportSnapshot_ViewBasedVulnReportFilters struct { ViewBasedVulnReportFilters *ViewBasedVulnerabilityReportFilters `protobuf:"bytes,12,opt,name=view_based_vuln_report_filters,json=viewBasedVulnReportFilters,proto3,oneof"` } +type ReportSnapshot_NodeVulnReportFilters struct { + NodeVulnReportFilters *NodeVulnerabilityReportFilters `protobuf:"bytes,15,opt,name=node_vuln_report_filters,json=nodeVulnReportFilters,proto3,oneof"` +} + func (*ReportSnapshot_VulnReportFilters) isReportSnapshot_Filter() {} func (*ReportSnapshot_ViewBasedVulnReportFilters) isReportSnapshot_Filter() {} +func (*ReportSnapshot_NodeVulnReportFilters) isReportSnapshot_Filter() {} + type CollectionSnapshot struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -612,7 +631,7 @@ var File_storage_report_snapshot_proto protoreflect.FileDescriptor const file_storage_report_snapshot_proto_rawDesc = "" + "\n" + - "\x1dstorage/report_snapshot.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\"storage/report_configuration.proto\x1a+storage/report_notifier_configuration.proto\x1a\x16storage/schedule.proto\x1a\x12storage/user.proto\"\xc2\x06\n" + + "\x1dstorage/report_snapshot.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\"storage/report_configuration.proto\x1a+storage/report_notifier_configuration.proto\x1a\x16storage/schedule.proto\x1a\x12storage/user.proto\"\xbe\a\n" + "\x0eReportSnapshot\x12\x1b\n" + "\treport_id\x18\x01 \x01(\tR\breportId\x126\n" + "\x17report_configuration_id\x18\x02 \x01(\tR\x15reportConfigurationId\x12\x12\n" + @@ -620,7 +639,8 @@ const file_storage_report_snapshot_proto_rawDesc = "" + "\vdescription\x18\x04 \x01(\tR\vdescription\x126\n" + "\x04type\x18\x05 \x01(\x0e2\".storage.ReportSnapshot.ReportTypeR\x04type\x12U\n" + "\x13vuln_report_filters\x18\x06 \x01(\v2#.storage.VulnerabilityReportFiltersH\x00R\x11vulnReportFilters\x12r\n" + - "\x1eview_based_vuln_report_filters\x18\f \x01(\v2,.storage.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12;\n" + + "\x1eview_based_vuln_report_filters\x18\f \x01(\v2,.storage.ViewBasedVulnerabilityReportFiltersH\x00R\x1aviewBasedVulnReportFilters\x12b\n" + + "\x18node_vuln_report_filters\x18\x0f \x01(\v2'.storage.NodeVulnerabilityReportFiltersH\x00R\x15nodeVulnReportFilters\x12;\n" + "\n" + "collection\x18\a \x01(\v2\x1b.storage.CollectionSnapshotR\n" + "collection\x12-\n" + @@ -630,10 +650,11 @@ const file_storage_report_snapshot_proto_rawDesc = "" + " \x03(\v2\x19.storage.NotifierSnapshotR\tnotifiers\x12/\n" + "\trequester\x18\v \x01(\v2\x11.storage.SlimUserR\trequester\x12&\n" + "\x0farea_of_concern\x18\r \x01(\tR\rareaOfConcern\x12=\n" + - "\x0eresource_scope\x18\x0e \x01(\v2\x16.storage.ResourceScopeR\rresourceScope\"\x1f\n" + + "\x0eresource_scope\x18\x0e \x01(\v2\x16.storage.ResourceScopeR\rresourceScope\"7\n" + "\n" + "ReportType\x12\x11\n" + - "\rVULNERABILITY\x10\x00B\b\n" + + "\rVULNERABILITY\x10\x00\x12\x16\n" + + "\x12NODE_VULNERABILITY\x10\x01B\b\n" + "\x06filter\"8\n" + "\x12CollectionSnapshot\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + @@ -690,33 +711,35 @@ var file_storage_report_snapshot_proto_goTypes = []any{ (*ReportStatus)(nil), // 7: storage.ReportStatus (*VulnerabilityReportFilters)(nil), // 8: storage.VulnerabilityReportFilters (*ViewBasedVulnerabilityReportFilters)(nil), // 9: storage.ViewBasedVulnerabilityReportFilters - (*Schedule)(nil), // 10: storage.Schedule - (*SlimUser)(nil), // 11: storage.SlimUser - (*ResourceScope)(nil), // 12: storage.ResourceScope - (*EmailNotifierConfiguration)(nil), // 13: storage.EmailNotifierConfiguration - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*NodeVulnerabilityReportFilters)(nil), // 10: storage.NodeVulnerabilityReportFilters + (*Schedule)(nil), // 11: storage.Schedule + (*SlimUser)(nil), // 12: storage.SlimUser + (*ResourceScope)(nil), // 13: storage.ResourceScope + (*EmailNotifierConfiguration)(nil), // 14: storage.EmailNotifierConfiguration + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp } var file_storage_report_snapshot_proto_depIdxs = []int32{ 0, // 0: storage.ReportSnapshot.type:type_name -> storage.ReportSnapshot.ReportType 8, // 1: storage.ReportSnapshot.vuln_report_filters:type_name -> storage.VulnerabilityReportFilters 9, // 2: storage.ReportSnapshot.view_based_vuln_report_filters:type_name -> storage.ViewBasedVulnerabilityReportFilters - 5, // 3: storage.ReportSnapshot.collection:type_name -> storage.CollectionSnapshot - 10, // 4: storage.ReportSnapshot.schedule:type_name -> storage.Schedule - 7, // 5: storage.ReportSnapshot.report_status:type_name -> storage.ReportStatus - 6, // 6: storage.ReportSnapshot.notifiers:type_name -> storage.NotifierSnapshot - 11, // 7: storage.ReportSnapshot.requester:type_name -> storage.SlimUser - 12, // 8: storage.ReportSnapshot.resource_scope:type_name -> storage.ResourceScope - 13, // 9: storage.NotifierSnapshot.email_config:type_name -> storage.EmailNotifierConfiguration - 1, // 10: storage.ReportStatus.run_state:type_name -> storage.ReportStatus.RunState - 14, // 11: storage.ReportStatus.queued_at:type_name -> google.protobuf.Timestamp - 14, // 12: storage.ReportStatus.completed_at:type_name -> google.protobuf.Timestamp - 3, // 13: storage.ReportStatus.report_request_type:type_name -> storage.ReportStatus.RunMethod - 2, // 14: storage.ReportStatus.report_notification_method:type_name -> storage.ReportStatus.NotificationMethod - 15, // [15:15] is the sub-list for method output_type - 15, // [15:15] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 10, // 3: storage.ReportSnapshot.node_vuln_report_filters:type_name -> storage.NodeVulnerabilityReportFilters + 5, // 4: storage.ReportSnapshot.collection:type_name -> storage.CollectionSnapshot + 11, // 5: storage.ReportSnapshot.schedule:type_name -> storage.Schedule + 7, // 6: storage.ReportSnapshot.report_status:type_name -> storage.ReportStatus + 6, // 7: storage.ReportSnapshot.notifiers:type_name -> storage.NotifierSnapshot + 12, // 8: storage.ReportSnapshot.requester:type_name -> storage.SlimUser + 13, // 9: storage.ReportSnapshot.resource_scope:type_name -> storage.ResourceScope + 14, // 10: storage.NotifierSnapshot.email_config:type_name -> storage.EmailNotifierConfiguration + 1, // 11: storage.ReportStatus.run_state:type_name -> storage.ReportStatus.RunState + 15, // 12: storage.ReportStatus.queued_at:type_name -> google.protobuf.Timestamp + 15, // 13: storage.ReportStatus.completed_at:type_name -> google.protobuf.Timestamp + 3, // 14: storage.ReportStatus.report_request_type:type_name -> storage.ReportStatus.RunMethod + 2, // 15: storage.ReportStatus.report_notification_method:type_name -> storage.ReportStatus.NotificationMethod + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_storage_report_snapshot_proto_init() } @@ -731,6 +754,7 @@ func file_storage_report_snapshot_proto_init() { file_storage_report_snapshot_proto_msgTypes[0].OneofWrappers = []any{ (*ReportSnapshot_VulnReportFilters)(nil), (*ReportSnapshot_ViewBasedVulnReportFilters)(nil), + (*ReportSnapshot_NodeVulnReportFilters)(nil), } file_storage_report_snapshot_proto_msgTypes[2].OneofWrappers = []any{ (*NotifierSnapshot_EmailConfig)(nil), diff --git a/generated/storage/report_snapshot_vtproto.pb.go b/generated/storage/report_snapshot_vtproto.pb.go index decb6e35adc97..6b9c8a76027e3 100644 --- a/generated/storage/report_snapshot_vtproto.pb.go +++ b/generated/storage/report_snapshot_vtproto.pb.go @@ -79,6 +79,15 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) CloneVT() isReportSnapshot_F return r } +func (m *ReportSnapshot_NodeVulnReportFilters) CloneVT() isReportSnapshot_Filter { + if m == nil { + return (*ReportSnapshot_NodeVulnReportFilters)(nil) + } + r := new(ReportSnapshot_NodeVulnReportFilters) + r.NodeVulnReportFilters = m.NodeVulnReportFilters.CloneVT() + return r +} + func (m *CollectionSnapshot) CloneVT() *CollectionSnapshot { if m == nil { return (*CollectionSnapshot)(nil) @@ -278,6 +287,31 @@ func (this *ReportSnapshot_ViewBasedVulnReportFilters) EqualVT(thatIface isRepor return true } +func (this *ReportSnapshot_NodeVulnReportFilters) EqualVT(thatIface isReportSnapshot_Filter) bool { + that, ok := thatIface.(*ReportSnapshot_NodeVulnReportFilters) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.NodeVulnReportFilters, that.NodeVulnReportFilters; p != q { + if p == nil { + p = &NodeVulnerabilityReportFilters{} + } + if q == nil { + q = &NodeVulnerabilityReportFilters{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *CollectionSnapshot) EqualVT(that *CollectionSnapshot) bool { if this == that { return true @@ -580,6 +614,29 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) MarshalToSizedBufferVT(dAtA } return len(dAtA) - i, nil } +func (m *ReportSnapshot_NodeVulnReportFilters) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReportSnapshot_NodeVulnReportFilters) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.NodeVulnReportFilters != nil { + size, err := m.NodeVulnReportFilters.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x7a + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x7a + } + return len(dAtA) - i, nil +} func (m *CollectionSnapshot) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -864,6 +921,20 @@ func (m *ReportSnapshot_ViewBasedVulnReportFilters) SizeVT() (n int) { } return n } +func (m *ReportSnapshot_NodeVulnReportFilters) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NodeVulnReportFilters != nil { + l = m.NodeVulnReportFilters.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *CollectionSnapshot) SizeVT() (n int) { if m == nil { return 0 @@ -1448,6 +1519,47 @@ func (m *ReportSnapshot) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportSnapshot_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -2445,6 +2557,47 @@ func (m *ReportSnapshot) UnmarshalVTUnsafe(dAtA []byte) error { return err } iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeVulnReportFilters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Filter.(*ReportSnapshot_NodeVulnReportFilters); ok { + if err := oneof.NodeVulnReportFilters.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &NodeVulnerabilityReportFilters{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Filter = &ReportSnapshot_NodeVulnReportFilters{NodeVulnReportFilters: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/proto/api/v2/node_report_service.proto b/proto/api/v2/node_report_service.proto new file mode 100644 index 0000000000000..5e0144f13d304 --- /dev/null +++ b/proto/api/v2/node_report_service.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; + +package v2; + +import "api/v2/common.proto"; +import "api/v2/report_service.proto"; +import "api/v2/search_query.proto"; +import weak "google/api/annotations.proto"; + +option go_package = "./api/v2;v2"; +option java_package = "io.stackrox.proto.api.v2"; + +service NodeReportService { + rpc PostNodeReportConfiguration(ReportConfiguration) returns (ReportConfiguration) { + option (google.api.http) = { + post: "/v2/reports/node/configurations" + body: "*" + }; + } + + rpc UpdateNodeReportConfiguration(ReportConfiguration) returns (Empty) { + option (google.api.http) = { + put: "/v2/reports/node/configurations/{id}" + body: "*" + }; + } + + rpc ListNodeReportConfigurations(RawQuery) returns (ListReportConfigurationsResponse) { + option (google.api.http) = {get: "/v2/reports/node/configurations"}; + } + + rpc CountNodeReportConfigurations(RawQuery) returns (CountReportConfigurationsResponse) { + option (google.api.http) = {get: "/v2/reports/node/configuration-count"}; + } + + rpc GetNodeReportConfiguration(ResourceByID) returns (ReportConfiguration) { + option (google.api.http) = {get: "/v2/reports/node/configurations/{id}"}; + } + + rpc DeleteNodeReportConfiguration(ResourceByID) returns (Empty) { + option (google.api.http) = {delete: "/v2/reports/node/configurations/{id}"}; + } + + rpc RunNodeReport(RunReportRequest) returns (RunReportResponse) { + option (google.api.http) = { + post: "/v2/reports/node/run" + body: "*" + }; + } + + rpc GetNodeReportHistory(GetReportHistoryRequest) returns (ReportHistoryResponse) { + option (google.api.http) = {get: "/v2/reports/node/configurations/{id}/history"}; + } + + rpc GetMyNodeReportHistory(GetReportHistoryRequest) returns (ReportHistoryResponse) { + option (google.api.http) = {get: "/v2/reports/node/configurations/{id}/my-history"}; + } + + rpc GetNodeReportStatus(ResourceByID) returns (ReportStatusResponse) { + option (google.api.http) = {get: "/v2/reports/node/jobs/{id}/status"}; + } + + rpc CancelNodeReport(ResourceByID) returns (Empty) { + option (google.api.http) = {delete: "/v2/reports/node/jobs/{id}/cancel"}; + } + + rpc DeleteNodeReport(DeleteReportRequest) returns (Empty) { + option (google.api.http) = {delete: "/v2/reports/node/jobs/{id}/delete"}; + } + + rpc PostViewBasedNodeReport(ReportRequestViewBased) returns (RunReportResponseViewBased) { + option (google.api.http) = { + post: "/v2/reports/node/view-based/run" + body: "*" + }; + } + + rpc GetViewBasedNodeReportHistory(GetViewBasedReportHistoryRequest) returns (ReportHistoryResponse) { + option (google.api.http) = {get: "/v2/reports/node/view-based/history"}; + } + + rpc GetViewBasedMyNodeReportHistory(GetViewBasedReportHistoryRequest) returns (ReportHistoryResponse) { + option (google.api.http) = {get: "/v2/reports/node/view-based/my-history"}; + } +} diff --git a/proto/api/v2/report_service.proto b/proto/api/v2/report_service.proto index 66feca34d922b..74adf036f6548 100644 --- a/proto/api/v2/report_service.proto +++ b/proto/api/v2/report_service.proto @@ -13,7 +13,8 @@ option java_package = "io.stackrox.proto.api.v2"; message ReportConfiguration { enum ReportType { - VULNERABILITY = 0; + VULNERABILITY = 0; // for historical reasons, this short form was used to mean "Image Vulnerability" + NODE_VULNERABILITY = 1; } string id = 1; string name = 2; @@ -21,6 +22,7 @@ message ReportConfiguration { ReportType type = 4; oneof filter { VulnerabilityReportFilters vuln_report_filters = 5; + NodeVulnerabilityReportFilters node_vuln_report_filters = 9; } ReportSchedule schedule = 6; @@ -65,6 +67,13 @@ message VulnerabilityReportFilters { string query = 12; } +message NodeVulnerabilityReportFilters { + oneof cves_since { + bool all_vuln = 1; + } + string query = 2; +} + //filter for ondemand view based reports message ViewBasedVulnerabilityReportFilters { string query = 5; @@ -192,6 +201,11 @@ message CollectionSnapshot { } message ReportSnapshot { + enum ReportType { + VULNERABILITY = 0; // for historical reasons, this short form was used to mean "Image Vulnerability" + NODE_VULNERABILITY = 1; + } + ReportType type = 16; string report_config_id = 1; string report_job_id = 2; string name = 3; @@ -199,6 +213,7 @@ message ReportSnapshot { oneof filter { VulnerabilityReportFilters vuln_report_filters = 5; ViewBasedVulnerabilityReportFilters view_based_vuln_report_filters = 12; + NodeVulnerabilityReportFilters node_vuln_report_filters = 15; } CollectionSnapshot collection_snapshot = 6; ReportSchedule schedule = 7; @@ -252,11 +267,13 @@ message DeleteReportRequest { message ReportRequestViewBased { enum ReportType { - VULNERABILITY = 0; + VULNERABILITY = 0; // for historical reasons, this short form was used to mean "Image Vulnerability" + NODE_VULNERABILITY = 1; } ReportType type = 1; oneof filter { ViewBasedVulnerabilityReportFilters view_based_vuln_report_filters = 2; + NodeVulnerabilityReportFilters node_vuln_report_filters = 4; } string area_of_concern = 3; diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index 7eab028b96890..99a29894e2557 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -15605,6 +15605,10 @@ "enum_fields": [ { "name": "VULNERABILITY" + }, + { + "name": "NODE_VULNERABILITY", + "integer": 1 } ] }, @@ -15723,6 +15727,12 @@ "type": "VulnerabilityReportFilters", "oneof_parent": "filter" }, + { + "id": 15, + "name": "node_vuln_report_filters", + "type": "NodeVulnerabilityReportFilters", + "oneof_parent": "filter" + }, { "id": 6, "name": "scope_id", @@ -15960,6 +15970,28 @@ } ] }, + { + "name": "NodeVulnerabilityReportFilters", + "fields": [ + { + "id": 1, + "name": "all_vuln", + "type": "bool", + "oneof_parent": "cves_since" + }, + { + "id": 2, + "name": "access_scope_rules", + "type": "SimpleAccessScope.Rules", + "is_repeated": true + }, + { + "id": 3, + "name": "query", + "type": "string" + } + ] + }, { "name": "ViewBasedVulnerabilityReportFilters", "fields": [ @@ -16087,6 +16119,10 @@ "enum_fields": [ { "name": "VULNERABILITY" + }, + { + "name": "NODE_VULNERABILITY", + "integer": 1 } ] }, @@ -16184,6 +16220,12 @@ "type": "ViewBasedVulnerabilityReportFilters", "oneof_parent": "filter" }, + { + "id": 15, + "name": "node_vuln_report_filters", + "type": "NodeVulnerabilityReportFilters", + "oneof_parent": "filter" + }, { "id": 7, "name": "collection", diff --git a/proto/storage/report_configuration.proto b/proto/storage/report_configuration.proto index 27216ef197de4..5f9ee58950270 100644 --- a/proto/storage/report_configuration.proto +++ b/proto/storage/report_configuration.proto @@ -15,7 +15,8 @@ option java_package = "io.stackrox.proto.storage"; message ReportConfiguration { enum ReportType { - VULNERABILITY = 0; + VULNERABILITY = 0; // for historical reasons, this short form was used to mean "Image Vulnerability" + NODE_VULNERABILITY = 1; } string id = 1; // @gotags: sql:"pk" @@ -24,6 +25,7 @@ message ReportConfiguration { ReportType type = 4; // @gotags: search:"Report Type" oneof filter { VulnerabilityReportFilters vuln_report_filters = 5; + NodeVulnerabilityReportFilters node_vuln_report_filters = 15; } // Deprecated: V1 field. Use resource_scope instead. string scope_id = 6 [deprecated = true]; // @gotags: search:"Embedded Collection ID" @@ -116,6 +118,14 @@ enum EntityField { FIELD_ANNOTATION = 4; } +message NodeVulnerabilityReportFilters { + oneof cves_since { + bool all_vuln = 1; + } + repeated SimpleAccessScope.Rules access_scope_rules = 2; + string query = 3; +} + // filter for view based reports message ViewBasedVulnerabilityReportFilters { string query = 1; diff --git a/proto/storage/report_snapshot.proto b/proto/storage/report_snapshot.proto index e810fc23c67a5..98707994acb74 100644 --- a/proto/storage/report_snapshot.proto +++ b/proto/storage/report_snapshot.proto @@ -15,7 +15,8 @@ option java_package = "io.stackrox.proto.storage"; // vulnerability filters, notifiers, etc used to generate a report. It also stores the final status of the report job. message ReportSnapshot { enum ReportType { - VULNERABILITY = 0; + VULNERABILITY = 0; // for historical reasons, this short form was used to mean "Image Vulnerability" + NODE_VULNERABILITY = 1; } string report_id = 1; // @gotags: sql:"pk,type(uuid)" @@ -26,6 +27,7 @@ message ReportSnapshot { oneof filter { VulnerabilityReportFilters vuln_report_filters = 6; ViewBasedVulnerabilityReportFilters view_based_vuln_report_filters = 12; + NodeVulnerabilityReportFilters node_vuln_report_filters = 15; } CollectionSnapshot collection = 7; storage.Schedule schedule = 8; From b9bcd3aa6a8e8586c37e6c815dd7bb6bfef407cd Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Wed, 19 Aug 2026 08:57:38 +0000 Subject: [PATCH 002/638] ROX-36114,ROX-36209: retry flaky registry tests (#22231) Co-authored-by: Claude Opus 4.6 --- .../docker/docker_integration_test.go | 33 ++++++++++++++++--- pkg/registries/quay/quay_integration_test.go | 22 ++++++------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/pkg/registries/docker/docker_integration_test.go b/pkg/registries/docker/docker_integration_test.go index 119b6ce2fa1e2..4c7d2c8a0048d 100644 --- a/pkg/registries/docker/docker_integration_test.go +++ b/pkg/registries/docker/docker_integration_test.go @@ -7,6 +7,7 @@ import ( "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/registries/types" + "github.com/stackrox/rox/pkg/retry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,8 +34,16 @@ func TestGetMetadataIntegration(t *testing.T) { Tag: "1.14.8", }, } - _, err = dockerHubClient.Metadata(&image) - require.Nil(t, err) + err = retry.WithRetry(func() error { + _, retryErr := dockerHubClient.Metadata(&image) + return retryErr + }, retry.Tries(3), + retry.WithExponentialBackoff(), + retry.OnFailedAttempts(func(err error) { + t.Logf("retrying: %v", err) + }), + ) + require.NoError(t, err) // Make sure that request and histogram metrics but no timeouts have been recorded. assert.NotEmpty(t, metricsHandler.TestCollectRequestCounter(t)) @@ -64,7 +73,15 @@ func TestOCIImageIndexManifest(t *testing.T) { }, } - _, err = gcrClient.Metadata(&image) + err = retry.WithRetry(func() error { + _, retryErr := gcrClient.Metadata(&image) + return retryErr + }, retry.Tries(3), + retry.WithExponentialBackoff(), + retry.OnFailedAttempts(func(err error) { + t.Logf("retrying: %v", err) + }), + ) require.NoError(t, err) } @@ -90,6 +107,14 @@ func TestOCIImageIndexManifestWithoutManifestCall(t *testing.T) { }, } - _, err = gcrClient.Metadata(&image) + err = retry.WithRetry(func() error { + _, retryErr := gcrClient.Metadata(&image) + return retryErr + }, retry.Tries(3), + retry.WithExponentialBackoff(), + retry.OnFailedAttempts(func(err error) { + t.Logf("retrying: %v", err) + }), + ) require.NoError(t, err) } diff --git a/pkg/registries/quay/quay_integration_test.go b/pkg/registries/quay/quay_integration_test.go index 3b8d66b75cc08..db4ed50111c56 100644 --- a/pkg/registries/quay/quay_integration_test.go +++ b/pkg/registries/quay/quay_integration_test.go @@ -3,10 +3,10 @@ package quay import ( - "strings" "testing" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/retry" "github.com/stretchr/testify/assert" ) @@ -30,15 +30,13 @@ func TestQuay(t *testing.T) { q, err := newRegistry(integration, false, nil) assert.NoError(t, err) - assert.NoError(t, filterOkErrors(q.Test())) -} - -func filterOkErrors(err error) error { - if err != nil && - (strings.Contains(err.Error(), "EOF") || - strings.Contains(err.Error(), "status=502")) { - // Ignore failures that can indicate quay.io outage - return nil - } - return err + err = retry.WithRetry(func() error { + return q.Test() + }, retry.Tries(3), + retry.WithExponentialBackoff(), + retry.OnFailedAttempts(func(err error) { + t.Logf("retrying: %v", err) + }), + ) + assert.NoError(t, err) } From 45ea16f4c330f20901853aa4279e86114175a9b3 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:37:22 +0200 Subject: [PATCH 003/638] chore(ci): Improve and simplify GHA E2E workflows (#22024) --- .github/workflows/e2e-byodb-tests.yaml | 12 +----- ....yaml => e2e-db-backup-restore-tests.yaml} | 18 ++++----- .github/workflows/e2e-dispatch.yaml | 10 ++--- .github/workflows/e2e-gke-upgrade-tests.yaml | 39 +++++++------------ .github/workflows/e2e-nongroovy-tests.yaml | 10 +---- 5 files changed, 32 insertions(+), 57 deletions(-) rename .github/workflows/{e2e-db-backup-restore-test.yaml => e2e-db-backup-restore-tests.yaml} (93%) diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index de83b6151f835..ce3fba26ffa7f 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -33,7 +33,6 @@ jobs: env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - USE_GKE_GCLOUD_AUTH_PLUGIN: "True" GCP_SERVICE_ACCOUNT_STACKROX_CI: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} CI_JOB_NAME: gke-byodb-qa-e2e-tests ARTIFACT_DIR: ./junit-reports @@ -78,13 +77,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster byodb - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster byodb + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Build roxctl run: | @@ -120,8 +114,6 @@ jobs: QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} - AWS_S3_BACKUP_TEST_BUCKET_NAME: ${{ secrets.AWS_S3_BACKUP_TEST_BUCKET_NAME }} - AWS_S3_BACKUP_TEST_BUCKET_REGION: ${{ secrets.AWS_S3_BACKUP_TEST_BUCKET_REGION || 'us-east-2' }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_ECR_REGISTRY_REGION: ${{ secrets.AWS_ECR_REGISTRY_REGION }} diff --git a/.github/workflows/e2e-db-backup-restore-test.yaml b/.github/workflows/e2e-db-backup-restore-tests.yaml similarity index 93% rename from .github/workflows/e2e-db-backup-restore-test.yaml rename to .github/workflows/e2e-db-backup-restore-tests.yaml index 18287a9003361..a78f2692d8fb9 100644 --- a/.github/workflows/e2e-db-backup-restore-test.yaml +++ b/.github/workflows/e2e-db-backup-restore-tests.yaml @@ -1,4 +1,4 @@ -name: e2e-db-backup-restore-test +name: e2e-db-backup-restore-tests on: workflow_call: @@ -27,13 +27,13 @@ defaults: shell: bash jobs: - gke-db-backup-restore-test: + gke-db-backup-restore-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run runs-on: ubuntu-latest env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - CI_JOB_NAME: gke-db-backup-restore-test + CI_JOB_NAME: gke-db-backup-restore-tests ARTIFACT_DIR: ./junit-reports LOAD_BALANCER: lb ROX_SCANNER_V4: "false" @@ -66,13 +66,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster dbbr - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster dbbr + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Build roxctl # The test uses roxctl for central backup/restore operations @@ -87,6 +82,9 @@ jobs: kubectl get nodes scripts/ci/gke.sh wait_for_cluster + - name: Create artifact directory + run: mkdir -p "$ARTIFACT_DIR" + - name: Run pre-test shell: python env: diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index 3261dac6988e2..dffba0084de15 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -92,7 +92,7 @@ jobs: jobs = { job: should_run(job) for job in [ 'e2e-byodb-tests', - 'e2e-db-backup-restore-test', + 'e2e-db-backup-restore-tests', 'e2e-gke-upgrade-tests', 'e2e-nongroovy-tests', ]} @@ -165,15 +165,15 @@ jobs: group: e2e-byodb-tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true - e2e-db-backup-restore-test: + e2e-db-backup-restore-tests: needs: [ wait-for-images, should-dispatch ] - uses: ./.github/workflows/e2e-db-backup-restore-test.yaml + uses: ./.github/workflows/e2e-db-backup-restore-tests.yaml with: tag: ${{ needs.wait-for-images.outputs.tag }} - should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-db-backup-restore-test }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-db-backup-restore-tests }} secrets: inherit concurrency: - group: e2e-db-backup-restore-test-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: e2e-db-backup-restore-tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true e2e-gke-upgrade-tests: diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index d08adbfbb61d9..b9c25c7ae4950 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -35,7 +35,6 @@ jobs: env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - USE_GKE_GCLOUD_AUTH_PLUGIN: "True" # Not making CI_JOB_NAME match *gke-upgrade-tests because JUnit reporting will be broken since the former # gke-upgrade-tests expect all sensor, central and postgres tests to run together in a single sequence. CI_JOB_NAME: gke-upgrade-tests-central @@ -63,13 +62,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster cent-up - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster cent-up + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Docker login to Quay.io env: @@ -92,6 +86,9 @@ jobs: kubectl get nodes scripts/ci/gke.sh wait_for_cluster + - name: Create artifact directory + run: mkdir -p "$ARTIFACT_DIR" + - name: Run pre-test shell: python env: @@ -183,7 +180,6 @@ jobs: env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - USE_GKE_GCLOUD_AUTH_PLUGIN: "True" # Not making CI_JOB_NAME match *gke-upgrade-tests because JUnit reporting will be broken since the former # gke-upgrade-tests expect all sensor, central and postgres tests to run together in a single sequence. CI_JOB_NAME: gke-upgrade-tests-postgres @@ -211,13 +207,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster pg-up - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster pg-up + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Docker login to Quay.io env: @@ -240,6 +231,9 @@ jobs: kubectl get nodes scripts/ci/gke.sh wait_for_cluster + - name: Create artifact directory + run: mkdir -p "$ARTIFACT_DIR" + - name: Run pre-test shell: python env: @@ -330,7 +324,6 @@ jobs: env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - USE_GKE_GCLOUD_AUTH_PLUGIN: "True" # Not making CI_JOB_NAME match *gke-upgrade-tests because JUnit reporting will be broken since the former # gke-upgrade-tests expect all sensor, central and postgres tests to run together in a single sequence. CI_JOB_NAME: gke-upgrade-tests-sensor @@ -358,13 +351,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster sens-up - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster sens-up + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Docker login to Quay.io env: @@ -387,6 +375,9 @@ jobs: kubectl get nodes scripts/ci/gke.sh wait_for_cluster + - name: Create artifact directory + run: mkdir -p "$ARTIFACT_DIR" + - name: Run pre-test shell: python env: diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index 794c95bbde70c..5122b4b80aba2 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -33,7 +33,6 @@ jobs: env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} - USE_GKE_GCLOUD_AUTH_PLUGIN: "True" GCP_SERVICE_ACCOUNT_STACKROX_CI: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} CI_JOB_NAME: gke-nongroovy-e2e-tests ARTIFACT_DIR: ./junit-reports @@ -80,13 +79,8 @@ jobs: GKE_SPOT: "false" timeout-minutes: 20 run: | - source scripts/ci/gke.sh - provision_gke_cluster nongroovy - { - echo "CLUSTER_NAME=${CLUSTER_NAME}" - echo "ZONE=${ZONE}" - echo "KUBECONFIG=${HOME}/.kube/config" - } >> "$GITHUB_ENV" + scripts/ci/gke.sh provision_gke_cluster nongroovy + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Build roxctl # roxctl is used for various CLI e2e tests (token-file, authz-trace, istio-support, etc.) From 8360e01d3b08dbb8f6021d4a29d5ee6dd63bd102 Mon Sep 17 00:00:00 2001 From: Vlad Bologa Date: Wed, 19 Aug 2026 13:22:28 +0200 Subject: [PATCH 004/638] docs: update TLS context README for agents (#22349) --- pkg/mtls/README.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/pkg/mtls/README.md b/pkg/mtls/README.md index fd669de21665f..f9045e81f8f0a 100644 --- a/pkg/mtls/README.md +++ b/pkg/mtls/README.md @@ -1,6 +1,13 @@ # TLS certificates in StackRox -This document covers **StackRox internal CA mTLS** only. +This document covers TLS certificate management in StackRox, primarily the internal CA and mTLS. + +> **IMPORTANT — Rules for new TLS code:** +> +> 1. Changes that affect the TLS architecture MUST be reflected in this README. +> 2. New servers and client connections MUST hot-reload certificates (use +> `certwatch` or `GetCertificate`/`GetClientCertificate` callbacks). Do NOT +> add process-lifetime caches for leaf certs. ## Architecture @@ -8,7 +15,7 @@ This document covers **StackRox internal CA mTLS** only. from the client TLS certificate for RPC authorization. - Exceptions (TLS but not mTLS): Postgres connections, config-controller. - Internal CA: self-signed, 5-year validity, stored in `central-tls` secret. - Only Central and the Operator have the CA private key. + Only Central and the Operator have access to the CA private key. - Leaf certs: 1-year validity, per-service secrets. SANs match service DNS names. - Central side: `-tls` (e.g. `central-tls`, `scanner-tls`, `central-db-tls`) - Secured Cluster side: `tls-cert-` (e.g. `tls-cert-sensor`, `tls-cert-collector`) @@ -54,43 +61,38 @@ This document covers **StackRox internal CA mTLS** only. ## Certificate caching -The following certificates are currently known to be cached at start-up and not reloaded: - -- CA material (`CACert()`, `SecondaryCACert()`, `CAForSigning()`, etc.) in `pkg/mtls/crypto.go`: `sync.Once`, never refreshed. Intentional — the Operator restarts all pods on CA change. +- CA certificates are cached at start-up and not reloaded (`CACert()`, `SecondaryCACert()`, `CAForSigning()`, etc.) in `pkg/mtls/crypto.go`: `sync.Once`. The Operator restarts all pods on CA change. Possible improvement: hot reloading CAs would reduce downtime due to pods restarts (this happens only every 1-3 years though). ## Certificate management — who manages what -- **Central side**: the Operator manages all TLS secrets, creates the CA, - issues leaf certs, renews them at half validity, and handles CA rotation. -- **Secured Cluster side**: Sensor requests new certs from Central via the - cert refresh API and writes them to local Kubernetes secrets. During CA - rotation, Sensor and the Operator work together: Sensor writes both CAs - (learned from Central) into a CA bundle ConfigMap (`tls-ca-bundle`), and the - Operator watches it to update the `caBundle` field on the admission - controller's `ValidatingWebhookConfiguration`. This is why full CA rotation - requires the Operator on the Secured Cluster side. +- **Central side**: the Operator manages all TLS secrets, creates the CA, issues leaf certs, renews them at half validity, and handles CA rotation. +- **Secured Cluster side**: Sensor requests new certs from Central via the cert refresh API and writes them to local Kubernetes secrets. During CA rotation, Sensor and the Operator work together: Sensor writes both CAs (learned from Central) into a CA bundle ConfigMap (`tls-ca-bundle`), and the Operator watches it to update the `caBundle` field on the admission controller's `ValidatingWebhookConfiguration`. This is why full CA rotation requires the Operator also on the Secured Cluster side. ## CA rotation -- Operator-only feature (since 4.9). Phases: AddSecondary (year 3), PromoteSecondary (year 4), DeleteSecondary (year 5). -- Dual-CA awareness is limited to: Operator, Central, Sensor, admission controller ValidatingWebhookConfiguration. -- All other intra-cluster components only know a single CA. The Operator restarts them on CA change via a pod template annotation hash that includes the CA PEM (`confighash.ComputeCAHash`). -- This was deliberate: the CA rotation problem is primarily Central↔Sensor (cross-cluster). Intra-cluster services can be restarted together. +- Operator-only feature (since 4.9). Orchestrated by the Operator on the Central side through phases: AddSecondary (year 3), PromoteSecondary (year 4), DeleteSecondary (year 5). +- Propagated to Secured Clusters via the cert refresh API and the TLSChallenge endpoint. +- The original design was for only Sensor and Central (the deployment) to be dual-CA aware. In practice, various corner cases led to most deployments gaining dual-CA awareness. +- Current state: Operator, all Central CR components (except legacy Scanner V2), Sensor, and admission controller on the SecuredCluster CR side are dual-CA aware. +- The Operator restarts all Central and SecuredCluster CR pods when a CA change occurs. ### Which CA signs what - Central services: always signed by primary CA. - Secured Cluster services: signed by newer CA if Sensor supports rotation; by Sensor's trusted CA (via fingerprint) for Helm clusters; by primary CA otherwise. -### Helm-managed Secured Clusters (partial CA rotation support) +### Legacy Helm-managed Secured Clusters (partial CA rotation support) - Can connect to a rotated Central (Sensor discovers new CA via TLSChallenge). -- Cannot restart pods on CA change (no Operator). -- Cannot update ValidatingWebhookConfiguration caBundle (no Operator to watch the CA bundle ConfigMap). +- However cannot update CA, because they: + - cannot restart pods on CA change (no Operator) + - cannot update ValidatingWebhookConfiguration caBundle (no Operator to watch the CA bundle ConfigMap). ## TLS challenge endpoint (/v1/tls-challenge) -- Unauthenticated endpoint. Sensor sends challenge token, Central returns signed TrustInfo. +- Unauthenticated endpoint, called by Sensor before establishing its main mTLS connection to Central, to bootstrap TLS trust. Sensor sends challenge token, Central returns signed TrustInfo. +- Original purpose: allows Sensor to discover Central's internal CA cert when Sensor cannot validate the certificate that Central presents, e.g when Central uses a custom default certificate. (This endpoint does not help when Central cannot receive Sensor's client certificate.) +- Extended in 4.9 for CA rotation: response includes both primary and secondary cert chains. This is how Sensor (on a different cluster) discovers the new CA — it verifies the response using the CA it already trusts, then adds the other CA to its trust pool. - Response includes: primary cert chain, secondary cert chain (if present), additional CAs, default TLS leaf cert. - Signed with both primary and secondary leaf certs. Sensor verifies one signature and trusts all certs in the response (trust delegation). - Secondary leaf cert: issued in memory from secondary CA with ~3-hour validity, auto-renewed before expiry. From 136dca7d790411b3344a80249255c353495d5992 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:20:47 +0200 Subject: [PATCH 005/638] ROX-35959: Fix tests for release case (#22353) --- .../compatibility_test.go | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/pkg/version/versioncompatibility/compatibility_test.go b/pkg/version/versioncompatibility/compatibility_test.go index 107f2149a522b..5cc8e4a03bbb5 100644 --- a/pkg/version/versioncompatibility/compatibility_test.go +++ b/pkg/version/versioncompatibility/compatibility_test.go @@ -3,6 +3,7 @@ package versioncompatibility import ( "testing" + "github.com/stackrox/rox/pkg/buildinfo" "github.com/stackrox/rox/pkg/version/productstreams" "github.com/stackrox/rox/pkg/version/testutils" "github.com/stretchr/testify/assert" @@ -348,20 +349,41 @@ func TestClassifyVersion(t *testing.T) { assert.Equal(t, CompatibleBehind, compat) } -func TestCompatibleVersionsPanicsOnInvalidVersion(t *testing.T) { +func TestCompatibleVersionsWhenInvalidVersion(t *testing.T) { testutils.SetMainVersion(t, "invalid") - assert.Panics(t, func() { _, _ = CompatibleVersions() }) + + if buildinfo.ReleaseBuild { + v, err := CompatibleVersions() + assert.Empty(t, v) + assert.Error(t, err) + } else { + assert.Panics(t, func() { _, _ = CompatibleVersions() }) + } } -func TestClassifyVersionPanicsOnInvalidVersion(t *testing.T) { +func TestClassifyVersionWhenInvalidVersion(t *testing.T) { testutils.SetMainVersion(t, "invalid") - assert.Panics(t, func() { _, _ = ClassifyVersion(xy(4, 9)) }) + + if buildinfo.ReleaseBuild { + c, err := ClassifyVersion(xy(4, 9)) + assert.Equal(t, Unknown, c) + assert.Error(t, err) + } else { + assert.Panics(t, func() { _, _ = ClassifyVersion(xy(4, 9)) }) + } } -func TestCompatibleVersionsPanicsOnMissingBumpData(t *testing.T) { +func TestCompatibleVersionsWhenMissingBumpData(t *testing.T) { productstreams.OverrideBumpsForTesting(t, `bumps: []`) testutils.SetMainVersion(t, "4.2.0-testing") - assert.Panics(t, func() { _, _ = CompatibleVersions() }) + + if buildinfo.ReleaseBuild { + v, err := CompatibleVersions() + assert.Empty(t, v) + assert.Error(t, err) + } else { + assert.Panics(t, func() { _, _ = CompatibleVersions() }) + } } func overrideTestBumps(t *testing.T) { From 6ca3bf0a00fe4eeeffe96a3e55bf9412c54ff7a8 Mon Sep 17 00:00:00 2001 From: Cong Du Date: Wed, 19 Aug 2026 08:21:53 -0700 Subject: [PATCH 006/638] ROX-36405: use camelCase tags for labels in policy CR export (#22334) Co-authored-by: Claude Sonnet 4.6 (1M context) --- central/policy/customresource/custom_resource_test.go | 4 ++++ central/policy/customresource/policy.go | 4 ++-- central/policy/customresource/testdata/custom_resource.yaml | 6 ++++++ generated/storage/scope.pb.go | 4 ++-- proto/storage/scope.proto | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/central/policy/customresource/custom_resource_test.go b/central/policy/customresource/custom_resource_test.go index a2da8636955ff..94cf6c6f9e545 100644 --- a/central/policy/customresource/custom_resource_test.go +++ b/central/policy/customresource/custom_resource_test.go @@ -74,6 +74,10 @@ func getTestPolicy() *storage.Policy { }, }, } + p.Scope = append(p.Scope, &storage.Scope{ + ClusterLabel: &storage.Scope_Label{Key: "env", Value: "prod"}, + NamespaceLabel: &storage.Scope_Label{Key: "team", Value: "platform"}, + }) return p } diff --git a/central/policy/customresource/policy.go b/central/policy/customresource/policy.go index 3da76898b67b2..233b500a5c66c 100644 --- a/central/policy/customresource/policy.go +++ b/central/policy/customresource/policy.go @@ -12,8 +12,8 @@ type Scope struct { Cluster string `yaml:",omitempty"` Namespace string `yaml:",omitempty"` Label *storage.Scope_Label `yaml:",omitempty"` - ClusterLabel *storage.Scope_Label `yaml:",omitempty"` - NamespaceLabel *storage.Scope_Label `yaml:",omitempty"` + ClusterLabel *storage.Scope_Label `yaml:"clusterLabel,omitempty"` + NamespaceLabel *storage.Scope_Label `yaml:"namespaceLabel,omitempty"` } // convertScope Converts storage.Scope to *Scope diff --git a/central/policy/customresource/testdata/custom_resource.yaml b/central/policy/customresource/testdata/custom_resource.yaml index ceac71f4f2acb..4de610ec37a80 100644 --- a/central/policy/customresource/testdata/custom_resource.yaml +++ b/central/policy/customresource/testdata/custom_resource.yaml @@ -42,6 +42,12 @@ spec: label: key: com.docker.stack.namespace value: prevent + - clusterLabel: + key: env + value: prod + namespaceLabel: + key: team + value: platform severity: LOW_SEVERITY enforcementActions: - FAIL_BUILD_ENFORCEMENT diff --git a/generated/storage/scope.pb.go b/generated/storage/scope.pb.go index 5810972bc89fe..1b16ac4a200e5 100644 --- a/generated/storage/scope.pb.go +++ b/generated/storage/scope.pb.go @@ -26,8 +26,8 @@ type Scope struct { Cluster string `protobuf:"bytes,1,opt,name=cluster,proto3" json:"cluster,omitempty" crYaml:",omitempty"` // @gotags: crYaml:",omitempty"` Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty" crYaml:",omitempty"` // @gotags: crYaml:",omitempty"` Label *Scope_Label `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty" crYaml:",omitempty"` // Deployment label. @gotags: crYaml:",omitempty"` - ClusterLabel *Scope_Label `protobuf:"bytes,4,opt,name=cluster_label,json=clusterLabel,proto3" json:"cluster_label,omitempty" crYaml:",omitempty"` // @gotags: crYaml:",omitempty"` - NamespaceLabel *Scope_Label `protobuf:"bytes,5,opt,name=namespace_label,json=namespaceLabel,proto3" json:"namespace_label,omitempty" crYaml:",omitempty"` // @gotags: crYaml:",omitempty"` + ClusterLabel *Scope_Label `protobuf:"bytes,4,opt,name=cluster_label,json=clusterLabel,proto3" json:"cluster_label,omitempty" crYaml:"clusterLabel,omitempty"` // @gotags: crYaml:"clusterLabel,omitempty"` + NamespaceLabel *Scope_Label `protobuf:"bytes,5,opt,name=namespace_label,json=namespaceLabel,proto3" json:"namespace_label,omitempty" crYaml:"namespaceLabel,omitempty"` // @gotags: crYaml:"namespaceLabel,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } diff --git a/proto/storage/scope.proto b/proto/storage/scope.proto index f857c0c72ede4..bbde8c1da1bae 100644 --- a/proto/storage/scope.proto +++ b/proto/storage/scope.proto @@ -13,6 +13,6 @@ message Scope { string value = 2; } Label label = 3; // Deployment label. @gotags: crYaml:",omitempty"` - Label cluster_label = 4; // @gotags: crYaml:",omitempty"` - Label namespace_label = 5; // @gotags: crYaml:",omitempty"` + Label cluster_label = 4; // @gotags: crYaml:"clusterLabel,omitempty"` + Label namespace_label = 5; // @gotags: crYaml:"namespaceLabel,omitempty"` } From 292c6fa595b4bb9c18021647aa184a291759ef72 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:41:09 +0200 Subject: [PATCH 007/638] ROX-36285: retry empty API token generation in token-file e2e (#22366) --- tests/roxctl/token-file.sh | 68 ++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/tests/roxctl/token-file.sh b/tests/roxctl/token-file.sh index 96651e7a76f92..1962935b62896 100755 --- a/tests/roxctl/token-file.sh +++ b/tests/roxctl/token-file.sh @@ -13,22 +13,68 @@ eecho() { echo "$@" >&2 } +die() { + eecho "$@" + exit 1 +} + curl_cfg() { # Use built-in echo to not expose $2 in the process list. echo -n "$1 = \"${2//[\"\\]/\\&}\"" } -# Retrieve API token +# token_file_has_token rejects jq's "null" for a missing .token field. +token_file_has_token() { + local token + token="$(cat "$TOKEN_FILE")" + [ -n "$token" ] && [ "$token" != "null" ] +} + +request_api_token() { + # wait_for_api_token owns retries; curl --retry would multiply generate POSTs. + curl -k -f \ + --config <(curl_cfg user "admin:$ROX_ADMIN_PASSWORD") \ + -d '{"name": "test", "role": "Admin"}' \ + --connect-timeout 10 \ + --max-time 30 \ + "https://$API_ENDPOINT/v1/apitokens/generate" +} + +write_token_file() { + local response + response="$(request_api_token)" || return 1 + printf '%s' "$response" | jq -r .token > "$TOKEN_FILE" || return 1 + token_file_has_token +} + +# sleep_before_next_attempt waits 1s, 2s, 4s, then 8s after attempts 1-4. +sleep_before_next_attempt() { + case "$1" in + 1) sleep 1 ;; + 2) sleep 2 ;; + 3) sleep 4 ;; + 4) sleep 8 ;; + esac +} + +wait_for_api_token() { + local attempt=1 + local max_attempts=5 + + while [ "$attempt" -le "$max_attempts" ]; do + if write_token_file; then + return 0 + fi + if [ "$attempt" -eq "$max_attempts" ]; then + die "Failed to retrieve API token after ${max_attempts} attempts: token file is empty" + fi + eecho "Token generate attempt ${attempt}/${max_attempts} failed, retrying..." + sleep_before_next_attempt "$attempt" + attempt=$((attempt + 1)) + done +} + TOKEN_FILE=$(mktemp) -curl -k -f \ - --config <(curl_cfg user "admin:$ROX_ADMIN_PASSWORD") \ - -d '{"name": "test", "role": "Admin"}' \ - --retry 5 \ - --retry-connrefused \ - "https://$API_ENDPOINT/v1/apitokens/generate" \ - | jq -r .token \ - > "$TOKEN_FILE" - -[ -n "$(cat "$TOKEN_FILE")" ] +wait_for_api_token test_roxctl_cmd() { echo "Testing command: roxctl " "$@" From 91057afbfdd421732cbe663877a55b24a938b365 Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:14:49 -0700 Subject: [PATCH 008/638] ROX-36422: Add ScanImageInternalForAdmission RPC (#22287) --- central/image/service/service_impl.go | 29 ++- central/image/service/service_impl_test.go | 187 ++++++++++++++++++++ generated/api/v1/image_service.pb.go | 58 +++--- generated/api/v1/image_service_grpc.pb.go | 48 +++++ proto/api/v1/image_service.proto | 7 + roxctl/central/export/images/images_test.go | 4 + 6 files changed, 300 insertions(+), 33 deletions(-) diff --git a/central/image/service/service_impl.go b/central/image/service/service_impl.go index 2a6fd35804413..a98dd40cf79ca 100644 --- a/central/image/service/service_impl.go +++ b/central/image/service/service_impl.go @@ -71,6 +71,7 @@ var ( }, or.SensorOr(idcheck.AdmissionControlOnly()): { v1.ImageService_ScanImageInternal_FullMethodName, + v1.ImageService_ScanImageInternalForAdmission_FullMethodName, }, idcheck.SensorsOnly(): { v1.ImageService_GetImageVulnerabilitiesInternal_FullMethodName, @@ -302,8 +303,24 @@ func (s *serviceImpl) saveImageV2(img *storage.ImageV2) error { // ScanImageInternal handles an image request from Sensor and Admission Controller. func (s *serviceImpl) ScanImageInternal(ctx context.Context, request *v1.ScanImageInternalRequest) (*v1.ScanImageInternalResponse, error) { + return s.scanImageInternalWithOpt(ctx, request, enricher.ForceRefetch) +} + +// ScanImageInternalForAdmission handles admission-controller image requests. +// For tag-only images it refreshes registry metadata but reuses an existing +// scan from Central's DB when the resolved digest is already known, avoiding +// a redundant Scanner round-trip. For digest-based images it behaves +// identically to ScanImageInternal. +func (s *serviceImpl) ScanImageInternalForAdmission(ctx context.Context, request *v1.ScanImageInternalRequest) (*v1.ScanImageInternalResponse, error) { + return s.scanImageInternalWithOpt(ctx, request, enricher.ForceRefetchMetadataOnly) +} + +// scanImageInternalWithOpt is the shared implementation for ScanImageInternal +// and ScanImageInternalForAdmission. tagOnlyFetchOpt controls the enricher +// FetchOption used when the request has no digest (tag-only image). +func (s *serviceImpl) scanImageInternalWithOpt(ctx context.Context, request *v1.ScanImageInternalRequest, tagOnlyFetchOpt enricher.FetchOption) (*v1.ScanImageInternalResponse, error) { if features.FlattenImageData.Enabled() { - return s.scanImageV2Internal(ctx, request) + return s.scanImageV2InternalWithOpt(ctx, request, tagOnlyFetchOpt) } if err := s.acquireScanSemaphore(ctx); err != nil { @@ -380,8 +397,8 @@ func (s *serviceImpl) ScanImageInternal(ctx context.Context, request *v1.ScanIma fetchOpt = enricher.UseCachesIfPossible if request.GetCachedOnly() { fetchOpt = enricher.NoExternalMetadata - } else if imgID == "" { // If no ID, then don't use caches as they could return stale data. - fetchOpt = enricher.ForceRefetch + } else if imgID == "" { + fetchOpt = tagOnlyFetchOpt } img = types.ToImage(request.GetImage()) } @@ -400,7 +417,7 @@ func (s *serviceImpl) ScanImageInternal(ctx context.Context, request *v1.ScanIma return internalScanRespFromImage(img), nil } -func (s *serviceImpl) scanImageV2Internal(ctx context.Context, request *v1.ScanImageInternalRequest) (*v1.ScanImageInternalResponse, error) { +func (s *serviceImpl) scanImageV2InternalWithOpt(ctx context.Context, request *v1.ScanImageInternalRequest, tagOnlyFetchOpt enricher.FetchOption) (*v1.ScanImageInternalResponse, error) { imgID := request.GetImage().GetIdV2() if imgID == "" { imgID = utils.NewImageV2ID(request.GetImage().GetName(), request.GetImage().GetId()) @@ -465,8 +482,8 @@ func (s *serviceImpl) scanImageV2Internal(ctx context.Context, request *v1.ScanI fetchOpt = enricher.UseCachesIfPossible if request.GetCachedOnly() { fetchOpt = enricher.NoExternalMetadata - } else if imgID == "" { // If no ID, then don't use caches as they could return stale data. - fetchOpt = enricher.ForceRefetch + } else if imgID == "" { + fetchOpt = tagOnlyFetchOpt } imgV2 = types.ToImageV2(request.GetImage()) if imgV2.GetId() == "" { diff --git a/central/image/service/service_impl_test.go b/central/image/service/service_impl_test.go index 6ec5c9e6beb58..023538816f75d 100644 --- a/central/image/service/service_impl_test.go +++ b/central/image/service/service_impl_test.go @@ -405,6 +405,193 @@ func TestResetClusterLocal(t *testing.T) { } } +// TestScanImageInternalForAdmission_FetchOpt verifies that the admission RPC +// uses ForceRefetchMetadataOnly for tag-only requests and behaves identically +// to ScanImageInternal for digest-based requests. +func TestScanImageInternalForAdmission_FetchOpt(t *testing.T) { + name, _, err := utils.GenerateImageNameFromString("reg.invalid/some/image:latest") + require.NoError(t, err) + + names := []*storage.ImageName{name} + + tcs := map[string]struct { + request *v1.ScanImageInternalRequest + existingImg *storage.Image + existingImgV2 *storage.ImageV2 + expectedFetchOpt enricher.FetchOption + expectEnrich bool + }{ + "tag-only uses ForceRefetchMetadataOnly": { + request: &v1.ScanImageInternalRequest{ + Image: &storage.ContainerImage{Name: name}, + }, + expectedFetchOpt: enricher.ForceRefetchMetadataOnly, + expectEnrich: true, + }, + "digest request, image in DB, skips enricher entirely": { + request: &v1.ScanImageInternalRequest{ + Image: &storage.ContainerImage{Id: "sha256:abc", Name: name}, + }, + existingImg: &storage.Image{ + Id: "sha256:abc", Name: name, Names: names, + Scan: &storage.ImageScan{}, + }, + existingImgV2: &storage.ImageV2{ + Id: utils.NewImageV2ID(name, "sha256:abc"), Digest: "sha256:abc", + Name: name, Scan: &storage.ImageScan{}, + }, + expectEnrich: false, + }, + "digest request, image NOT in DB, uses UseCachesIfPossible": { + request: &v1.ScanImageInternalRequest{ + Image: &storage.ContainerImage{Id: "sha256:abc", Name: name}, + }, + expectedFetchOpt: enricher.UseCachesIfPossible, + expectEnrich: true, + }, + } + + for desc, tc := range tcs { + t.Run(desc, func(t *testing.T) { + ctrl := gomock.NewController(t) + riskManagerMock := riskManagerMocks.NewMockManager(ctrl) + + var s *serviceImpl + if features.FlattenImageData.Enabled() { + imageEnricherV2Mock := enricherMocks.NewMockImageEnricherV2(ctrl) + if tc.expectEnrich { + fetchOptMatcher := gomock.Cond(func(eCtx enricher.EnrichmentContext) bool { + return eCtx.FetchOpt == tc.expectedFetchOpt + }) + imageEnricherV2Mock.EXPECT(). + EnrichImage(gomock.Any(), fetchOptMatcher, gomock.Any()). + Return(enricher.EnrichmentResult{}, nil).Times(1) + } + + riskManagerMock.EXPECT(). + CalculateRiskAndUpsertImageV2(gomock.Any()). + Return(nil).AnyTimes() + + imageV2DSMock := imageV2DSMocks.NewMockDataStore(ctrl) + imageV2DSMock.EXPECT(). + GetImage(gomock.Any(), gomock.Any()). + Return(tc.existingImgV2, tc.existingImgV2 != nil, nil).AnyTimes() + imageV2DSMock.EXPECT(). + GetImageNames(gomock.Any(), gomock.Any()). + Return(names, nil).AnyTimes() + + connMgrMock := connMgrMocks.NewMockManager(ctrl) + connMgrMock.EXPECT().AllSensorsHaveCapability(gomock.Any()).AnyTimes().Return(false) + + s = &serviceImpl{ + internalScanSemaphore: semaphore.NewWeighted(int64(env.MaxParallelImageScanInternal.IntegerSetting())), + enricherV2: imageEnricherV2Mock, + datastoreV2: imageV2DSMock, + riskManager: riskManagerMock, + connManager: connMgrMock, + } + } else { + imageEnricherMock := enricherMocks.NewMockImageEnricher(ctrl) + if tc.expectEnrich { + fetchOptMatcher := gomock.Cond(func(eCtx enricher.EnrichmentContext) bool { + return eCtx.FetchOpt == tc.expectedFetchOpt + }) + imageEnricherMock.EXPECT(). + EnrichImage(gomock.Any(), fetchOptMatcher, gomock.Any()). + Return(enricher.EnrichmentResult{}, nil).Times(1) + } + + riskManagerMock.EXPECT(). + CalculateRiskAndUpsertImage(gomock.Any()). + Return(nil).AnyTimes() + + imageDSMock := imageDSMocks.NewMockDataStore(ctrl) + imageDSMock.EXPECT(). + GetImage(gomock.Any(), gomock.Any()). + Return(tc.existingImg, tc.existingImg != nil, nil).AnyTimes() + + s = &serviceImpl{ + internalScanSemaphore: semaphore.NewWeighted(int64(env.MaxParallelImageScanInternal.IntegerSetting())), + enricher: imageEnricherMock, + datastore: imageDSMock, + riskManager: riskManagerMock, + } + } + + resp, err := s.ScanImageInternalForAdmission(context.Background(), tc.request) + require.NoError(t, err) + assert.NotNil(t, resp) + }) + } +} + +// TestScanImageInternalForAdmission_DigestParity ensures the admission RPC +// produces the same result as ScanImageInternal for digest-based requests. +func TestScanImageInternalForAdmission_DigestParity(t *testing.T) { + name, _, err := utils.GenerateImageNameFromString("reg.invalid/some/image:latest") + require.NoError(t, err) + + names := []*storage.ImageName{name} + req := &v1.ScanImageInternalRequest{ + Image: &storage.ContainerImage{Id: "sha256:abc", Name: name}, + } + + curScan := &storage.ImageScan{ScanTime: timestamppb.New(time.Now())} + + buildService := func(ctrl *gomock.Controller) *serviceImpl { + riskManagerMock := riskManagerMocks.NewMockManager(ctrl) + if features.FlattenImageData.Enabled() { + existingImgV2 := &storage.ImageV2{ + Id: utils.NewImageV2ID(name, "sha256:abc"), Digest: "sha256:abc", + Name: name, Scan: curScan, + } + imageV2DSMock := imageV2DSMocks.NewMockDataStore(ctrl) + imageV2DSMock.EXPECT(). + GetImage(gomock.Any(), gomock.Any()). + Return(existingImgV2, true, nil).AnyTimes() + imageV2DSMock.EXPECT(). + GetImageNames(gomock.Any(), gomock.Any()). + Return(names, nil).AnyTimes() + + connMgrMock := connMgrMocks.NewMockManager(ctrl) + connMgrMock.EXPECT().AllSensorsHaveCapability(gomock.Any()).AnyTimes().Return(false) + + return &serviceImpl{ + internalScanSemaphore: semaphore.NewWeighted(int64(env.MaxParallelImageScanInternal.IntegerSetting())), + datastoreV2: imageV2DSMock, + riskManager: riskManagerMock, + connManager: connMgrMock, + } + } + existingImg := &storage.Image{ + Id: "sha256:abc", Name: name, Names: names, Scan: curScan, + } + imageDSMock := imageDSMocks.NewMockDataStore(ctrl) + imageDSMock.EXPECT(). + GetImage(gomock.Any(), gomock.Any()). + Return(existingImg, true, nil).AnyTimes() + + return &serviceImpl{ + internalScanSemaphore: semaphore.NewWeighted(int64(env.MaxParallelImageScanInternal.IntegerSetting())), + datastore: imageDSMock, + riskManager: riskManagerMock, + } + } + + ctrl1 := gomock.NewController(t) + ctrl2 := gomock.NewController(t) + + s1 := buildService(ctrl1) + s2 := buildService(ctrl2) + + resp1, err1 := s1.ScanImageInternal(context.Background(), req) + resp2, err2 := s2.ScanImageInternalForAdmission(context.Background(), req) + + require.NoError(t, err1) + require.NoError(t, err2) + protoassert.Equal(t, resp1.GetImage(), resp2.GetImage()) +} + // TestEnrichLocalImageInternal_ImageNames ensures that image names are // populated from the existing image in Central DB when the image // requires re-enrichment. (ie: when scan has expired) diff --git a/generated/api/v1/image_service.pb.go b/generated/api/v1/image_service.pb.go index faeb7f0d10492..5175ee8c5578b 100644 --- a/generated/api/v1/image_service.pb.go +++ b/generated/api/v1/image_service.pb.go @@ -1309,7 +1309,8 @@ const file_api_v1_image_service_proto_rawDesc = "" + "\atimeout\x18\x01 \x01(\x05R\atimeout\x12\x14\n" + "\x05query\x18\x02 \x01(\tR\x05query\";\n" + "\x13ExportImageResponse\x12$\n" + - "\x05image\x18\x01 \x01(\v2\x0e.storage.ImageR\x05image2\xce\t\n" + + "\x05image\x18\x01 \x01(\v2\x0e.storage.ImageR\x05image2\xac\n" + + "\n" + "\fImageService\x12H\n" + "\bGetImage\x12\x13.v1.GetImageRequest\x1a\x0e.storage.Image\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/images/{id}\x12M\n" + "\vCountImages\x12\f.v1.RawQuery\x1a\x17.v1.CountImagesResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/imagescount\x12F\n" + @@ -1317,7 +1318,8 @@ const file_api_v1_image_service_proto_rawDesc = "" + "ListImages\x12\f.v1.RawQuery\x1a\x16.v1.ListImagesResponse\"\x12\x82\xd3\xe4\x93\x02\f\x12\n" + "/v1/images\x12M\n" + "\tScanImage\x12\x14.v1.ScanImageRequest\x1a\x0e.storage.Image\"\x1a\x82\xd3\xe4\x93\x02\x14:\x01*\"\x0f/v1/images/scan\x12P\n" + - "\x11ScanImageInternal\x12\x1c.v1.ScanImageInternalRequest\x1a\x1d.v1.ScanImageInternalResponse\x12q\n" + + "\x11ScanImageInternal\x12\x1c.v1.ScanImageInternalRequest\x1a\x1d.v1.ScanImageInternalResponse\x12\\\n" + + "\x1dScanImageInternalForAdmission\x12\x1c.v1.ScanImageInternalRequest\x1a\x1d.v1.ScanImageInternalResponse\x12q\n" + "\x1fGetImageVulnerabilitiesInternal\x12*.v1.GetImageVulnerabilitiesInternalRequest\x1a\x1d.v1.ScanImageInternalResponse\"\x03\x88\x02\x01\x12^\n" + "\x18EnrichLocalImageInternal\x12#.v1.EnrichLocalImageInternalRequest\x1a\x1d.v1.ScanImageInternalResponse\x12T\n" + "\x1dUpdateLocalScanStatusInternal\x12(.v1.UpdateLocalScanStatusInternalRequest\x1a\t.v1.Empty\x12\\\n" + @@ -1406,31 +1408,33 @@ var file_api_v1_image_service_proto_depIdxs = []int32{ 31, // 21: v1.ImageService.ListImages:input_type -> v1.RawQuery 4, // 22: v1.ImageService.ScanImage:input_type -> v1.ScanImageRequest 5, // 23: v1.ImageService.ScanImageInternal:input_type -> v1.ScanImageInternalRequest - 7, // 24: v1.ImageService.GetImageVulnerabilitiesInternal:input_type -> v1.GetImageVulnerabilitiesInternalRequest - 8, // 25: v1.ImageService.EnrichLocalImageInternal:input_type -> v1.EnrichLocalImageInternalRequest - 9, // 26: v1.ImageService.UpdateLocalScanStatusInternal:input_type -> v1.UpdateLocalScanStatusInternalRequest - 33, // 27: v1.ImageService.InvalidateScanAndRegistryCaches:input_type -> v1.Empty - 10, // 28: v1.ImageService.DeleteImages:input_type -> v1.DeleteImagesRequest - 12, // 29: v1.ImageService.WatchImage:input_type -> v1.WatchImageRequest - 14, // 30: v1.ImageService.UnwatchImage:input_type -> v1.UnwatchImageRequest - 33, // 31: v1.ImageService.GetWatchedImages:input_type -> v1.Empty - 17, // 32: v1.ImageService.ExportImages:input_type -> v1.ExportImageRequest - 23, // 33: v1.ImageService.GetImage:output_type -> storage.Image - 3, // 34: v1.ImageService.CountImages:output_type -> v1.CountImagesResponse - 2, // 35: v1.ImageService.ListImages:output_type -> v1.ListImagesResponse - 23, // 36: v1.ImageService.ScanImage:output_type -> storage.Image - 6, // 37: v1.ImageService.ScanImageInternal:output_type -> v1.ScanImageInternalResponse - 6, // 38: v1.ImageService.GetImageVulnerabilitiesInternal:output_type -> v1.ScanImageInternalResponse - 6, // 39: v1.ImageService.EnrichLocalImageInternal:output_type -> v1.ScanImageInternalResponse - 33, // 40: v1.ImageService.UpdateLocalScanStatusInternal:output_type -> v1.Empty - 33, // 41: v1.ImageService.InvalidateScanAndRegistryCaches:output_type -> v1.Empty - 11, // 42: v1.ImageService.DeleteImages:output_type -> v1.DeleteImagesResponse - 13, // 43: v1.ImageService.WatchImage:output_type -> v1.WatchImageResponse - 33, // 44: v1.ImageService.UnwatchImage:output_type -> v1.Empty - 15, // 45: v1.ImageService.GetWatchedImages:output_type -> v1.GetWatchedImagesResponse - 18, // 46: v1.ImageService.ExportImages:output_type -> v1.ExportImageResponse - 33, // [33:47] is the sub-list for method output_type - 19, // [19:33] is the sub-list for method input_type + 5, // 24: v1.ImageService.ScanImageInternalForAdmission:input_type -> v1.ScanImageInternalRequest + 7, // 25: v1.ImageService.GetImageVulnerabilitiesInternal:input_type -> v1.GetImageVulnerabilitiesInternalRequest + 8, // 26: v1.ImageService.EnrichLocalImageInternal:input_type -> v1.EnrichLocalImageInternalRequest + 9, // 27: v1.ImageService.UpdateLocalScanStatusInternal:input_type -> v1.UpdateLocalScanStatusInternalRequest + 33, // 28: v1.ImageService.InvalidateScanAndRegistryCaches:input_type -> v1.Empty + 10, // 29: v1.ImageService.DeleteImages:input_type -> v1.DeleteImagesRequest + 12, // 30: v1.ImageService.WatchImage:input_type -> v1.WatchImageRequest + 14, // 31: v1.ImageService.UnwatchImage:input_type -> v1.UnwatchImageRequest + 33, // 32: v1.ImageService.GetWatchedImages:input_type -> v1.Empty + 17, // 33: v1.ImageService.ExportImages:input_type -> v1.ExportImageRequest + 23, // 34: v1.ImageService.GetImage:output_type -> storage.Image + 3, // 35: v1.ImageService.CountImages:output_type -> v1.CountImagesResponse + 2, // 36: v1.ImageService.ListImages:output_type -> v1.ListImagesResponse + 23, // 37: v1.ImageService.ScanImage:output_type -> storage.Image + 6, // 38: v1.ImageService.ScanImageInternal:output_type -> v1.ScanImageInternalResponse + 6, // 39: v1.ImageService.ScanImageInternalForAdmission:output_type -> v1.ScanImageInternalResponse + 6, // 40: v1.ImageService.GetImageVulnerabilitiesInternal:output_type -> v1.ScanImageInternalResponse + 6, // 41: v1.ImageService.EnrichLocalImageInternal:output_type -> v1.ScanImageInternalResponse + 33, // 42: v1.ImageService.UpdateLocalScanStatusInternal:output_type -> v1.Empty + 33, // 43: v1.ImageService.InvalidateScanAndRegistryCaches:output_type -> v1.Empty + 11, // 44: v1.ImageService.DeleteImages:output_type -> v1.DeleteImagesResponse + 13, // 45: v1.ImageService.WatchImage:output_type -> v1.WatchImageResponse + 33, // 46: v1.ImageService.UnwatchImage:output_type -> v1.Empty + 15, // 47: v1.ImageService.GetWatchedImages:output_type -> v1.GetWatchedImagesResponse + 18, // 48: v1.ImageService.ExportImages:output_type -> v1.ExportImageResponse + 34, // [34:49] is the sub-list for method output_type + 19, // [19:34] is the sub-list for method input_type 19, // [19:19] is the sub-list for extension type_name 19, // [19:19] is the sub-list for extension extendee 0, // [0:19] is the sub-list for field type_name diff --git a/generated/api/v1/image_service_grpc.pb.go b/generated/api/v1/image_service_grpc.pb.go index d509663dcd851..9f23fdcda8632 100644 --- a/generated/api/v1/image_service_grpc.pb.go +++ b/generated/api/v1/image_service_grpc.pb.go @@ -25,6 +25,7 @@ const ( ImageService_ListImages_FullMethodName = "/v1.ImageService/ListImages" ImageService_ScanImage_FullMethodName = "/v1.ImageService/ScanImage" ImageService_ScanImageInternal_FullMethodName = "/v1.ImageService/ScanImageInternal" + ImageService_ScanImageInternalForAdmission_FullMethodName = "/v1.ImageService/ScanImageInternalForAdmission" ImageService_GetImageVulnerabilitiesInternal_FullMethodName = "/v1.ImageService/GetImageVulnerabilitiesInternal" ImageService_EnrichLocalImageInternal_FullMethodName = "/v1.ImageService/EnrichLocalImageInternal" ImageService_UpdateLocalScanStatusInternal_FullMethodName = "/v1.ImageService/UpdateLocalScanStatusInternal" @@ -52,6 +53,12 @@ type ImageServiceClient interface { ScanImage(ctx context.Context, in *ScanImageRequest, opts ...grpc.CallOption) (*storage.Image, error) // ScanImageInternal is used solely by the Sensor and Admission Controller to send scan requests ScanImageInternal(ctx context.Context, in *ScanImageInternalRequest, opts ...grpc.CallOption) (*ScanImageInternalResponse, error) + // ScanImageInternalForAdmission is used solely by the Admission Controller + // (via Sensor or directly) to send scan requests. For tag-only images it + // refreshes registry metadata but reuses an existing Central scan when + // the resolved digest is already known. For digest-based images it + // behaves identically to ScanImageInternal. + ScanImageInternalForAdmission(ctx context.Context, in *ScanImageInternalRequest, opts ...grpc.CallOption) (*ScanImageInternalResponse, error) // Deprecated: Do not use. // Deprecated: GetImageVulnerabilities is used solely by the Sensor to send vulnerability matching requests. GetImageVulnerabilitiesInternal(ctx context.Context, in *GetImageVulnerabilitiesInternalRequest, opts ...grpc.CallOption) (*ScanImageInternalResponse, error) @@ -135,6 +142,16 @@ func (c *imageServiceClient) ScanImageInternal(ctx context.Context, in *ScanImag return out, nil } +func (c *imageServiceClient) ScanImageInternalForAdmission(ctx context.Context, in *ScanImageInternalRequest, opts ...grpc.CallOption) (*ScanImageInternalResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ScanImageInternalResponse) + err := c.cc.Invoke(ctx, ImageService_ScanImageInternalForAdmission_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // Deprecated: Do not use. func (c *imageServiceClient) GetImageVulnerabilitiesInternal(ctx context.Context, in *GetImageVulnerabilitiesInternalRequest, opts ...grpc.CallOption) (*ScanImageInternalResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) @@ -251,6 +268,12 @@ type ImageServiceServer interface { ScanImage(context.Context, *ScanImageRequest) (*storage.Image, error) // ScanImageInternal is used solely by the Sensor and Admission Controller to send scan requests ScanImageInternal(context.Context, *ScanImageInternalRequest) (*ScanImageInternalResponse, error) + // ScanImageInternalForAdmission is used solely by the Admission Controller + // (via Sensor or directly) to send scan requests. For tag-only images it + // refreshes registry metadata but reuses an existing Central scan when + // the resolved digest is already known. For digest-based images it + // behaves identically to ScanImageInternal. + ScanImageInternalForAdmission(context.Context, *ScanImageInternalRequest) (*ScanImageInternalResponse, error) // Deprecated: Do not use. // Deprecated: GetImageVulnerabilities is used solely by the Sensor to send vulnerability matching requests. GetImageVulnerabilitiesInternal(context.Context, *GetImageVulnerabilitiesInternalRequest) (*ScanImageInternalResponse, error) @@ -298,6 +321,9 @@ func (UnimplementedImageServiceServer) ScanImage(context.Context, *ScanImageRequ func (UnimplementedImageServiceServer) ScanImageInternal(context.Context, *ScanImageInternalRequest) (*ScanImageInternalResponse, error) { return nil, status.Error(codes.Unimplemented, "method ScanImageInternal not implemented") } +func (UnimplementedImageServiceServer) ScanImageInternalForAdmission(context.Context, *ScanImageInternalRequest) (*ScanImageInternalResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ScanImageInternalForAdmission not implemented") +} func (UnimplementedImageServiceServer) GetImageVulnerabilitiesInternal(context.Context, *GetImageVulnerabilitiesInternalRequest) (*ScanImageInternalResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetImageVulnerabilitiesInternal not implemented") } @@ -435,6 +461,24 @@ func _ImageService_ScanImageInternal_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _ImageService_ScanImageInternalForAdmission_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ScanImageInternalRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ImageServiceServer).ScanImageInternalForAdmission(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ImageService_ScanImageInternalForAdmission_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ImageServiceServer).ScanImageInternalForAdmission(ctx, req.(*ScanImageInternalRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ImageService_GetImageVulnerabilitiesInternal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetImageVulnerabilitiesInternalRequest) if err := dec(in); err != nil { @@ -617,6 +661,10 @@ var ImageService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ScanImageInternal", Handler: _ImageService_ScanImageInternal_Handler, }, + { + MethodName: "ScanImageInternalForAdmission", + Handler: _ImageService_ScanImageInternalForAdmission_Handler, + }, { MethodName: "GetImageVulnerabilitiesInternal", Handler: _ImageService_GetImageVulnerabilitiesInternal_Handler, diff --git a/proto/api/v1/image_service.proto b/proto/api/v1/image_service.proto index 380a566cfc571..2cfabdb3c3fa6 100644 --- a/proto/api/v1/image_service.proto +++ b/proto/api/v1/image_service.proto @@ -177,6 +177,13 @@ service ImageService { // ScanImageInternal is used solely by the Sensor and Admission Controller to send scan requests rpc ScanImageInternal(ScanImageInternalRequest) returns (ScanImageInternalResponse); + // ScanImageInternalForAdmission is used solely by the Admission Controller + // (via Sensor or directly) to send scan requests. For tag-only images it + // refreshes registry metadata but reuses an existing Central scan when + // the resolved digest is already known. For digest-based images it + // behaves identically to ScanImageInternal. + rpc ScanImageInternalForAdmission(ScanImageInternalRequest) returns (ScanImageInternalResponse); + // Deprecated: GetImageVulnerabilities is used solely by the Sensor to send vulnerability matching requests. rpc GetImageVulnerabilitiesInternal(GetImageVulnerabilitiesInternalRequest) returns (ScanImageInternalResponse) { option deprecated = true; diff --git a/roxctl/central/export/images/images_test.go b/roxctl/central/export/images/images_test.go index 479f5f19555d7..57617dff568e8 100644 --- a/roxctl/central/export/images/images_test.go +++ b/roxctl/central/export/images/images_test.go @@ -88,6 +88,10 @@ func (s *fakeImageService) ScanImageInternal(_ context.Context, _ *v1.ScanImageI return nil, errox.NotImplemented } +func (s *fakeImageService) ScanImageInternalForAdmission(_ context.Context, _ *v1.ScanImageInternalRequest) (*v1.ScanImageInternalResponse, error) { + return nil, errox.NotImplemented +} + func (s *fakeImageService) ScanImage(_ context.Context, _ *v1.ScanImageRequest) (*storage.Image, error) { return nil, errox.NotImplemented } From 1753e4b4492e4c319d01d3c713f3e9ec15877305 Mon Sep 17 00:00:00 2001 From: Gustavo Alkmim Date: Wed, 19 Aug 2026 15:11:39 -0700 Subject: [PATCH 009/638] ROX-35508: Filter OSV.dev records when Red Hat VEX data exists (#22147) ROX-35508: when a vulnerability is covered by Red Hat VEX security data (known_affected) for a Red Hat product image, filter the corresponding OSV.dev-sourced record for the same CVE and let Red Hat's own severity/CVSS/remediation data be the only one shown, instead of reporting both (often with conflicting severity). - Only OSV packages are filtered. - The filtering occurs only when the Red Hat Vex affects an OCI package, targeting the same CVE-like alias, on the same layer or below. Changes: [major] Added ROX_SCANNER_V4_SUPPRESS_OSV_WITH_RED_HAT_VEX feature flag [major] Added filterOSVSupersededByRedHatVEX function to supress OSV records when red hat vex is present. [minor] Refactored repetitive codes into getPackageLayerIndex and environmentList functions. [minor] Optmize filterNotAffectedVulnerabilities to simplify the filtering logic --- CHANGELOG.md | 1 + pkg/features/list.go | 3 + pkg/scanners/scannerv4/convert.go | 173 +++-- pkg/scanners/scannerv4/convert_test.go | 922 +++++++++++++++++++++++++ 4 files changed, 1058 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f60c0cc0a46e..6e37a324239dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35546: File access policies now detect extended attribute (xattr) changes. - ROX-32461: Red Hat OpenShift Data Foundation is now officially supported as an S3-compatible backup target. - ROX-35962: On OCP, central API is exposed via a new `central-ocp` service, signed and rotated by OCP. +- ROX-35508: Scanner V4 now suppresses duplicate OSV.dev vulnerability records when Red Hat VEX data covers the same CVE for a Red Hat product image, showing Red Hat's own severity/CVSS/remediation data instead of a conflicting OSV.dev one. Enabled by default; disable via `ROX_SCANNER_V4_SUPPRESS_OSV_WITH_RED_HAT_VEX=false` if needed. ### Removed Features diff --git a/pkg/features/list.go b/pkg/features/list.go index 51afb53e39134..a0a22bb13d91c 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -206,4 +206,7 @@ var ( // ScannerV4Dedupe de-duplicates packages and vulnerabilities from appearing in scan results. ScannerV4Dedupe = registerFeature("Deduplicate packages and vulnerabilities found in Scanner V4 results.", "ROX_SCANNER_V4_DEDUPE", enabled) + + // ScannerV4SuppressOSVWithRedHatVEX suppresses OSV.dev vulnerabilities when a corresponding Red Hat VEX assertion exists. + ScannerV4SuppressOSVWithRedHatVEX = registerFeature("Scanner V4 will suppress OSV.dev vulnerabilities when a corresponding Red Hat VEX assertion exists", "ROX_SCANNER_V4_SUPPRESS_OSV_WITH_RED_HAT_VEX", enabled) ) diff --git a/pkg/scanners/scannerv4/convert.go b/pkg/scanners/scannerv4/convert.go index fab0e4e3d1abe..39b5452d25fc2 100644 --- a/pkg/scanners/scannerv4/convert.go +++ b/pkg/scanners/scannerv4/convert.go @@ -34,6 +34,9 @@ func imageScan(metadata *storage.ImageMetadata, report *v4.VulnerabilityReport, if features.ScannerV4RedHatVEXNotAffected.Enabled() { filterNotAffectedVulnerabilities(report, layerSHAToIndex) } + if features.ScannerV4SuppressOSVWithRedHatVEX.Enabled() { + filterOSVSupersededByRedHatVEX(report, layerSHAToIndex) + } scan := &storage.ImageScan{ ScannerVersion: scannerVersion, @@ -146,18 +149,20 @@ func envOS(env *v4.Environment, report *v4.VulnerabilityReport) string { return dist.GetDid() + ":" + dist.GetVersionId() } -func environment(report *v4.VulnerabilityReport, id string) *v4.Environment { +// environmentList returns the *v4.Environment_List associated with the given +// package ID, falling back to the deprecated environments map if the +// current one is unset. +func environmentList(report *v4.VulnerabilityReport, id string) *v4.Environment_List { environments := report.GetContents().GetEnvironments() if environments == nil { // Fallback to deprecated environments. environments = report.GetContents().GetEnvironmentsDEPRECATED() } - envList, ok := environments[id] - if !ok { - return nil - } + return environments[id] +} - envs := envList.GetEnvironments() +func environment(report *v4.VulnerabilityReport, id string) *v4.Environment { + envs := environmentList(report, id).GetEnvironments() if len(envs) > 0 { // Just use the first environment. // It is possible there are multiple environments associated with this package; @@ -170,6 +175,20 @@ func environment(report *v4.VulnerabilityReport, id string) *v4.Environment { return nil } +// packageHasRepositoryKey reports whether pkgID is associated, via any of +// its Environment entries, with a Repository whose Key matches key. +func packageHasRepositoryKey(report *v4.VulnerabilityReport, pkgID, key string) bool { + repos := report.GetContents().GetRepositories() + for _, env := range environmentList(report, pkgID).GetEnvironments() { + for _, repoID := range env.GetRepositoryIds() { + if repos[repoID].GetKey() == key { + return true + } + } + } + return false +} + // ParsePackageDB parses the given packageDB into its source type + filepath. func ParsePackageDB(packageDB string) (storage.SourceType, string) { prefix, path, found := strings.Cut(packageDB, ":") @@ -548,6 +567,15 @@ func getPackageLayerSHA(report *v4.VulnerabilityReport, pkgID string) (string, b return env.GetIntroducedIn(), true } +func getPackageLayerIndex(report *v4.VulnerabilityReport, layerSHAToIndex map[string]int32, pkgID string) (int32, bool) { + layerSHA, ok := getPackageLayerSHA(report, pkgID) + if !ok { + return 0, false + } + layerIdx, ok := layerSHAToIndex[layerSHA] + return layerIdx, ok +} + // filterNotAffectedVulnerabilities removes vulnerabilities from PackageVulnerabilities // when they are covered by VEX not-affected assertions via AncestryPackage entries. // A package is covered if it was introduced at or below the AncestryPackage's layer @@ -557,58 +585,129 @@ func filterNotAffectedVulnerabilities(report *v4.VulnerabilityReport, layerSHATo return } - // Find AncestryPackage entries and collect their boundaries with aliases. - type boundary struct { - layerIndex int32 - notAffectedKeys set.StringSet - } - var boundaries []boundary - + // Find AncestryPackage entries. A package introduced at or before that + // layer is also not affected, so keep the highest layer index per alias. + aliasKeyToLayerIndex := make(map[string]int32) for pkgID, vulnIDs := range report.GetPackageNotVulnerable() { pkg := report.GetContents().GetPackages()[pkgID] if pkg == nil || pkg.GetKind() != "ancestry" { continue } - layerSHA, ok := getPackageLayerSHA(report, pkgID) + layerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) if !ok { continue } - layerIdx, ok := layerSHAToIndex[layerSHA] + + for _, vulnID := range vulnIDs.GetValues() { + vuln := report.GetVulnerabilities()[vulnID] + if vuln == nil { + continue + } + for _, alias := range vuln.GetAliases() { + key := aliasKey(alias) + if cur, ok := aliasKeyToLayerIndex[key]; !ok || layerIdx > cur { + aliasKeyToLayerIndex[key] = layerIdx + } + } + } + } + + if len(aliasKeyToLayerIndex) == 0 { + return + } + + // Remove PackageVulnerabilities entries covered by a same-or-lower-layer + // not-affected assertion. + for pkgID, vulnIDs := range report.GetPackageVulnerabilities() { + pkgLayerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) if !ok { continue } - aliasKeys := set.NewStringSet() + var filtered []string for _, vulnID := range vulnIDs.GetValues() { vuln := report.GetVulnerabilities()[vulnID] if vuln == nil { + filtered = append(filtered, vulnID) continue } + + removed := false for _, alias := range vuln.GetAliases() { - aliasKeys.Add(aliasKey(alias)) + if layerIdx, ok := aliasKeyToLayerIndex[aliasKey(alias)]; ok && pkgLayerIdx <= layerIdx { + removed = true + break + } + } + + if removed { + log.Debugf("Suppressing vuln %q for package %q due to VEX not-affected assertion", vuln.GetName(), pkgID) + } else { + filtered = append(filtered, vulnID) } } - if aliasKeys.Cardinality() > 0 { - boundaries = append(boundaries, boundary{ - layerIndex: layerIdx, - notAffectedKeys: aliasKeys, - }) + if len(filtered) == 0 { + delete(report.PackageVulnerabilities, pkgID) //nolint:protogetter // mutation requires direct field access + } else { + report.PackageVulnerabilities[pkgID] = &v4.StringList{Values: filtered} } } +} + +// filterOSVSupersededByRedHatVEX removes osv/*-sourced vulnerabilities from +// PackageVulnerabilities when a rhel-vex updater vulnerability shares a +// CVE alias at or above the OSV package's layer. +func filterOSVSupersededByRedHatVEX(report *v4.VulnerabilityReport, layerSHAToIndex map[string]int32) { + const ( + osvUpdaterPrefix = "osv/" + redHatVEXUpdaterName = "rhel-vex" + // redHatContainerRepositoryKey matches + // github.com/quay/claircore/rhel/rhcc.RepositoryKey: it is set on + // every Repository indexed by claircore's RHCC (container) ecosystem. + redHatContainerRepositoryKey = "rhcc-container-repository" + ) - if len(boundaries) == 0 { + if len(report.GetPackageVulnerabilities()) == 0 { return } - // Filter PackageVulnerabilities based on boundaries. + // Find packages with a rhel-vex vulnerability. An OSV package introduced + // at or before that layer is superseded too, so keep the highest layer per alias. + aliasKeyToLayerIndex := make(map[string]int32) for pkgID, vulnIDs := range report.GetPackageVulnerabilities() { - pkgLayerSHA, ok := getPackageLayerSHA(report, pkgID) + // Only consider OCI (RHCC) packages. + if !packageHasRepositoryKey(report, pkgID, redHatContainerRepositoryKey) { + continue + } + + layerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) if !ok { continue } - pkgLayerIdx, ok := layerSHAToIndex[pkgLayerSHA] + + for _, vulnID := range vulnIDs.GetValues() { + vuln := report.GetVulnerabilities()[vulnID] + if vuln == nil || vuln.GetUpdater() != redHatVEXUpdaterName { + continue + } + for _, alias := range vuln.GetAliases() { + key := aliasKey(alias) + if cur, ok := aliasKeyToLayerIndex[key]; !ok || layerIdx > cur { + aliasKeyToLayerIndex[key] = layerIdx + } + } + } + } + + if len(aliasKeyToLayerIndex) == 0 { + return + } + + // Remove osv/* vulnerabilities covered by a same-or-lower-layer rhel-vex vulnerability. + for pkgID, vulnIDs := range report.GetPackageVulnerabilities() { + pkgLayerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) if !ok { continue } @@ -616,29 +715,21 @@ func filterNotAffectedVulnerabilities(report *v4.VulnerabilityReport, layerSHATo var filtered []string for _, vulnID := range vulnIDs.GetValues() { vuln := report.GetVulnerabilities()[vulnID] - if vuln == nil { + if vuln == nil || !strings.HasPrefix(vuln.GetUpdater(), osvUpdaterPrefix) { filtered = append(filtered, vulnID) continue } - suppressed := false - for _, b := range boundaries { - if pkgLayerIdx > b.layerIndex { - continue - } - for _, alias := range vuln.GetAliases() { - if b.notAffectedKeys.Contains(aliasKey(alias)) { - suppressed = true - break - } - } - if suppressed { + removed := false + for _, alias := range vuln.GetAliases() { + if layerIdx, ok := aliasKeyToLayerIndex[aliasKey(alias)]; ok && pkgLayerIdx <= layerIdx { + removed = true break } } - if suppressed { - log.Debugf("Suppressing vuln %q for package %q due to VEX not-affected assertion", vuln.GetName(), pkgID) + if removed { + log.Debugf("Suppressing OSV vuln %q for package %q in favor of Red Hat VEX", vuln.GetName(), pkgID) } else { filtered = append(filtered, vulnID) } diff --git a/pkg/scanners/scannerv4/convert_test.go b/pkg/scanners/scannerv4/convert_test.go index c5422688d1007..16608d3f3dfc5 100644 --- a/pkg/scanners/scannerv4/convert_test.go +++ b/pkg/scanners/scannerv4/convert_test.go @@ -2251,6 +2251,928 @@ func TestFilterNotAffectedVulnerabilities(t *testing.T) { } } +func TestFilterOSVSupersededByRedHatVEX(t *testing.T) { + layerSHAToIndex := map[string]int32{ + "sha256:layer0": 0, + "sha256:layer1": 1, + "sha256:layer2": 2, + } + + testcases := []struct { + name string + report *v4.VulnerabilityReport + expectedPkgVulns map[string][]string + }{ + { + name: "nil/empty PackageVulnerabilities - no-op", + report: &v4.VulnerabilityReport{ + PackageVulnerabilities: nil, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-1111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + }, + }, + expectedPkgVulns: map[string][]string{}, + }, + { + name: "no rhel-vex vuln in the report - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Environments: map[string]*v4.Environment_List{ + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer1"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "go-pkg-1": {"osv-vuln"}, + }, + }, + { + name: "two packages, same layer, share a CVE alias - OSV suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + }, + }, + { + name: "two packages share a CVE alias, OSV package on a later layer - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer2"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + "go-pkg-1": {"osv-vuln"}, + }, + }, + { + name: "two packages, same layer, different vuln names, share a CVE alias - OSV suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "GHSA-8r3f-844c-mc37", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "ghsa", Name: "GHSA-8r3f-844c-mc37"}, + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + }, + }, + { + name: "no shared alias between an OSV vuln and a rhel-vex vuln - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-2222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-1111", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + "go-pkg-1": {"osv-vuln"}, + }, + }, + { + name: "non-osv vuln sharing an alias with a rhel-vex vuln, same layer - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "nvd-vuln": { + Id: "nvd-vuln", + Name: "CVE-2024-24786", + Updater: "nvd", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"nvd-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + "go-pkg-1": {"nvd-vuln"}, + }, + }, + { + name: "two pairs of packages, each pair sharing its own CVE alias - both OSV vulns suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "bin-2": {Id: "bin-2", Kind: "binary", Name: "rhel8-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "pkg1"}, + "go-pkg-2": {Id: "go-pkg-2", Kind: "binary", Name: "pkg2"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "bin-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer1", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + "go-pkg-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer1"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln-1": { + Id: "vex-vuln-1", + Name: "CVE-2024-1111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "vex-vuln-2": { + Id: "vex-vuln-2", + Name: "CVE-2024-2222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "osv-vuln-1": { + Id: "osv-vuln-1", + Name: "CVE-2024-1111", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-2222", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln-1"}}, + "bin-2": {Values: []string{"vex-vuln-2"}}, + "go-pkg-1": {Values: []string{"osv-vuln-1"}}, + "go-pkg-2": {Values: []string{"osv-vuln-2"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln-1"}, + "bin-2": {"vex-vuln-2"}, + }, + }, + { + name: "partial suppression - only the matching OSV id is removed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-match": { + Id: "osv-match", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-other": { + Id: "osv-other", + Name: "CVE-2024-3333", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-3333"}, + }, + }, + "nvd-other": { + Id: "nvd-other", + Name: "CVE-2024-24786", + Updater: "nvd", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-match", "osv-other", "nvd-other"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + "go-pkg-1": {"osv-other", "nvd-other"}, + }, + }, + { + name: "OSV package has no resolvable layer - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "bin-2": {Id: "bin-2", Kind: "binary", Name: "rhel8-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + "go-pkg-2": {Id: "go-pkg-2", Kind: "binary", Name: "pkg2"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "bin-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + // go-pkg-1 intentionally has no Environments entry. + "go-pkg-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:unknown-layer"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln-1": { + Id: "vex-vuln-1", + Name: "CVE-2024-1111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "vex-vuln-2": { + Id: "vex-vuln-2", + Name: "CVE-2024-2222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "osv-vuln-1": { + Id: "osv-vuln-1", + Name: "CVE-2024-1111", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-2222", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln-1"}}, + "bin-2": {Values: []string{"vex-vuln-2"}}, + "go-pkg-1": {Values: []string{"osv-vuln-1"}}, + "go-pkg-2": {Values: []string{"osv-vuln-2"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln-1"}, + "bin-2": {"vex-vuln-2"}, + "go-pkg-1": {"osv-vuln-1"}, + "go-pkg-2": {"osv-vuln-2"}, + }, + }, + { + name: "vuln ID missing from report.Vulnerabilities - kept, no panic", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "bin-2": {Id: "bin-2", Kind: "binary", Name: "rhel8-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + "go-pkg-2": {Id: "go-pkg-2", Kind: "binary", Name: "pkg2"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "bin-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + "go-pkg-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln-1": { + Id: "vex-vuln-1", + Name: "CVE-2024-1111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "vex-vuln-2": { + Id: "vex-vuln-2", + Name: "CVE-2024-2222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-2222", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln-1"}}, + "bin-2": {Values: []string{"vex-vuln-2"}}, + "go-pkg-1": {Values: []string{"missing-vuln-id"}}, + "go-pkg-2": {Values: []string{"osv-vuln-2"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln-1"}, + "bin-2": {"vex-vuln-2"}, + "go-pkg-1": {"missing-vuln-id"}, + }, + }, + { + name: "rhel-vex package has no resolvable layer - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "bin-2": {Id: "bin-2", Kind: "binary", Name: "rhel8-container"}, + "bin-3": {Id: "bin-3", Kind: "binary", Name: "other-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "pkg1"}, + "go-pkg-2": {Id: "go-pkg-2", Kind: "binary", Name: "pkg2"}, + "go-pkg-3": {Id: "go-pkg-3", Kind: "binary", Name: "pkg3"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + // bin-1 intentionally has no Environments entry. + "bin-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:unknown-layer", RepositoryIds: []string{"rhcc-repo"}}}}, + "bin-3": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + "go-pkg-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + "go-pkg-3": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln-1": { + Id: "vex-vuln-1", + Name: "CVE-2024-1111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "vex-vuln-2": { + Id: "vex-vuln-2", + Name: "CVE-2024-2222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "vex-vuln-3": { + Id: "vex-vuln-3", + Name: "CVE-2024-3333", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-3333"}, + }, + }, + "osv-vuln-1": { + Id: "osv-vuln-1", + Name: "CVE-2024-1111", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-1111"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-2222", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-2222"}, + }, + }, + "osv-vuln-3": { + Id: "osv-vuln-3", + Name: "CVE-2024-3333", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-3333"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln-1"}}, + "bin-2": {Values: []string{"vex-vuln-2"}}, + "bin-3": {Values: []string{"vex-vuln-3"}}, + "go-pkg-1": {Values: []string{"osv-vuln-1"}}, + "go-pkg-2": {Values: []string{"osv-vuln-2"}}, + "go-pkg-3": {Values: []string{"osv-vuln-3"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln-1"}, + "bin-2": {"vex-vuln-2"}, + "bin-3": {"vex-vuln-3"}, + "go-pkg-1": {"osv-vuln-1"}, + "go-pkg-2": {"osv-vuln-2"}, + }, + }, + { + name: "one rhel-vex vuln suppresses OSV vulns on two different packages", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + "go-pkg-2": {Id: "go-pkg-2", Kind: "binary", Name: "github.com/other/vendored-copy"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + "go-pkg-2": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln-1": { + Id: "osv-vuln-1", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln-1"}}, + "go-pkg-2": {Values: []string{"osv-vuln-2"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + }, + }, + { + name: "rhel-vex package also has a different-updater vuln alongside it", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "other-vuln": { + Id: "other-vuln", + Name: "CVE-2024-9999", + Updater: "nvd", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-9999"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln", "other-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln", "other-vuln"}, + }, + }, + { + name: "rhel-vex vuln with an empty alias list - not suppressed", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + // Intentionally no aliases. + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "CVE-2024-24786", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "bin-1": {"vex-vuln"}, + "go-pkg-1": {"osv-vuln"}, + }, + }, + { + name: "Mixed RHCC (OCI) and RPM packages: only RHCC suppresses OSV", + report: &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "rhcc-pkg": {Id: "rhcc-pkg", Kind: "binary", Name: "ubi9-container"}, + "rpm-pkg": {Id: "rpm-pkg", Kind: "binary", Name: "some-rpm-package"}, + "go-pkg": {Id: "go-pkg", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + "rhel-repo": {Id: "rhel-repo", Key: "rhel-cpe-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "rhcc-pkg": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "rpm-pkg": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhel-repo"}}}}, + "go-pkg": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "rhcc-vex-vuln": { + Id: "rhcc-vex-vuln", + Name: "CVE-2024-11111", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-11111"}, + }, + }, + "rpm-vex-vuln": { + Id: "rpm-vex-vuln", + Name: "CVE-2024-22222", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-22222"}, + }, + }, + "osv-vuln-1": { + Id: "osv-vuln-1", + Name: "CVE-2024-11111", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-11111"}, + }, + }, + "osv-vuln-2": { + Id: "osv-vuln-2", + Name: "CVE-2024-22222", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-22222"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "rhcc-pkg": {Values: []string{"rhcc-vex-vuln"}}, + "rpm-pkg": {Values: []string{"rpm-vex-vuln"}}, + "go-pkg": {Values: []string{"osv-vuln-1", "osv-vuln-2"}}, + }, + }, + expectedPkgVulns: map[string][]string{ + "rhcc-pkg": {"rhcc-vex-vuln"}, + "rpm-pkg": {"rpm-vex-vuln"}, + "go-pkg": {"osv-vuln-2"}, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + filterOSVSupersededByRedHatVEX(tc.report, layerSHAToIndex) + + got := make(map[string][]string) + for pkgID, vulns := range tc.report.GetPackageVulnerabilities() { + got[pkgID] = vulns.GetValues() + } + assert.Equal(t, tc.expectedPkgVulns, got) + }) + } +} + +func TestFilterOSVSupersededByRedHatVEX_FeatureFlag(t *testing.T) { + newReport := func() *v4.VulnerabilityReport { + return &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "bin-1": {Id: "bin-1", Kind: "binary", Name: "ubi9-container"}, + "go-pkg-1": {Id: "go-pkg-1", Kind: "binary", Name: "google.golang.org/protobuf"}, + }, + Repositories: map[string]*v4.Repository{ + "rhcc-repo": {Id: "rhcc-repo", Key: "rhcc-container-repository"}, + }, + Environments: map[string]*v4.Environment_List{ + "bin-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0", RepositoryIds: []string{"rhcc-repo"}}}}, + "go-pkg-1": {Environments: []*v4.Environment{{IntroducedIn: "sha256:layer0"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "vex-vuln": { + Id: "vex-vuln", + Name: "CVE-2024-24786", + Updater: "rhel-vex", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + "osv-vuln": { + Id: "osv-vuln", + Name: "GHSA-osv-flag-test", + Updater: "osv/go", + Aliases: []*v4.VulnerabilityReport_Alias{ + {Space: "ghsa", Name: "GHSA-osv-flag-test"}, + {Space: "cve", Name: "CVE-2024-24786"}, + }, + }, + }, + PackageVulnerabilities: map[string]*v4.StringList{ + "bin-1": {Values: []string{"vex-vuln"}}, + "go-pkg-1": {Values: []string{"osv-vuln"}}, + }, + } + } + metadata := &storage.ImageMetadata{ + V2: &storage.V2Metadata{}, + V1: &storage.V1Metadata{Layers: []*storage.ImageLayer{{Empty: false}}}, + LayerShas: []string{"sha256:layer0"}, + } + const osvCVE = "GHSA-osv-flag-test" + + t.Run("flag off - OSV vuln survives", func(t *testing.T) { + t.Setenv(features.ScannerV4SuppressOSVWithRedHatVEX.EnvVar(), "false") + scan := imageScan(metadata, newReport(), scannerVersion) + assert.True(t, hasCVE(scan, osvCVE), "expected %s to survive with the flag disabled", osvCVE) + }) + + t.Run("flag on - OSV vuln suppressed", func(t *testing.T) { + t.Setenv(features.ScannerV4SuppressOSVWithRedHatVEX.EnvVar(), "true") + scan := imageScan(metadata, newReport(), scannerVersion) + assert.False(t, hasCVE(scan, osvCVE), "expected %s to be suppressed with the flag enabled", osvCVE) + }) +} + +// hasCVE reports whether any component in scan carries a vulnerability whose +// Cve field matches cve. +func hasCVE(scan *storage.ImageScan, cve string) bool { + for _, c := range scan.GetComponents() { + for _, v := range c.GetVulns() { + if v.GetCve() == cve { + return true + } + } + } + return false +} + // TestVulnDataSource // // If this test fails due to a datasource format change From cc2fca759c34065c30aff40ff0cca9a6710c2d73 Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:34:25 -0700 Subject: [PATCH 010/638] ROX-36423: Wire admission control to new scan reuse RPC (#22288) --- sensor/admission-control/manager/images.go | 2 +- sensor/common/image/interfaces.go | 1 + sensor/common/image/mocks/interfaces.go | 20 ++++++++++++++++++++ sensor/common/image/service_impl.go | 2 +- sensor/common/image/service_impl_test.go | 2 +- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/sensor/admission-control/manager/images.go b/sensor/admission-control/manager/images.go index 84ef75aecf3e7..a0be3b67a0b2c 100644 --- a/sensor/admission-control/manager/images.go +++ b/sensor/admission-control/manager/images.go @@ -125,7 +125,7 @@ func (m *manager) getImageFromSensorOrCentral(ctx context.Context, s *state, img // Note: Sensor is required to scan images in the local registry. if !m.sensorConnStatus.Get() && s.centralConn != nil && s.centralConn.GetState() != connectivity.Shutdown { start := time.Now() - resp, err := v1.NewImageServiceClient(s.centralConn).ScanImageInternal(ctx, &v1.ScanImageInternalRequest{ + resp, err := v1.NewImageServiceClient(s.centralConn).ScanImageInternalForAdmission(ctx, &v1.ScanImageInternalRequest{ Image: img, CachedOnly: !s.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline(), }) diff --git a/sensor/common/image/interfaces.go b/sensor/common/image/interfaces.go index 31660e859fb56..a016d829960dc 100644 --- a/sensor/common/image/interfaces.go +++ b/sensor/common/image/interfaces.go @@ -20,6 +20,7 @@ type registryStore interface { type centralClient interface { ScanImageInternal(context.Context, *v1.ScanImageInternalRequest, ...grpc.CallOption) (*v1.ScanImageInternalResponse, error) + ScanImageInternalForAdmission(context.Context, *v1.ScanImageInternalRequest, ...grpc.CallOption) (*v1.ScanImageInternalResponse, error) EnrichLocalImageInternal(context.Context, *v1.EnrichLocalImageInternalRequest, ...grpc.CallOption) (*v1.ScanImageInternalResponse, error) } diff --git a/sensor/common/image/mocks/interfaces.go b/sensor/common/image/mocks/interfaces.go index 0c38b631f0dd3..0de225325e782 100644 --- a/sensor/common/image/mocks/interfaces.go +++ b/sensor/common/image/mocks/interfaces.go @@ -167,6 +167,26 @@ func (mr *MockcentralClientMockRecorder) ScanImageInternal(arg0, arg1 any, arg2 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanImageInternal", reflect.TypeOf((*MockcentralClient)(nil).ScanImageInternal), varargs...) } +// ScanImageInternalForAdmission mocks base method. +func (m *MockcentralClient) ScanImageInternalForAdmission(arg0 context.Context, arg1 *v1.ScanImageInternalRequest, arg2 ...grpc.CallOption) (*v1.ScanImageInternalResponse, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ScanImageInternalForAdmission", varargs...) + ret0, _ := ret[0].(*v1.ScanImageInternalResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ScanImageInternalForAdmission indicates an expected call of ScanImageInternalForAdmission. +func (mr *MockcentralClientMockRecorder) ScanImageInternalForAdmission(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanImageInternalForAdmission", reflect.TypeOf((*MockcentralClient)(nil).ScanImageInternalForAdmission), varargs...) +} + // MocklocalScan is a mock of localScan interface. type MocklocalScan struct { ctrl *gomock.Controller diff --git a/sensor/common/image/service_impl.go b/sensor/common/image/service_impl.go index a3984996c063f..b6c3cc5eeefe8 100644 --- a/sensor/common/image/service_impl.go +++ b/sensor/common/image/service_impl.go @@ -110,7 +110,7 @@ func (s *serviceImpl) GetImage(ctx context.Context, req *sensor.GetImageRequest) // Ask Central to scan the image if the image is neither internal nor local if !req.GetImage().GetIsClusterLocal() { - scanResp, err := s.centralClient.ScanImageInternal(ctx, &v1.ScanImageInternalRequest{ + scanResp, err := s.centralClient.ScanImageInternalForAdmission(ctx, &v1.ScanImageInternalRequest{ Image: req.GetImage(), CachedOnly: !req.GetScanInline(), }) diff --git a/sensor/common/image/service_impl_test.go b/sensor/common/image/service_impl_test.go index 37fa3355c3cf3..b7664f6d5369d 100644 --- a/sensor/common/image/service_impl_test.go +++ b/sensor/common/image/service_impl_test.go @@ -171,7 +171,7 @@ func expectRegistryHelper(mockRegistryStore *imageMocks.MockregistryStore, times func expectCentralCall(mockCentral *imageMocks.MockcentralClient, times int, retValue *v1.ScanImageInternalResponse, retErr error) expectFn { return func() { - mockCentral.EXPECT().ScanImageInternal(gomock.Any(), gomock.Any()).Times(times). + mockCentral.EXPECT().ScanImageInternalForAdmission(gomock.Any(), gomock.Any()).Times(times). Return(retValue, retErr) } } From f4f10e62c9412c3ef314fe95d74cc76dc3869322 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 04:21:18 +0000 Subject: [PATCH 011/638] Update Konflux references (#22358) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 16 ++++++++-------- .tekton/checks.yaml | 4 ++-- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/main-pipeline.yaml | 16 ++++++++-------- .tekton/operator-bundle-pipeline.yaml | 16 ++++++++-------- .tekton/operator-index-build.yaml | 6 +++--- .tekton/retag-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 16 ++++++++-------- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index c472b099cb424..2669e17a9b4a5 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4dbbfedc4edb21884fa2ad84521408d2fad090d4d34eb6c555992f8444eba40d + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -251,7 +251,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.9.0@sha256:4486aaa69770d6b27c59c8df7d82e450d95dab2302aef9b8a345f2ac0fabbab0 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 - name: kind value: task resolver: bundles @@ -310,7 +310,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:227cf5b487cd2221301ea2fccf2334f41c0f5b21edb04696609245520b2d2691 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:714a86d454203804fe14e422b4ba8d775d9ec19e5b7922cedd55ef93a3bc9166 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 - name: kind value: task resolver: bundles @@ -350,7 +350,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:c7ecd1e518007e31e19188333a17dd3558e222ce2e3b2a484a1e59ec069799cf + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c - name: kind value: task resolver: bundles @@ -541,7 +541,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:cc5133504ff03909ae970db81a986cb25a5532b2faf62a8ede5c60f28b716f54 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d - name: kind value: task resolver: bundles @@ -567,7 +567,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ee041e2bdf5638faaca60baa171c3e13927097a0e69ed2172ccf1de5e5ee711a + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 2508b4492121d..9e9e8e7156dd8 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -146,7 +146,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:de5580c99ca52588f9b8cec452023bc7e3de470a594c99243320752d250bb2a2 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:cf32941c77e413818c4ef6b78c2a88d6e6ddc21729767d8f4d316cd293e4a15d - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 9806101ab1331..b89973a01b585 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -201,7 +201,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 667d630fce643..1ad4f137fdeb0 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -169,7 +169,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4dbbfedc4edb21884fa2ad84521408d2fad090d4d34eb6c555992f8444eba40d + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 - name: kind value: task resolver: bundles @@ -193,7 +193,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.9.0@sha256:4486aaa69770d6b27c59c8df7d82e450d95dab2302aef9b8a345f2ac0fabbab0 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 - name: kind value: task resolver: bundles @@ -331,7 +331,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:227cf5b487cd2221301ea2fccf2334f41c0f5b21edb04696609245520b2d2691 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 - name: kind value: task resolver: bundles @@ -351,7 +351,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:714a86d454203804fe14e422b4ba8d775d9ec19e5b7922cedd55ef93a3bc9166 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 - name: kind value: task resolver: bundles @@ -372,7 +372,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:c7ecd1e518007e31e19188333a17dd3558e222ce2e3b2a484a1e59ec069799cf + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c - name: kind value: task resolver: bundles @@ -563,7 +563,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:cc5133504ff03909ae970db81a986cb25a5532b2faf62a8ede5c60f28b716f54 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d - name: kind value: task resolver: bundles @@ -589,7 +589,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ee041e2bdf5638faaca60baa171c3e13927097a0e69ed2172ccf1de5e5ee711a + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index c633a41366280..40bb845e7570e 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -265,7 +265,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4dbbfedc4edb21884fa2ad84521408d2fad090d4d34eb6c555992f8444eba40d + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 - name: kind value: task resolver: bundles @@ -289,7 +289,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -348,7 +348,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.9.0@sha256:4486aaa69770d6b27c59c8df7d82e450d95dab2302aef9b8a345f2ac0fabbab0 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 - name: kind value: task resolver: bundles @@ -517,7 +517,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:de5580c99ca52588f9b8cec452023bc7e3de470a594c99243320752d250bb2a2 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:cf32941c77e413818c4ef6b78c2a88d6e6ddc21729767d8f4d316cd293e4a15d - name: kind value: task resolver: bundles @@ -536,7 +536,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3@sha256:f89a59d4d043fd3c545a9cb5e89b53396f1e04017c6f68da1e50626715c2d423 + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3@sha256:2dae3c4beaf1070db565c536bc997e93925ae8c0c45dd8da9b4ae5293fd8c592 - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:c7ecd1e518007e31e19188333a17dd3558e222ce2e3b2a484a1e59ec069799cf + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c - name: kind value: task resolver: bundles @@ -736,7 +736,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:cc5133504ff03909ae970db81a986cb25a5532b2faf62a8ede5c60f28b716f54 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d - name: kind value: task resolver: bundles @@ -762,7 +762,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ee041e2bdf5638faaca60baa171c3e13927097a0e69ed2172ccf1de5e5ee711a + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 05d8643c56d99..e2b404c09a05b 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -195,7 +195,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -414,7 +414,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:227cf5b487cd2221301ea2fccf2334f41c0f5b21edb04696609245520b2d2691 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 - name: kind value: task resolver: bundles @@ -433,7 +433,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:714a86d454203804fe14e422b4ba8d775d9ec19e5b7922cedd55ef93a3bc9166 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index bc0d6222e788e..591f7298f2973 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index d72064834c14e..b512836f73313 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4dbbfedc4edb21884fa2ad84521408d2fad090d4d34eb6c555992f8444eba40d + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:5e56115ccb92a1ac49432eef7255929839d02dd43f0e2d6f9557b561e7251a25 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 - name: kind value: task resolver: bundles @@ -271,7 +271,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.9.0@sha256:4486aaa69770d6b27c59c8df7d82e450d95dab2302aef9b8a345f2ac0fabbab0 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 - name: kind value: task resolver: bundles @@ -330,7 +330,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:227cf5b487cd2221301ea2fccf2334f41c0f5b21edb04696609245520b2d2691 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 - name: kind value: task resolver: bundles @@ -349,7 +349,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:714a86d454203804fe14e422b4ba8d775d9ec19e5b7922cedd55ef93a3bc9166 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 - name: kind value: task resolver: bundles @@ -370,7 +370,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:c7ecd1e518007e31e19188333a17dd3558e222ce2e3b2a484a1e59ec069799cf + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c - name: kind value: task resolver: bundles @@ -561,7 +561,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:cc5133504ff03909ae970db81a986cb25a5532b2faf62a8ede5c60f28b716f54 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d - name: kind value: task resolver: bundles @@ -587,7 +587,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ee041e2bdf5638faaca60baa171c3e13927097a0e69ed2172ccf1de5e5ee711a + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 - name: kind value: task resolver: bundles From 4698a0e12eff6f98f530c54111cbaaff0e55f3d3 Mon Sep 17 00:00:00 2001 From: Johannes Malsam Date: Thu, 20 Aug 2026 06:53:19 +0200 Subject: [PATCH 012/638] ROX-35257: verify bg migrations complete on upgrade tests (#21958) --- tests/upgrade/lib.sh | 35 +++++++++++++++++++++++++++++++++++ tests/upgrade/postgres_run.sh | 2 ++ 2 files changed, 37 insertions(+) diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index 58b62ce1cb8ca..5c354af6fe8fc 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -84,6 +84,41 @@ function roxcurl() { curl --config <(curl_cfg user "admin:${ROX_ADMIN_PASSWORD}") -k "https://${API_ENDPOINT}${url}" "$@" } +wait_for_background_migrations() { + info "Waiting for background migrations to complete..." + local retries=10 + local interval=30 + local i + local target_seqnum + target_seqnum=$(get_target_bg_migration_seqnum) + local db_password + db_password=$(kubectl -n stackrox get secret central-db-password -o jsonpath='{.data.password}' | base64 -d) || true + info "Target background migration seqnum: $target_seqnum" + for ((i=0; i&1); then + db_seqnum=$(echo "$db_seqnum" | tr -d '[:space:]') + if [[ "$db_seqnum" -ge "$target_seqnum" ]] 2>/dev/null; then + info "Background migrations complete (seqnum: $db_seqnum)" + return 0 + fi + info "Background migrations not yet complete (seqnum: ${db_seqnum:-null}, target: $target_seqnum), attempt $((i+1))/$retries" + else + info "DB query failed (attempt $((i+1))/$retries): $db_seqnum" + fi + if (( i < retries - 1 )); then + sleep "$interval" + fi + done + die "Background migrations did not complete within $((retries * interval)) seconds" +} + +get_target_bg_migration_seqnum() { + grep -oP 'CurrentBgMigrationSeqNum\s*=\s*\K[0-9]+' \ + "$TEST_ROOT/central/backgroundmigrations/seq_num.go" +} + deploy_earlier_postgres_central() { info "Deploying: $EARLIER_TAG..." diff --git a/tests/upgrade/postgres_run.sh b/tests/upgrade/postgres_run.sh index 59a9e0c769849..cc1beeeca06eb 100755 --- a/tests/upgrade/postgres_run.sh +++ b/tests/upgrade/postgres_run.sh @@ -190,6 +190,7 @@ test_upgrade_paths() { kubectl -n stackrox set image deploy/central "*=$REGISTRY/main:$CURRENT_TAG" kubectl -n stackrox set image deploy/central-db "*=$REGISTRY/central-db:$CURRENT_TAG" wait_for_api + wait_for_background_migrations # Verify data is still there checkForPostgresAccessScopes @@ -221,6 +222,7 @@ test_upgrade_paths() { kubectl -n stackrox set image deploy/central-db "*=$REGISTRY/central-db:$CURRENT_TAG" wait_for_api + wait_for_background_migrations # Cleanup the scaled sensor before smoke tests helm uninstall -n stackrox stackrox-secured-cluster-services From 7548d93e549fe3c47d12c1b5401757af10509c3f Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 07:49:36 +0000 Subject: [PATCH 013/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 2dd16ee (#22359) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 2669e17a9b4a5..39feafa2c2f69 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 9e9e8e7156dd8..db546bbe5ede2 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index b89973a01b585..50b73602e26a5 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 1ad4f137fdeb0..68b264578f391 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 40bb845e7570e..fd39c0237e30e 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index e2b404c09a05b..d10db40f447ac 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 591f7298f2973..ef6002780997e 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index b512836f73313..2c05a1dcf70bf 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2d425bf6d8d292d31e641fa71a23a866d329f4339ba57bf84dd55dc55c4b1b67 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 - name: kind value: task resolver: bundles From 576d405e2609fa156ff9486da9f8c95dc4675f07 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:58:18 +0200 Subject: [PATCH 014/638] ROX-27910: Disable rollback strategy in operator (#22279) --- .../internal/reconciler/reconciler_factory.go | 4 + .../tests/reconcile/reconcile_suite_test.go | 6 +- operator/tests/reconcile/rollback_test.go | 244 ++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 operator/tests/reconcile/rollback_test.go diff --git a/operator/internal/reconciler/reconciler_factory.go b/operator/internal/reconciler/reconciler_factory.go index 5f15d564c4b17..e511338a233e5 100644 --- a/operator/internal/reconciler/reconciler_factory.go +++ b/operator/internal/reconciler/reconciler_factory.go @@ -94,6 +94,10 @@ func SetupReconcilerWithManager(mgr ctrl.Manager, gvk schema.GroupVersionKind, c return confighash.NewPodTemplateAnnotationPostRenderer(kubeClient, obj, renderCache) }, ), + // Disable rollbacks here. Failed installs/upgrades leave the release in a failed state. + // Recovery happens by correcting the spec so subsequent reconcile attempts succeed. + // See also https://github.com/operator-framework/helm-operator-plugins/issues/421. + client.WithFailureRollbacks(false), } actionClientGetter, err := client.NewActionClientGetter(actionConfigGetter, opts...) diff --git a/operator/tests/reconcile/reconcile_suite_test.go b/operator/tests/reconcile/reconcile_suite_test.go index e95a6d6d01c1a..c85f88b4764f8 100644 --- a/operator/tests/reconcile/reconcile_suite_test.go +++ b/operator/tests/reconcile/reconcile_suite_test.go @@ -7,6 +7,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + versionTestutils "github.com/stackrox/rox/pkg/version/testutils" + "go.uber.org/zap/zapcore" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -24,14 +26,16 @@ var ( ) func TestReconcileExtensions(t *testing.T) { + versionTestutils.SetExampleVersion(t) RegisterFailHandler(Fail) RunSpecs(t, "Reconcile Extensions Suite") } var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), zap.Level(zapcore.InfoLevel))) testEnv = &envtest.Environment{ AttachControlPlaneOutput: false, // set to true to see kube-apiserver and etcd logs + CRDDirectoryPaths: []string{"../../config/crd/bases"}, } var err error diff --git a/operator/tests/reconcile/rollback_test.go b/operator/tests/reconcile/rollback_test.go new file mode 100644 index 0000000000000..71ff26e0b9f7f --- /dev/null +++ b/operator/tests/reconcile/rollback_test.go @@ -0,0 +1,244 @@ +package reconcile + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + helmClient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "helm.sh/helm/v3/pkg/release" + corev1 "k8s.io/api/core/v1" + apiErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + "github.com/stackrox/rox/image" + platform "github.com/stackrox/rox/operator/api/v1alpha1" + centralTranslation "github.com/stackrox/rox/operator/internal/central/values/translation" + stackroxReconciler "github.com/stackrox/rox/operator/internal/reconciler" + "github.com/stackrox/rox/pkg/uuid" +) + +var _ = Describe("Upgrade failure with rollbacks disabled", func() { + const ( + centralName = "test-central" + ) + + var ( + namespace = "upgrade-failure-test-" + uuid.NewV4().String()[0:6] + namespacedCentralName = types.NamespacedName{Namespace: namespace, Name: centralName} + + mgr manager.Manager + ctx context.Context + cancel context.CancelFunc + ) + + BeforeEach(func() { + s := runtime.NewScheme() + Expect(clientgoscheme.AddToScheme(s)).To(Succeed()) + Expect(platform.AddToScheme(s)).To(Succeed()) + + var err error + mgr, err = manager.New(cfg, manager.Options{ + Scheme: s, + Metrics: server.Options{BindAddress: "0"}, + }) + Expect(err).NotTo(HaveOccurred()) + + translator := centralTranslation.New(mgr.GetClient()) + Expect(stackroxReconciler.SetupReconcilerWithManager( + mgr, platform.CentralGVK, image.CentralServicesChartPrefix, + translator, nil, + )).To(Succeed()) + + ctx, cancel = context.WithCancel(context.Background()) + go func() { + defer GinkgoRecover() + Expect(mgr.Start(ctx)).To(Succeed()) + }() + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + Expect(mgr.GetClient().Create(ctx, &ns)).To(Succeed()) + }) + + AfterEach(func() { + By("ensuring Central CR and test namespace is deleted") + ns := corev1.Namespace{} + if err := mgr.GetAPIReader().Get(ctx, types.NamespacedName{Name: namespace}, &ns); err == nil { + central := &platform.Central{} + if err := mgr.GetAPIReader().Get(ctx, namespacedCentralName, central); err == nil { + By("deleting finalizers") + central.SetFinalizers([]string{}) + Expect(mgr.GetClient().Update(ctx, central)).To(Succeed()) + By("marking CR for deletion") + Expect(mgr.GetClient().Delete(ctx, central)).To(Succeed()) + By("ensuring CR is gone") + central := platform.Central{} + Eventually(func(g Gomega) { + g.Expect(mgr.GetAPIReader().Get(ctx, namespacedCentralName, ¢ral)). + To(MatchError(apiErrors.IsNotFound, "IsNotFound")) + }, 10*time.Second, 1*time.Second).To(Succeed()) + } + By("cleaning up test namespace") + Expect(mgr.GetClient().Delete(ctx, &ns)).To(Succeed()) + } + + cancel() // Stops manager. + }) + + It("should not attempt rollback after a failed upgrade, preserving PVCs", func() { + pvcName := types.NamespacedName{Namespace: namespace, Name: "scanner-v4-db"} + var pvcUid types.UID + central := &platform.Central{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralName, + Namespace: namespace, + }, + Spec: platform.CentralSpec{ + ScannerV4: &platform.ScannerV4Spec{ + ScannerComponent: new(platform.ScannerV4ComponentEnabled), + }, + }, + } + + By("creating a Central CR with Scanner V4 enabled") + createCentral(mgr, ctx, central) + + By("waiting for initial install to complete") + waitForInstall(mgr, ctx, namespacedCentralName) + + By("simulating PVC binding by setting spec.volumeName") + simulatePVCBinding(mgr, ctx, pvcName, &pvcUid) + + By("applying an invalid overlay to trigger upgrade failure") + applyInvalidOverlay(mgr, ctx, namespacedCentralName) + + By("waiting for upgrade failure without rollback attempt") + waitForUpgradeFailure(mgr, ctx, namespacedCentralName) + + By("verifying no rollback revision in Helm release history") + verifyNoRollbackRevision(mgr, ctx, central, centralName) + + By("repairing the custom resource, which failed reconciliation") + repairCR(mgr, ctx, central, namespacedCentralName) + + By("waiting for reconciliation to succeed again") + waitForRecovery(mgr, ctx, namespacedCentralName) + + By("verifying PVC is still intact") + verifyPVCIntact(mgr, ctx, pvcName, pvcUid) + }) +}) + +func createCentral(mgr manager.Manager, ctx context.Context, central *platform.Central) { + Expect(mgr.GetClient().Create(ctx, central)).To(Succeed()) +} + +func waitForInstall(mgr manager.Manager, ctx context.Context, key types.NamespacedName) { + Eventually(func(g Gomega) { + c := &platform.Central{} + g.Expect(mgr.GetAPIReader().Get(ctx, key, c)).To(Succeed()) + g.Expect(c.GetCondition(platform.ConditionDeployed)).To( + SatisfyAll(Not(BeNil()), HaveField("Status", Equal(platform.StatusTrue))), + "Deployed condition should be True after successful install") + }, 30*time.Second, 2*time.Second).Should(Succeed()) +} + +func simulatePVCBinding(mgr manager.Manager, ctx context.Context, pvcName types.NamespacedName, pvcUid *types.UID) { + Eventually(func(g Gomega) { + pvc := &corev1.PersistentVolumeClaim{} + g.Expect(mgr.GetAPIReader().Get(ctx, pvcName, pvc)).To(Succeed()) + pvc.Spec.VolumeName = "fake-pv" + g.Expect(mgr.GetClient().Update(ctx, pvc)).To(Succeed()) + *pvcUid = pvc.GetUID() + }, 10*time.Second, 1*time.Second).Should(Succeed()) +} + +func applyInvalidOverlay(mgr manager.Manager, ctx context.Context, key types.NamespacedName) { + Eventually(func(g Gomega) { + c := &platform.Central{} + g.Expect(mgr.GetAPIReader().Get(ctx, key, c)).To(Succeed()) + c.Spec.Overlays = []*platform.K8sObjectOverlay{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "scanner-v4-matcher-config", + Patches: []*platform.K8sObjectOverlayPatch{ + { + Path: `data.config\.yaml`, + Value: "matcher:\n vulnerabilities_url: \"https://bad\"", // This will be unmarshalled as a map. + }, + }, + }, + } + g.Expect(mgr.GetClient().Update(ctx, c)).To(Succeed()) + }, 30*time.Second, 1*time.Second).Should(Succeed()) +} + +func waitForUpgradeFailure(mgr manager.Manager, ctx context.Context, key types.NamespacedName) { + Eventually(func(g Gomega) { + c := &platform.Central{} + g.Expect(mgr.GetAPIReader().Get(ctx, key, c)).To(Succeed()) + + releaseFailed := c.GetCondition(platform.ConditionReleaseFailed) + irreconcilable := c.GetCondition(platform.ConditionIrreconcilable) + + g.Expect(releaseFailed).To(SatisfyAll(Not(BeNil()), HaveField("Status", Equal(platform.StatusTrue))), + "ReleaseFailed condition should be True") + g.Expect(irreconcilable).To(SatisfyAll(Not(BeNil()), HaveField("Status", Equal(platform.StatusTrue))), + "Irreconcilable condition should be True") + }, 30*time.Second, 1*time.Second).Should(Succeed()) +} + +func verifyNoRollbackRevision(mgr manager.Manager, ctx context.Context, central *platform.Central, releaseName string) { + actionConfigGetter, err := helmClient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper()) + Expect(err).NotTo(HaveOccurred()) + acfg, err := actionConfigGetter.ActionConfigFor(ctx, central) + Expect(err).NotTo(HaveOccurred()) + history, err := acfg.Releases.History(releaseName) + Expect(err).NotTo(HaveOccurred()) + for _, rel := range history { + Expect(rel.Info.Status).NotTo(Equal(release.StatusPendingRollback), + "release v%d should not have pending-rollback status", rel.Version) + Expect(rel.Info.Description).NotTo(MatchRegexp(`(?i)rollback`), + "release v%d description %q suggests a rollback was attempted", rel.Version, rel.Info.Description) + } +} + +func repairCR(mgr manager.Manager, ctx context.Context, central *platform.Central, key types.NamespacedName) { + Eventually(func(g Gomega) { + g.Expect(mgr.GetAPIReader().Get(ctx, key, central)).To(Succeed()) + central.Spec.Overlays = []*platform.K8sObjectOverlay{} + g.Expect(mgr.GetClient().Update(ctx, central)).To(Succeed()) + }, 10*time.Second, 1*time.Second).Should(Succeed()) +} + +func waitForRecovery(mgr manager.Manager, ctx context.Context, key types.NamespacedName) { + Eventually(func(g Gomega) { + c := &platform.Central{} + g.Expect(mgr.GetAPIReader().Get(ctx, key, c)).To(Succeed()) + + releaseFailed := c.GetCondition(platform.ConditionReleaseFailed) + irreconcilable := c.GetCondition(platform.ConditionIrreconcilable) + + g.Expect(c.GetCondition(platform.ConditionDeployed)).To( + SatisfyAll(Not(BeNil()), HaveField("Status", Equal(platform.StatusTrue))), + "Deployed condition should be True again") + g.Expect(releaseFailed).To(SatisfyAny(BeNil(), HaveField("Status", Equal(platform.StatusFalse))), + "ReleaseFailed condition should be nil or False") + g.Expect(irreconcilable).To(SatisfyAny(BeNil(), HaveField("Status", Equal(platform.StatusFalse))), + "Irreconcilable condition should be nil or False") + }, 2*time.Minute, 2*time.Second).Should(Succeed()) +} + +func verifyPVCIntact(mgr manager.Manager, ctx context.Context, pvcName types.NamespacedName, pvcUid types.UID) { + pvc := &corev1.PersistentVolumeClaim{} + Expect(mgr.GetAPIReader().Get(ctx, pvcName, pvc)).To(Succeed()) + Expect(pvc.Spec.VolumeName).To(Equal("fake-pv")) + Expect(pvc.GetUID()).To(Equal(pvcUid)) +} From 7368bb34fd3d4be3133e2ddec4d43ced1655914d Mon Sep 17 00:00:00 2001 From: Stephan Hesselmann Date: Thu, 20 Aug 2026 10:14:49 +0200 Subject: [PATCH 015/638] ROX-35887: switch rhtas from staging central to testing services (#22347) --- .../ImageSignatureVerificationTest.groovy | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy b/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy index f45330daf0a3a..d7939df593b29 100644 --- a/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy +++ b/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy @@ -143,34 +143,37 @@ nzTe7BpOmVwmqLkIefEJe5L4PSXtp2KFLZqGO/kY5A== static final private String KEYLESS_SIGSTORE_IDENTITY = ".*@redhat.com" static final private String KEYLESS_SIGSTORE_REKOR_URL = "https://rekor.sigstore.dev" + // RHTAS instance is hosted on + // [testing services cluster]( + // https://console-openshift-console.apps.rosa.c3a7d6d7i1o8i5l.427x.p3.openshiftapps.com + // ). static final private String KEYLESS_RHTAS_ISSUER = "https://accounts.google.com" static final private String KEYLESS_RHTAS_IDENTITY = ".*@redhat.com" static final private String KEYLESS_RHTAS_FULCIO_CHAIN = """\ -----BEGIN CERTIFICATE----- -MIICBDCCAYqgAwIBAgIUNo8BCDFZXeig9JJONBUirNKTPW0wCgYIKoZIzj0EAwMw +MIICAzCCAYqgAwIBAgIUctVXHIIauBVird+Ti/YJjvX8hpowCgYIKoZIzj0EAwMw LDEQMA4GA1UEChMHUmVkIEhhdDEYMBYGA1UEAxMPZnVsY2lvLmhvc3RuYW1lMB4X -DTI1MDQwOTE1MzgzMFoXDTM1MDQwNzE1MzgzMFowLDEQMA4GA1UEChMHUmVkIEhh +DTI2MDcyNDE0MzkwMloXDTM2MDcyMTE0MzkwMlowLDEQMA4GA1UEChMHUmVkIEhh dDEYMBYGA1UEAxMPZnVsY2lvLmhvc3RuYW1lMHYwEAYHKoZIzj0CAQYFK4EEACID -YgAEBUJFBeglXU9zgd34suFRG8FIWz3eNChxgd5vcAI22LJvT0dhDLxOE/W1h0f+ -jRa9jM9V0EeYRpMQ6SKhbOu5mv9dTJ1d36f0e1iHQAPKDXq09D5mDcwZmR7uiaE8 -rMPjo20wazAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUNoDRLK7bVIxt7tLbETIvDzdJ5hAwKQYDVR0RBCIwIIEeYWNzLXRlYW0tYXV0 -b21hdGlvbkByZWRoYXQuY29tMAoGCCqGSM49BAMDA2gAMGUCMG8sro032ep9lnOx -XaZsqx+Vjb6dGmJmFQbPZX9EZxgZxG1n50EnLi/xTMxR98z4HgIxAI4ViQ6pd+6r -ceLzLr4eKGR5yqqoWWciLF5Che/Cfqgma3jSRxbiL2urMRS3Y7038g== +YgAEozLgKDyvO+qnl3PZNnspzjY4xgBd1bMeyLZwKIPH19bRuCSGgJPVUizTdrkb +mOXiGyHw7w97XLITtbEOSi7mIEhc+3CuvE2nq/mfWHQQRsgm6yOx1EDh1hhiBkuD +asNuo20wazAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUNqwE3qpbn7ZYcF8yWCigAzsMfRcwKQYDVR0RBCIwIIEeYWNzLXRlYW0tYXV0 +b21hdGlvbkByZWRoYXQuY29tMAoGCCqGSM49BAMDA2cAMGQCMFYVSgoPjQzhxBx+ +vpgZb5CUJwUZDHCvp9PPN/Kz48+ELC0kDF90KbTJv0Tj0GjnzAIwPl5EVmjV/Hoy +yri0BdxRThPV8+/wTrDCqsA1bOlgOYzAzEAZqTNTvnLinNzf7yQ5 -----END CERTIFICATE-----""" static final private String KEYLESS_RHTAS_CTLOG_PUBLIC_KEY = """\ -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHOWcrgffC8A4CwsPqmfy+unEU1km -SgPCnfCzwRToJ9263qyEp+3aaCv0T4QicC31fsokxoUIGzK0Ftrt3SoXDw== +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8ewvXPAw43N3ULmsN8YIYDh47rZu +0EXuZ06hVeddfdxbRzHehpWIVNUO1zzl7wFpqPPYAffxxDmDDkNmy8cB6Q== -----END PUBLIC KEY-----""" static final private String KEYLESS_RHTAS_REKOR_URL = - "https://rekor-server-trusted-artifact-signer.apps.staging-central.services.rox.systems" + "https://rekor-server-trusted-artifact-signer.apps.rosa.c3a7d6d7i1o8i5l.427x.p3.openshiftapps.com" static final private String KEYLESS_RHTAS_REKOR_PUBLIC_KEY = """\ -----BEGIN PUBLIC KEY----- -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECsUdy4SYw0Wey6iYiJ7uArUqHhKTwpRn -YTuoC8lh1tt0nLkIQpdAJMuWndZJkRHcZriW1Qc2l3Mau0DtuYK17uz7pEwci+tK -5mll4EcDCwjeQyH0cXjCdn9gXfIDFjg/ +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAFH7J1JajguG/zJ6CsUWIbKau0/A +l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== -----END PUBLIC KEY-----""" static final private String DISTROLESS_IMAGE_DIGEST = From 24b7600507c7f57c6cc9d5a53a0781b0a516cf15 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:32:08 +0200 Subject: [PATCH 016/638] ROX-35685: add SyncRepoCPEMapping proto + shared mapping helpers (#22140) Co-authored-by: Cursor --- .../virtualmachine/v1/vm_service.pb.go | 340 +++++- .../v1/vm_service_vtproto.pb.go | 1058 ++++++++++++++++- pkg/virtualmachine/cpemapping/validate.go | 40 + .../cpemapping/validate_test.go | 62 + .../virtualmachine/v1/vm_service.proto | 26 + 5 files changed, 1410 insertions(+), 116 deletions(-) create mode 100644 pkg/virtualmachine/cpemapping/validate.go create mode 100644 pkg/virtualmachine/cpemapping/validate_test.go diff --git a/generated/internalapi/virtualmachine/v1/vm_service.pb.go b/generated/internalapi/virtualmachine/v1/vm_service.pb.go index 247cb13a9b7f9..074fa178915c0 100644 --- a/generated/internalapi/virtualmachine/v1/vm_service.pb.go +++ b/generated/internalapi/virtualmachine/v1/vm_service.pb.go @@ -23,6 +23,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type RepoCPEMappingUpdatePath int32 + +const ( + RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED RepoCPEMappingUpdatePath = 0 + RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR RepoCPEMappingUpdatePath = 1 + RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL RepoCPEMappingUpdatePath = 2 +) + +// Enum value maps for RepoCPEMappingUpdatePath. +var ( + RepoCPEMappingUpdatePath_name = map[int32]string{ + 0: "REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED", + 1: "REPO_CPE_MAPPING_UPDATE_PATH_SENSOR", + 2: "REPO_CPE_MAPPING_UPDATE_PATH_URL", + } + RepoCPEMappingUpdatePath_value = map[string]int32{ + "REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED": 0, + "REPO_CPE_MAPPING_UPDATE_PATH_SENSOR": 1, + "REPO_CPE_MAPPING_UPDATE_PATH_URL": 2, + } +) + +func (x RepoCPEMappingUpdatePath) Enum() *RepoCPEMappingUpdatePath { + p := new(RepoCPEMappingUpdatePath) + *p = x + return p +} + +func (x RepoCPEMappingUpdatePath) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RepoCPEMappingUpdatePath) Descriptor() protoreflect.EnumDescriptor { + return file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[0].Descriptor() +} + +func (RepoCPEMappingUpdatePath) Type() protoreflect.EnumType { + return &file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[0] +} + +func (x RepoCPEMappingUpdatePath) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RepoCPEMappingUpdatePath.Descriptor instead. +func (RepoCPEMappingUpdatePath) EnumDescriptor() ([]byte, []int) { + return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{0} +} + type ErrorCode int32 const ( @@ -41,6 +90,10 @@ const ( ErrorCode_ERROR_CODE_REQUEST_TOO_LARGE ErrorCode = 5 // Agent is already serving another connection; retry after a backoff. ErrorCode_ERROR_CODE_BUSY ErrorCode = 6 + // No usable mapping yet; guest disk has not been scanned. + ErrorCode_ERROR_CODE_MAPPING_REQUIRED ErrorCode = 7 + // SyncRepoCPEMapping rejected because updater is not Sensor-managed. + ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED ErrorCode = 8 ) // Enum value maps for ErrorCode. @@ -53,15 +106,19 @@ var ( 4: "ERROR_CODE_MALFORMED_REQUEST", 5: "ERROR_CODE_REQUEST_TOO_LARGE", 6: "ERROR_CODE_BUSY", + 7: "ERROR_CODE_MAPPING_REQUIRED", + 8: "ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED", } ErrorCode_value = map[string]int32{ - "ERROR_CODE_UNSPECIFIED": 0, - "ERROR_CODE_UNKNOWN_METHOD": 1, - "ERROR_CODE_NOT_READY": 2, - "ERROR_CODE_INTERNAL": 3, - "ERROR_CODE_MALFORMED_REQUEST": 4, - "ERROR_CODE_REQUEST_TOO_LARGE": 5, - "ERROR_CODE_BUSY": 6, + "ERROR_CODE_UNSPECIFIED": 0, + "ERROR_CODE_UNKNOWN_METHOD": 1, + "ERROR_CODE_NOT_READY": 2, + "ERROR_CODE_INTERNAL": 3, + "ERROR_CODE_MALFORMED_REQUEST": 4, + "ERROR_CODE_REQUEST_TOO_LARGE": 5, + "ERROR_CODE_BUSY": 6, + "ERROR_CODE_MAPPING_REQUIRED": 7, + "ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED": 8, } ) @@ -76,11 +133,11 @@ func (x ErrorCode) String() string { } func (ErrorCode) Descriptor() protoreflect.EnumDescriptor { - return file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[0].Descriptor() + return file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[1].Descriptor() } func (ErrorCode) Type() protoreflect.EnumType { - return &file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[0] + return &file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes[1] } func (x ErrorCode) Number() protoreflect.EnumNumber { @@ -89,7 +146,7 @@ func (x ErrorCode) Number() protoreflect.EnumNumber { // Deprecated: Use ErrorCode.Descriptor instead. func (ErrorCode) EnumDescriptor() ([]byte, []int) { - return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{0} + return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{1} } // VMServiceRequest is sent by Sensor to roxagent over a length-prefixed VSOCK frame. @@ -100,6 +157,7 @@ type VMServiceRequest struct { // Types that are valid to be assigned to Method: // // *VMServiceRequest_GetReport + // *VMServiceRequest_SyncRepoCpeMapping Method isVMServiceRequest_Method `protobuf_oneof:"method"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -158,6 +216,15 @@ func (x *VMServiceRequest) GetGetReport() *GetReportRequest { return nil } +func (x *VMServiceRequest) GetSyncRepoCpeMapping() *SyncRepoCPEMappingRequest { + if x != nil { + if x, ok := x.Method.(*VMServiceRequest_SyncRepoCpeMapping); ok { + return x.SyncRepoCpeMapping + } + } + return nil +} + type isVMServiceRequest_Method interface { isVMServiceRequest_Method() } @@ -166,8 +233,14 @@ type VMServiceRequest_GetReport struct { GetReport *GetReportRequest `protobuf:"bytes,2,opt,name=get_report,json=getReport,proto3,oneof"` } +type VMServiceRequest_SyncRepoCpeMapping struct { + SyncRepoCpeMapping *SyncRepoCPEMappingRequest `protobuf:"bytes,3,opt,name=sync_repo_cpe_mapping,json=syncRepoCpeMapping,proto3,oneof"` +} + func (*VMServiceRequest_GetReport) isVMServiceRequest_Method() {} +func (*VMServiceRequest_SyncRepoCpeMapping) isVMServiceRequest_Method() {} + // VMServiceResponse is returned by roxagent. Contains either a successful result // or an ErrorResponse, never both. type VMServiceResponse struct { @@ -177,6 +250,7 @@ type VMServiceResponse struct { // // *VMServiceResponse_GetReport // *VMServiceResponse_Error + // *VMServiceResponse_SyncRepoCpeMapping Result isVMServiceResponse_Result `protobuf_oneof:"result"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -244,6 +318,15 @@ func (x *VMServiceResponse) GetError() *ErrorResponse { return nil } +func (x *VMServiceResponse) GetSyncRepoCpeMapping() *SyncRepoCPEMappingResponse { + if x != nil { + if x, ok := x.Result.(*VMServiceResponse_SyncRepoCpeMapping); ok { + return x.SyncRepoCpeMapping + } + } + return nil +} + type isVMServiceResponse_Result interface { isVMServiceResponse_Result() } @@ -256,10 +339,16 @@ type VMServiceResponse_Error struct { Error *ErrorResponse `protobuf:"bytes,3,opt,name=error,proto3,oneof"` } +type VMServiceResponse_SyncRepoCpeMapping struct { + SyncRepoCpeMapping *SyncRepoCPEMappingResponse `protobuf:"bytes,4,opt,name=sync_repo_cpe_mapping,json=syncRepoCpeMapping,proto3,oneof"` +} + func (*VMServiceResponse_GetReport) isVMServiceResponse_Result() {} func (*VMServiceResponse_Error) isVMServiceResponse_Result() {} +func (*VMServiceResponse_SyncRepoCpeMapping) isVMServiceResponse_Result() {} + type RequestMeta struct { state protoimpl.MessageState `protogen:"open.v1"` // Correlation ID (UUID) for tracing across Sensor and agent logs. @@ -346,9 +435,14 @@ type ResponseMeta struct { // false-negative dedup when a restarted agent's reset generation coincides // with Sensor's cached value for that VM. Zero means the agent predates this // field; Sensor falls back to generation-only comparison in that case. - Epoch uint32 `protobuf:"varint,6,opt,name=epoch,proto3" json:"epoch,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Epoch uint32 `protobuf:"varint,6,opt,name=epoch,proto3" json:"epoch,omitempty"` + // XXH64 hex of active mapping; empty string means none. optional so absence + // means an older agent that does not report mapping metadata. + RepoCpeMappingHash *string `protobuf:"bytes,7,opt,name=repo_cpe_mapping_hash,json=repoCpeMappingHash,proto3,oneof" json:"repo_cpe_mapping_hash,omitempty"` + // SENSOR or URL only; UNSPECIFIED means Sensor must not sync. + RepoCpeMappingUpdatePath *RepoCPEMappingUpdatePath `protobuf:"varint,8,opt,name=repo_cpe_mapping_update_path,json=repoCpeMappingUpdatePath,proto3,enum=virtualmachine.v1.RepoCPEMappingUpdatePath,oneof" json:"repo_cpe_mapping_update_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ResponseMeta) Reset() { @@ -423,6 +517,20 @@ func (x *ResponseMeta) GetEpoch() uint32 { return 0 } +func (x *ResponseMeta) GetRepoCpeMappingHash() string { + if x != nil && x.RepoCpeMappingHash != nil { + return *x.RepoCpeMappingHash + } + return "" +} + +func (x *ResponseMeta) GetRepoCpeMappingUpdatePath() RepoCPEMappingUpdatePath { + if x != nil && x.RepoCpeMappingUpdatePath != nil { + return *x.RepoCpeMappingUpdatePath + } + return RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED +} + type GetReportRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Generation last observed by Sensor for this VM (see ResponseMeta.report_generation). @@ -540,6 +648,95 @@ func (x *GetReportResponse) GetUnchanged() bool { return false } +type SyncRepoCPEMappingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mapping []byte `protobuf:"bytes,1,opt,name=mapping,proto3" json:"mapping,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncRepoCPEMappingRequest) Reset() { + *x = SyncRepoCPEMappingRequest{} + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncRepoCPEMappingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncRepoCPEMappingRequest) ProtoMessage() {} + +func (x *SyncRepoCPEMappingRequest) ProtoReflect() protoreflect.Message { + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncRepoCPEMappingRequest.ProtoReflect.Descriptor instead. +func (*SyncRepoCPEMappingRequest) Descriptor() ([]byte, []int) { + return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{6} +} + +func (x *SyncRepoCPEMappingRequest) GetMapping() []byte { + if x != nil { + return x.Mapping + } + return nil +} + +type SyncRepoCPEMappingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // True when the agent accepted new content (applied or staged). + Updated bool `protobuf:"varint,1,opt,name=updated,proto3" json:"updated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncRepoCPEMappingResponse) Reset() { + *x = SyncRepoCPEMappingResponse{} + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncRepoCPEMappingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncRepoCPEMappingResponse) ProtoMessage() {} + +func (x *SyncRepoCPEMappingResponse) ProtoReflect() protoreflect.Message { + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncRepoCPEMappingResponse.ProtoReflect.Descriptor instead. +func (*SyncRepoCPEMappingResponse) Descriptor() ([]byte, []int) { + return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{7} +} + +func (x *SyncRepoCPEMappingResponse) GetUpdated() bool { + if x != nil { + return x.Updated + } + return false +} + type ErrorResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Code ErrorCode `protobuf:"varint,1,opt,name=code,proto3,enum=virtualmachine.v1.ErrorCode" json:"code,omitempty"` @@ -553,7 +750,7 @@ type ErrorResponse struct { func (x *ErrorResponse) Reset() { *x = ErrorResponse{} - mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[6] + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -565,7 +762,7 @@ func (x *ErrorResponse) String() string { func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[6] + mi := &file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -578,7 +775,7 @@ func (x *ErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { - return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{6} + return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{8} } func (x *ErrorResponse) GetCode() ErrorCode { @@ -606,17 +803,19 @@ var File_internalapi_virtualmachine_v1_vm_service_proto protoreflect.FileDescrip const file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc = "" + "\n" + - ".internalapi/virtualmachine/v1/vm_service.proto\x12\x11virtualmachine.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a)internalapi/scanner/v4/index_report.proto\"\x96\x01\n" + + ".internalapi/virtualmachine/v1/vm_service.proto\x12\x11virtualmachine.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a)internalapi/scanner/v4/index_report.proto\"\xf9\x01\n" + "\x10VMServiceRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.virtualmachine.v1.RequestMetaR\x04meta\x12D\n" + "\n" + - "get_report\x18\x02 \x01(\v2#.virtualmachine.v1.GetReportRequestH\x00R\tgetReportB\b\n" + - "\x06method\"\xd3\x01\n" + + "get_report\x18\x02 \x01(\v2#.virtualmachine.v1.GetReportRequestH\x00R\tgetReport\x12a\n" + + "\x15sync_repo_cpe_mapping\x18\x03 \x01(\v2,.virtualmachine.v1.SyncRepoCPEMappingRequestH\x00R\x12syncRepoCpeMappingB\b\n" + + "\x06method\"\xb7\x02\n" + "\x11VMServiceResponse\x123\n" + "\x04meta\x18\x01 \x01(\v2\x1f.virtualmachine.v1.ResponseMetaR\x04meta\x12E\n" + "\n" + "get_report\x18\x02 \x01(\v2$.virtualmachine.v1.GetReportResponseH\x00R\tgetReport\x128\n" + - "\x05error\x18\x03 \x01(\v2 .virtualmachine.v1.ErrorResponseH\x00R\x05errorB\b\n" + + "\x05error\x18\x03 \x01(\v2 .virtualmachine.v1.ErrorResponseH\x00R\x05error\x12b\n" + + "\x15sync_repo_cpe_mapping\x18\x04 \x01(\v2-.virtualmachine.v1.SyncRepoCPEMappingResponseH\x00R\x12syncRepoCpeMappingB\b\n" + "\x06result\"\xcb\x01\n" + "\vRequestMeta\x12\x1d\n" + "\n" + @@ -626,32 +825,44 @@ const file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc = "" + "\n" + "FactsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xeb\x02\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd0\x04\n" + "\fResponseMeta\x12#\n" + "\ragent_version\x18\x01 \x01(\tR\fagentVersion\x12J\n" + "\x13report_generated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x11reportGeneratedAt\x12+\n" + "\x11report_generation\x18\x03 \x01(\rR\x10reportGeneration\x12+\n" + "\x11supported_methods\x18\x04 \x03(\tR\x10supportedMethods\x12@\n" + "\x05facts\x18\x05 \x03(\v2*.virtualmachine.v1.ResponseMeta.FactsEntryR\x05facts\x12\x14\n" + - "\x05epoch\x18\x06 \x01(\rR\x05epoch\x1a8\n" + + "\x05epoch\x18\x06 \x01(\rR\x05epoch\x126\n" + + "\x15repo_cpe_mapping_hash\x18\a \x01(\tH\x00R\x12repoCpeMappingHash\x88\x01\x01\x12p\n" + + "\x1crepo_cpe_mapping_update_path\x18\b \x01(\x0e2+.virtualmachine.v1.RepoCPEMappingUpdatePathH\x01R\x18repoCpeMappingUpdatePath\x88\x01\x01\x1a8\n" + "\n" + "FactsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"g\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x18\n" + + "\x16_repo_cpe_mapping_hashB\x1f\n" + + "\x1d_repo_cpe_mapping_update_path\"g\n" + "\x10GetReportRequest\x122\n" + "\x15last_known_generation\x18\x01 \x01(\rR\x13lastKnownGeneration\x12\x1f\n" + "\vknown_epoch\x18\x02 \x01(\rR\n" + "knownEpoch\"m\n" + "\x11GetReportResponse\x12:\n" + "\findex_report\x18\x01 \x01(\v2\x17.scanner.v4.IndexReportR\vindexReport\x12\x1c\n" + - "\tunchanged\x18\x02 \x01(\bR\tunchanged\"\xe0\x01\n" + + "\tunchanged\x18\x02 \x01(\bR\tunchanged\"5\n" + + "\x19SyncRepoCPEMappingRequest\x12\x18\n" + + "\amapping\x18\x01 \x01(\fR\amapping\"6\n" + + "\x1aSyncRepoCPEMappingResponse\x12\x18\n" + + "\aupdated\x18\x01 \x01(\bR\aupdated\"\xe0\x01\n" + "\rErrorResponse\x120\n" + "\x04code\x18\x01 \x01(\x0e2\x1c.virtualmachine.v1.ErrorCodeR\x04code\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12G\n" + "\adetails\x18\x03 \x03(\v2-.virtualmachine.v1.ErrorResponse.DetailsEntryR\adetails\x1a:\n" + "\fDetailsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\xd2\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\x97\x01\n" + + "\x18RepoCPEMappingUpdatePath\x12,\n" + + "(REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED\x10\x00\x12'\n" + + "#REPO_CPE_MAPPING_UPDATE_PATH_SENSOR\x10\x01\x12$\n" + + " REPO_CPE_MAPPING_UPDATE_PATH_URL\x10\x02*\x9e\x02\n" + "\tErrorCode\x12\x1a\n" + "\x16ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19ERROR_CODE_UNKNOWN_METHOD\x10\x01\x12\x18\n" + @@ -659,7 +870,9 @@ const file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc = "" + "\x13ERROR_CODE_INTERNAL\x10\x03\x12 \n" + "\x1cERROR_CODE_MALFORMED_REQUEST\x10\x04\x12 \n" + "\x1cERROR_CODE_REQUEST_TOO_LARGE\x10\x05\x12\x13\n" + - "\x0fERROR_CODE_BUSY\x10\x06B$Z\"./internalapi/virtualmachine/v1;v1b\x06proto3" + "\x0fERROR_CODE_BUSY\x10\x06\x12\x1f\n" + + "\x1bERROR_CODE_MAPPING_REQUIRED\x10\a\x12)\n" + + "%ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED\x10\bB$Z\"./internalapi/virtualmachine/v1;v1b\x06proto3" var ( file_internalapi_virtualmachine_v1_vm_service_proto_rawDescOnce sync.Once @@ -673,40 +886,46 @@ func file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP() []byte { return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescData } -var file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_internalapi_virtualmachine_v1_vm_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_internalapi_virtualmachine_v1_vm_service_proto_goTypes = []any{ - (ErrorCode)(0), // 0: virtualmachine.v1.ErrorCode - (*VMServiceRequest)(nil), // 1: virtualmachine.v1.VMServiceRequest - (*VMServiceResponse)(nil), // 2: virtualmachine.v1.VMServiceResponse - (*RequestMeta)(nil), // 3: virtualmachine.v1.RequestMeta - (*ResponseMeta)(nil), // 4: virtualmachine.v1.ResponseMeta - (*GetReportRequest)(nil), // 5: virtualmachine.v1.GetReportRequest - (*GetReportResponse)(nil), // 6: virtualmachine.v1.GetReportResponse - (*ErrorResponse)(nil), // 7: virtualmachine.v1.ErrorResponse - nil, // 8: virtualmachine.v1.RequestMeta.FactsEntry - nil, // 9: virtualmachine.v1.ResponseMeta.FactsEntry - nil, // 10: virtualmachine.v1.ErrorResponse.DetailsEntry - (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp - (*v4.IndexReport)(nil), // 12: scanner.v4.IndexReport + (RepoCPEMappingUpdatePath)(0), // 0: virtualmachine.v1.RepoCPEMappingUpdatePath + (ErrorCode)(0), // 1: virtualmachine.v1.ErrorCode + (*VMServiceRequest)(nil), // 2: virtualmachine.v1.VMServiceRequest + (*VMServiceResponse)(nil), // 3: virtualmachine.v1.VMServiceResponse + (*RequestMeta)(nil), // 4: virtualmachine.v1.RequestMeta + (*ResponseMeta)(nil), // 5: virtualmachine.v1.ResponseMeta + (*GetReportRequest)(nil), // 6: virtualmachine.v1.GetReportRequest + (*GetReportResponse)(nil), // 7: virtualmachine.v1.GetReportResponse + (*SyncRepoCPEMappingRequest)(nil), // 8: virtualmachine.v1.SyncRepoCPEMappingRequest + (*SyncRepoCPEMappingResponse)(nil), // 9: virtualmachine.v1.SyncRepoCPEMappingResponse + (*ErrorResponse)(nil), // 10: virtualmachine.v1.ErrorResponse + nil, // 11: virtualmachine.v1.RequestMeta.FactsEntry + nil, // 12: virtualmachine.v1.ResponseMeta.FactsEntry + nil, // 13: virtualmachine.v1.ErrorResponse.DetailsEntry + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*v4.IndexReport)(nil), // 15: scanner.v4.IndexReport } var file_internalapi_virtualmachine_v1_vm_service_proto_depIdxs = []int32{ - 3, // 0: virtualmachine.v1.VMServiceRequest.meta:type_name -> virtualmachine.v1.RequestMeta - 5, // 1: virtualmachine.v1.VMServiceRequest.get_report:type_name -> virtualmachine.v1.GetReportRequest - 4, // 2: virtualmachine.v1.VMServiceResponse.meta:type_name -> virtualmachine.v1.ResponseMeta - 6, // 3: virtualmachine.v1.VMServiceResponse.get_report:type_name -> virtualmachine.v1.GetReportResponse - 7, // 4: virtualmachine.v1.VMServiceResponse.error:type_name -> virtualmachine.v1.ErrorResponse - 8, // 5: virtualmachine.v1.RequestMeta.facts:type_name -> virtualmachine.v1.RequestMeta.FactsEntry - 11, // 6: virtualmachine.v1.ResponseMeta.report_generated_at:type_name -> google.protobuf.Timestamp - 9, // 7: virtualmachine.v1.ResponseMeta.facts:type_name -> virtualmachine.v1.ResponseMeta.FactsEntry - 12, // 8: virtualmachine.v1.GetReportResponse.index_report:type_name -> scanner.v4.IndexReport - 0, // 9: virtualmachine.v1.ErrorResponse.code:type_name -> virtualmachine.v1.ErrorCode - 10, // 10: virtualmachine.v1.ErrorResponse.details:type_name -> virtualmachine.v1.ErrorResponse.DetailsEntry - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 4, // 0: virtualmachine.v1.VMServiceRequest.meta:type_name -> virtualmachine.v1.RequestMeta + 6, // 1: virtualmachine.v1.VMServiceRequest.get_report:type_name -> virtualmachine.v1.GetReportRequest + 8, // 2: virtualmachine.v1.VMServiceRequest.sync_repo_cpe_mapping:type_name -> virtualmachine.v1.SyncRepoCPEMappingRequest + 5, // 3: virtualmachine.v1.VMServiceResponse.meta:type_name -> virtualmachine.v1.ResponseMeta + 7, // 4: virtualmachine.v1.VMServiceResponse.get_report:type_name -> virtualmachine.v1.GetReportResponse + 10, // 5: virtualmachine.v1.VMServiceResponse.error:type_name -> virtualmachine.v1.ErrorResponse + 9, // 6: virtualmachine.v1.VMServiceResponse.sync_repo_cpe_mapping:type_name -> virtualmachine.v1.SyncRepoCPEMappingResponse + 11, // 7: virtualmachine.v1.RequestMeta.facts:type_name -> virtualmachine.v1.RequestMeta.FactsEntry + 14, // 8: virtualmachine.v1.ResponseMeta.report_generated_at:type_name -> google.protobuf.Timestamp + 12, // 9: virtualmachine.v1.ResponseMeta.facts:type_name -> virtualmachine.v1.ResponseMeta.FactsEntry + 0, // 10: virtualmachine.v1.ResponseMeta.repo_cpe_mapping_update_path:type_name -> virtualmachine.v1.RepoCPEMappingUpdatePath + 15, // 11: virtualmachine.v1.GetReportResponse.index_report:type_name -> scanner.v4.IndexReport + 1, // 12: virtualmachine.v1.ErrorResponse.code:type_name -> virtualmachine.v1.ErrorCode + 13, // 13: virtualmachine.v1.ErrorResponse.details:type_name -> virtualmachine.v1.ErrorResponse.DetailsEntry + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_internalapi_virtualmachine_v1_vm_service_proto_init() } @@ -716,18 +935,21 @@ func file_internalapi_virtualmachine_v1_vm_service_proto_init() { } file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[0].OneofWrappers = []any{ (*VMServiceRequest_GetReport)(nil), + (*VMServiceRequest_SyncRepoCpeMapping)(nil), } file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[1].OneofWrappers = []any{ (*VMServiceResponse_GetReport)(nil), (*VMServiceResponse_Error)(nil), + (*VMServiceResponse_SyncRepoCpeMapping)(nil), } + file_internalapi_virtualmachine_v1_vm_service_proto_msgTypes[3].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc), len(file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc)), - NumEnums: 1, - NumMessages: 10, + NumEnums: 2, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go b/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go index db84d131c4430..36afa3c8ec7aa 100644 --- a/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go +++ b/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go @@ -54,6 +54,15 @@ func (m *VMServiceRequest_GetReport) CloneVT() isVMServiceRequest_Method { return r } +func (m *VMServiceRequest_SyncRepoCpeMapping) CloneVT() isVMServiceRequest_Method { + if m == nil { + return (*VMServiceRequest_SyncRepoCpeMapping)(nil) + } + r := new(VMServiceRequest_SyncRepoCpeMapping) + r.SyncRepoCpeMapping = m.SyncRepoCpeMapping.CloneVT() + return r +} + func (m *VMServiceResponse) CloneVT() *VMServiceResponse { if m == nil { return (*VMServiceResponse)(nil) @@ -94,6 +103,15 @@ func (m *VMServiceResponse_Error) CloneVT() isVMServiceResponse_Result { return r } +func (m *VMServiceResponse_SyncRepoCpeMapping) CloneVT() isVMServiceResponse_Result { + if m == nil { + return (*VMServiceResponse_SyncRepoCpeMapping)(nil) + } + r := new(VMServiceResponse_SyncRepoCpeMapping) + r.SyncRepoCpeMapping = m.SyncRepoCpeMapping.CloneVT() + return r +} + func (m *RequestMeta) CloneVT() *RequestMeta { if m == nil { return (*RequestMeta)(nil) @@ -144,6 +162,14 @@ func (m *ResponseMeta) CloneVT() *ResponseMeta { } r.Facts = tmpContainer } + if rhs := m.RepoCpeMappingHash; rhs != nil { + tmpVal := *rhs + r.RepoCpeMappingHash = &tmpVal + } + if rhs := m.RepoCpeMappingUpdatePath; rhs != nil { + tmpVal := *rhs + r.RepoCpeMappingUpdatePath = &tmpVal + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -197,6 +223,44 @@ func (m *GetReportResponse) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *SyncRepoCPEMappingRequest) CloneVT() *SyncRepoCPEMappingRequest { + if m == nil { + return (*SyncRepoCPEMappingRequest)(nil) + } + r := new(SyncRepoCPEMappingRequest) + if rhs := m.Mapping; rhs != nil { + tmpBytes := make([]byte, len(rhs)) + copy(tmpBytes, rhs) + r.Mapping = tmpBytes + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *SyncRepoCPEMappingRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + +func (m *SyncRepoCPEMappingResponse) CloneVT() *SyncRepoCPEMappingResponse { + if m == nil { + return (*SyncRepoCPEMappingResponse)(nil) + } + r := new(SyncRepoCPEMappingResponse) + r.Updated = m.Updated + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *SyncRepoCPEMappingResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *ErrorResponse) CloneVT() *ErrorResponse { if m == nil { return (*ErrorResponse)(nil) @@ -278,6 +342,31 @@ func (this *VMServiceRequest_GetReport) EqualVT(thatIface isVMServiceRequest_Met return true } +func (this *VMServiceRequest_SyncRepoCpeMapping) EqualVT(thatIface isVMServiceRequest_Method) bool { + that, ok := thatIface.(*VMServiceRequest_SyncRepoCpeMapping) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.SyncRepoCpeMapping, that.SyncRepoCpeMapping; p != q { + if p == nil { + p = &SyncRepoCPEMappingRequest{} + } + if q == nil { + q = &SyncRepoCPEMappingRequest{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *VMServiceResponse) EqualVT(that *VMServiceResponse) bool { if this == that { return true @@ -359,6 +448,31 @@ func (this *VMServiceResponse_Error) EqualVT(thatIface isVMServiceResponse_Resul return true } +func (this *VMServiceResponse_SyncRepoCpeMapping) EqualVT(thatIface isVMServiceResponse_Result) bool { + that, ok := thatIface.(*VMServiceResponse_SyncRepoCpeMapping) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.SyncRepoCpeMapping, that.SyncRepoCpeMapping; p != q { + if p == nil { + p = &SyncRepoCPEMappingResponse{} + } + if q == nil { + q = &SyncRepoCPEMappingResponse{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *RequestMeta) EqualVT(that *RequestMeta) bool { if this == that { return true @@ -438,6 +552,12 @@ func (this *ResponseMeta) EqualVT(that *ResponseMeta) bool { if this.Epoch != that.Epoch { return false } + if p, q := this.RepoCpeMappingHash, that.RepoCpeMappingHash; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { + return false + } + if p, q := this.RepoCpeMappingUpdatePath, that.RepoCpeMappingUpdatePath; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -496,6 +616,44 @@ func (this *GetReportResponse) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *SyncRepoCPEMappingRequest) EqualVT(that *SyncRepoCPEMappingRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if string(this.Mapping) != string(that.Mapping) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SyncRepoCPEMappingRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*SyncRepoCPEMappingRequest) + if !ok { + return false + } + return this.EqualVT(that) +} +func (this *SyncRepoCPEMappingResponse) EqualVT(that *SyncRepoCPEMappingResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Updated != that.Updated { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *SyncRepoCPEMappingResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*SyncRepoCPEMappingResponse) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *ErrorResponse) EqualVT(that *ErrorResponse) bool { if this == that { return true @@ -605,6 +763,29 @@ func (m *VMServiceRequest_GetReport) MarshalToSizedBufferVT(dAtA []byte) (int, e } return len(dAtA) - i, nil } +func (m *VMServiceRequest_SyncRepoCpeMapping) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *VMServiceRequest_SyncRepoCpeMapping) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.SyncRepoCpeMapping != nil { + size, err := m.SyncRepoCpeMapping.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x1a + } + return len(dAtA) - i, nil +} func (m *VMServiceResponse) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -703,6 +884,29 @@ func (m *VMServiceResponse_Error) MarshalToSizedBufferVT(dAtA []byte) (int, erro } return len(dAtA) - i, nil } +func (m *VMServiceResponse_SyncRepoCpeMapping) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *VMServiceResponse_SyncRepoCpeMapping) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.SyncRepoCpeMapping != nil { + size, err := m.SyncRepoCpeMapping.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x22 + } + return len(dAtA) - i, nil +} func (m *RequestMeta) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -801,6 +1005,18 @@ func (m *ResponseMeta) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.RepoCpeMappingUpdatePath != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.RepoCpeMappingUpdatePath)) + i-- + dAtA[i] = 0x40 + } + if m.RepoCpeMappingHash != nil { + i -= len(*m.RepoCpeMappingHash) + copy(dAtA[i:], *m.RepoCpeMappingHash) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(*m.RepoCpeMappingHash))) + i-- + dAtA[i] = 0x3a + } if m.Epoch != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Epoch)) i-- @@ -967,6 +1183,89 @@ func (m *GetReportResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *SyncRepoCPEMappingRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SyncRepoCPEMappingRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *SyncRepoCPEMappingRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Mapping) > 0 { + i -= len(m.Mapping) + copy(dAtA[i:], m.Mapping) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Mapping))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SyncRepoCPEMappingResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SyncRepoCPEMappingResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *SyncRepoCPEMappingResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Updated { + i-- + if m.Updated { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *ErrorResponse) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1062,6 +1361,20 @@ func (m *VMServiceRequest_GetReport) SizeVT() (n int) { } return n } +func (m *VMServiceRequest_SyncRepoCpeMapping) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SyncRepoCpeMapping != nil { + l = m.SyncRepoCpeMapping.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *VMServiceResponse) SizeVT() (n int) { if m == nil { return 0 @@ -1107,6 +1420,20 @@ func (m *VMServiceResponse_Error) SizeVT() (n int) { } return n } +func (m *VMServiceResponse_SyncRepoCpeMapping) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SyncRepoCpeMapping != nil { + l = m.SyncRepoCpeMapping.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *RequestMeta) SizeVT() (n int) { if m == nil { return 0 @@ -1169,6 +1496,13 @@ func (m *ResponseMeta) SizeVT() (n int) { if m.Epoch != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.Epoch)) } + if m.RepoCpeMappingHash != nil { + l = len(*m.RepoCpeMappingHash) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.RepoCpeMappingUpdatePath != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.RepoCpeMappingUpdatePath)) + } n += len(m.unknownFields) return n } @@ -1212,23 +1546,50 @@ func (m *GetReportResponse) SizeVT() (n int) { return n } -func (m *ErrorResponse) SizeVT() (n int) { +func (m *SyncRepoCPEMappingRequest) SizeVT() (n int) { if m == nil { return 0 } var l int _ = l - if m.Code != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Code)) - } - l = len(m.Message) + l = len(m.Mapping) if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Details) > 0 { - for k, v := range m.Details { - _ = k - _ = v + n += len(m.unknownFields) + return n +} + +func (m *SyncRepoCPEMappingResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Updated { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *ErrorResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Code != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Code)) + } + l = len(m.Message) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.Details) > 0 { + for k, v := range m.Details { + _ = k + _ = v mapEntrySize := 1 + len(k) + protohelpers.SizeOfVarint(uint64(len(k))) + 1 + len(v) + protohelpers.SizeOfVarint(uint64(len(v))) n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) } @@ -1343,6 +1704,47 @@ func (m *VMServiceRequest) UnmarshalVT(dAtA []byte) error { m.Method = &VMServiceRequest_GetReport{GetReport: v} } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SyncRepoCpeMapping", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Method.(*VMServiceRequest_SyncRepoCpeMapping); ok { + if err := oneof.SyncRepoCpeMapping.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &SyncRepoCPEMappingRequest{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Method = &VMServiceRequest_SyncRepoCpeMapping{SyncRepoCpeMapping: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -1512,6 +1914,47 @@ func (m *VMServiceResponse) UnmarshalVT(dAtA []byte) error { m.Result = &VMServiceResponse_Error{Error: v} } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SyncRepoCpeMapping", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Result.(*VMServiceResponse_SyncRepoCpeMapping); ok { + if err := oneof.SyncRepoCpeMapping.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &SyncRepoCPEMappingResponse{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Result = &VMServiceResponse_SyncRepoCpeMapping{SyncRepoCpeMapping: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -2070,6 +2513,59 @@ func (m *ResponseMeta) UnmarshalVT(dAtA []byte) error { break } } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.RepoCpeMappingHash = &s + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingUpdatePath", wireType) + } + var v RepoCPEMappingUpdatePath + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= RepoCPEMappingUpdatePath(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RepoCpeMappingUpdatePath = &v default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -2296,7 +2792,7 @@ func (m *GetReportResponse) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ErrorResponse) UnmarshalVT(dAtA []byte) error { +func (m *SyncRepoCPEMappingRequest) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2319,36 +2815,17 @@ func (m *ErrorResponse) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ErrorResponse: wiretype end group for non-group") + return fmt.Errorf("proto: SyncRepoCPEMappingRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ErrorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SyncRepoCPEMappingRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) - } - m.Code = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Code |= ErrorCode(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Mapping", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -2358,29 +2835,82 @@ func (m *ErrorResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Message = string(dAtA[iNdEx:postIndex]) + m.Mapping = append(m.Mapping[:0], dAtA[iNdEx:postIndex]...) + if m.Mapping == nil { + m.Mapping = []byte{} + } iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err } - var msglen int + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SyncRepoCPEMappingResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SyncRepoCPEMappingResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SyncRepoCPEMappingResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Updated", wireType) + } + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -2390,30 +2920,152 @@ func (m *ErrorResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength + m.Updated = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err } - postIndex := iNdEx + msglen - if postIndex < 0 { + if (skippy < 0) || (iNdEx+skippy) < 0 { return protohelpers.ErrInvalidLength } - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - if m.Details == nil { - m.Details = make(map[string]string) - } - var mapkey string - var mapvalue string - for iNdEx < postIndex { - entryPreIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ErrorResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ErrorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ErrorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) + } + m.Code = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Code |= ErrorCode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Details == nil { + m.Details = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow } @@ -2631,6 +3283,47 @@ func (m *VMServiceRequest) UnmarshalVTUnsafe(dAtA []byte) error { m.Method = &VMServiceRequest_GetReport{GetReport: v} } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SyncRepoCpeMapping", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Method.(*VMServiceRequest_SyncRepoCpeMapping); ok { + if err := oneof.SyncRepoCpeMapping.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &SyncRepoCPEMappingRequest{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Method = &VMServiceRequest_SyncRepoCpeMapping{SyncRepoCpeMapping: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -2800,6 +3493,47 @@ func (m *VMServiceResponse) UnmarshalVTUnsafe(dAtA []byte) error { m.Result = &VMServiceResponse_Error{Error: v} } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SyncRepoCpeMapping", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Result.(*VMServiceResponse_SyncRepoCpeMapping); ok { + if err := oneof.SyncRepoCpeMapping.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &SyncRepoCPEMappingResponse{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Result = &VMServiceResponse_SyncRepoCpeMapping{SyncRepoCpeMapping: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -3390,6 +4124,63 @@ func (m *ResponseMeta) UnmarshalVTUnsafe(dAtA []byte) error { break } } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + s := stringValue + m.RepoCpeMappingHash = &s + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingUpdatePath", wireType) + } + var v RepoCPEMappingUpdatePath + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= RepoCPEMappingUpdatePath(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RepoCpeMappingUpdatePath = &v default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -3616,6 +4407,159 @@ func (m *GetReportResponse) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *SyncRepoCPEMappingRequest) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SyncRepoCPEMappingRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SyncRepoCPEMappingRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mapping", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Mapping = dAtA[iNdEx:postIndex] + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SyncRepoCPEMappingResponse) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SyncRepoCPEMappingResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SyncRepoCPEMappingResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Updated", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Updated = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ErrorResponse) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/pkg/virtualmachine/cpemapping/validate.go b/pkg/virtualmachine/cpemapping/validate.go new file mode 100644 index 0000000000000..5d125ee00621a --- /dev/null +++ b/pkg/virtualmachine/cpemapping/validate.go @@ -0,0 +1,40 @@ +package cpemapping + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/cespare/xxhash/v2" +) + +// MaxMappingBytes is the accepted size cap for repository-to-CPE JSON (5 MiB). +const MaxMappingBytes = 5 * 1024 * 1024 + +// mappingShape is the minimal JSON structure used for validation only. +// The canonical type lives in pkg/scannerv4/repositorytocpe; this avoids +// coupling the VM-scanning package to that scanner-owned module. +type mappingShape struct { + Data map[string]json.RawMessage `json:"data"` +} + +// ValidateMapping rejects oversize or undecodable repository-to-CPE JSON. +func ValidateMapping(content []byte) error { + if len(content) > MaxMappingBytes { + return fmt.Errorf("mapping size %d exceeds %d bytes", len(content), MaxMappingBytes) + } + var m mappingShape + if err := json.Unmarshal(content, &m); err != nil { + return fmt.Errorf("decode mapping: %w", err) + } + if m.Data == nil { + return errors.New("mapping missing data object") + } + return nil +} + +// HashMapping returns XXH64 of content as 16 lowercase hex characters. +func HashMapping(content []byte) string { + sum := xxhash.Sum64(content) + return fmt.Sprintf("%016x", sum) +} diff --git a/pkg/virtualmachine/cpemapping/validate_test.go b/pkg/virtualmachine/cpemapping/validate_test.go new file mode 100644 index 0000000000000..76c1b0e20bc35 --- /dev/null +++ b/pkg/virtualmachine/cpemapping/validate_test.go @@ -0,0 +1,62 @@ +package cpemapping + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateMapping(t *testing.T) { + tests := map[string]struct { + content []byte + wantErr bool + }{ + "valid minimal JSON accepted": { + content: []byte(`{"data":{}}`), + wantErr: false, + }, + "malformed JSON rejected": { + content: []byte(`{"data":`), + wantErr: true, + }, + "oversize content rejected": { + content: bytes.Repeat([]byte("a"), MaxMappingBytes+1), + wantErr: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + err := ValidateMapping(tt.content) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestHashMapping(t *testing.T) { + tests := map[string]struct { + content []byte + wantHash string + }{ + "stable hash for fixed bytes": { + content: []byte(`{"data":{"repo-1":{"cpes":["cpe:/o:redhat:rhel:8"]}}}`), + wantHash: "e34b7b416f5e54e8", + }, + "empty content hash is still 16 hex chars": { + content: []byte(""), + wantHash: "ef46db3751d8e999", + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + hash := HashMapping(tt.content) + assert.Len(t, hash, 16) + assert.Equal(t, tt.wantHash, hash) + }) + } +} diff --git a/proto/internalapi/virtualmachine/v1/vm_service.proto b/proto/internalapi/virtualmachine/v1/vm_service.proto index b0ee43746ef07..52d22597a003b 100644 --- a/proto/internalapi/virtualmachine/v1/vm_service.proto +++ b/proto/internalapi/virtualmachine/v1/vm_service.proto @@ -19,6 +19,7 @@ message VMServiceRequest { RequestMeta meta = 1; oneof method { GetReportRequest get_report = 2; + SyncRepoCPEMappingRequest sync_repo_cpe_mapping = 3; } } @@ -29,6 +30,7 @@ message VMServiceResponse { oneof result { GetReportResponse get_report = 2; ErrorResponse error = 3; + SyncRepoCPEMappingResponse sync_repo_cpe_mapping = 4; } } @@ -66,6 +68,11 @@ message ResponseMeta { // with Sensor's cached value for that VM. Zero means the agent predates this // field; Sensor falls back to generation-only comparison in that case. uint32 epoch = 6; + // XXH64 hex of active mapping; empty string means none. optional so absence + // means an older agent that does not report mapping metadata. + optional string repo_cpe_mapping_hash = 7; + // SENSOR or URL only; UNSPECIFIED means Sensor must not sync. + optional RepoCPEMappingUpdatePath repo_cpe_mapping_update_path = 8; } // --- Methods --- @@ -93,6 +100,21 @@ message GetReportResponse { bool unchanged = 2; } +message SyncRepoCPEMappingRequest { + bytes mapping = 1; +} + +message SyncRepoCPEMappingResponse { + // True when the agent accepted new content (applied or staged). + bool updated = 1; +} + +enum RepoCPEMappingUpdatePath { + REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED = 0; + REPO_CPE_MAPPING_UPDATE_PATH_SENSOR = 1; + REPO_CPE_MAPPING_UPDATE_PATH_URL = 2; +} + // --- Errors --- message ErrorResponse { @@ -119,4 +141,8 @@ enum ErrorCode { ERROR_CODE_REQUEST_TOO_LARGE = 5; // Agent is already serving another connection; retry after a backoff. ERROR_CODE_BUSY = 6; + // No usable mapping yet; guest disk has not been scanned. + ERROR_CODE_MAPPING_REQUIRED = 7; + // SyncRepoCPEMapping rejected because updater is not Sensor-managed. + ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED = 8; } From e369b6fbae843916244bd0a05564def0fcc1e6c7 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Thu, 20 Aug 2026 11:33:07 +0200 Subject: [PATCH 017/638] ROX-35312: add GHA workflow to publish signing key bundle (#21408) Co-authored-by: Claude Opus 4.6 (1M context) --- .../scripts/validate-signing-key-bundle.sh | 75 +++++++++++++++++++ .../workflows/signing-key-bundle-update.yaml | 58 ++++++++++++++ 2 files changed, 133 insertions(+) create mode 100755 .github/workflows/scripts/validate-signing-key-bundle.sh create mode 100644 .github/workflows/signing-key-bundle-update.yaml diff --git a/.github/workflows/scripts/validate-signing-key-bundle.sh b/.github/workflows/scripts/validate-signing-key-bundle.sh new file mode 100755 index 0000000000000..3be88e7ab3b70 --- /dev/null +++ b/.github/workflows/scripts/validate-signing-key-bundle.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Validates a Red Hat signing key bundle JSON file. +# +# Checks mirror the Go ParseKeyBundle logic in pkg/signatures/key_bundle.go: +# - valid JSON with non-empty "cosignKeys" array +# - each key has a non-empty "name" (after trimming) without path separators +# - no duplicate key names +# - each "publicKey" value is a valid PEM-encoded public key +# +# USAGE: validate-signing-key-bundle.sh + +set -euo pipefail + +bundle="${1:?Usage: validate-signing-key-bundle.sh }" + +if [[ ! -f "$bundle" ]]; then + echo "ERROR: file not found: $bundle" >&2 + exit 1 +fi + +# 1. Valid JSON structure with non-empty cosignKeys array. +key_count=$(jq -e '.cosignKeys | length' "$bundle") || { + echo "ERROR: invalid JSON or missing 'cosignKeys' array" >&2 + exit 1 +} + +if [[ "$key_count" -eq 0 ]]; then + echo "ERROR: key bundle must contain at least one key" >&2 + exit 1 +fi + +echo "Found $key_count key(s), validating..." + +# 2-4. Validate each key entry. +seen_names=() +for i in $(seq 0 $((key_count - 1))); do + raw_name=$(jq -r ".cosignKeys[$i].name // empty" "$bundle") + pem_data=$(jq -r ".cosignKeys[$i].publicKey // empty" "$bundle") + + # Trim whitespace to match Go's strings.TrimSpace before all checks. + name="${raw_name#"${raw_name%%[![:space:]]*}"}" + name="${name%"${name##*[![:space:]]}"}" + + if [[ -z "$name" ]]; then + echo "ERROR: key at index $i has empty name" >&2 + exit 1 + fi + + if [[ "$name" == */* || "$name" == *\\* ]]; then + echo "ERROR: key name '$name' contains path separators" >&2 + exit 1 + fi + + for seen in "${seen_names[@]+"${seen_names[@]}"}"; do + if [[ "$seen" == "$name" ]]; then + echo "ERROR: duplicate key name '$name'" >&2 + exit 1 + fi + done + seen_names+=("$name") + + if [[ -z "$pem_data" ]]; then + echo "ERROR: key '$name' has empty publicKey" >&2 + exit 1 + fi + + if ! echo "$pem_data" | openssl pkey -pubin -noout 2>/dev/null; then + echo "ERROR: key '$name' has invalid PEM public key" >&2 + exit 1 + fi + + echo " [$i] '$name' — valid" +done + +echo "Bundle validation passed: $key_count key(s) OK" diff --git a/.github/workflows/signing-key-bundle-update.yaml b/.github/workflows/signing-key-bundle-update.yaml new file mode 100644 index 0000000000000..fdf59e69efa9a --- /dev/null +++ b/.github/workflows/signing-key-bundle-update.yaml @@ -0,0 +1,58 @@ +name: Red Hat signing key bundle update + +on: + push: + branches: + - master + paths: + - pkg/signatures/bundle.json + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate-and-upload: + if: ${{ github.repository_owner == 'stackrox' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 + with: + persist-credentials: false + + - name: Validate key bundle + run: .github/workflows/scripts/validate-signing-key-bundle.sh pkg/signatures/bundle.json + + - name: Authenticate with Google Cloud + if: github.ref == 'refs/heads/master' + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER }} + + - name: Set up Cloud SDK + if: github.ref == 'refs/heads/master' + uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 + + - name: Upload key bundle to GCS + if: github.ref == 'refs/heads/master' + run: | + gsutil -h "Content-Type: application/json" \ + cp pkg/signatures/bundle.json \ + gs://definitions.stackrox.io/signing-keys/bundle.json + + send-notification: + needs: + - validate-and-upload + runs-on: ubuntu-latest + if: failure() + steps: + - name: Send Slack notification on failure + run: | + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\" <${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}|Workflow ${{ github.workflow }}> failed: Red Hat signing key bundle update\"}" \ + ${{ secrets.SLACK_ONCALL_SCANNER_WEBHOOK }} From ac5bb3ba4f8f56287d10062ced2b6ea458b49f16 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:54:34 +0200 Subject: [PATCH 018/638] ROX-35584: expect Enabled() in TestFeatureFlagSettings (#22364) --- tests/feature_flag_test.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/feature_flag_test.go b/tests/feature_flag_test.go index 06fc74f723500..8a6f15ffea975 100644 --- a/tests/feature_flag_test.go +++ b/tests/feature_flag_test.go @@ -28,6 +28,8 @@ var ( } ) +// TestFeatureFlagSettings asserts Central's live flags match flag.Enabled() +// computed in this process from the same environment and build type. func TestFeatureFlagSettings(t *testing.T) { if os.Getenv("ORCHESTRATOR_FLAVOR") == "openshift" { t.Skip("Skipping on OCP: ci_export uses cci-export which does not set shell variables, causing systemic mismatch between test runner and Central") @@ -37,23 +39,13 @@ func TestFeatureFlagSettings(t *testing.T) { conn := centralgrpc.GRPCConnectionToCentral(t) - metadataService := v1.NewMetadataServiceClient(conn) - metadata, err := metadataService.GetMetadata(ctx, &v1.Empty{}) - require.NoError(t, err, "failed to retrieve metadata") - expectedFlagVals := make(map[string]bool) for _, flag := range features.Flags { if skipFeatures[flag.EnvVar()] { continue } - // For non-release builds, test that feature flag settings match the local environment; - // for release builds, test that they match the defaults. - expectedVal := flag.Enabled() - if metadata.GetReleaseBuild() { - expectedVal = flag.Default() - } - expectedFlagVals[flag.EnvVar()] = expectedVal + expectedFlagVals[flag.EnvVar()] = flag.Enabled() } featureFlagService := v1.NewFeatureFlagServiceClient(conn) From 3584ea28bf6b2ca8842e57e6130881828c66b070 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 20 Aug 2026 11:29:44 +0000 Subject: [PATCH 019/638] ROX-35906: allowlist scanner restart during upgrade postgres bounce (#22101) Co-authored-by: Claude Opus 4.6 --- scripts/ci/logcheck/check-restart-logs_test.bats | 11 +++++++++++ scripts/ci/logcheck/restart-ok-patterns.json | 6 ++++++ .../test_fixtures/upgrade-scanner-previous.log | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 scripts/ci/logcheck/test_fixtures/upgrade-scanner-previous.log diff --git a/scripts/ci/logcheck/check-restart-logs_test.bats b/scripts/ci/logcheck/check-restart-logs_test.bats index 622cef31ce623..998d1a3f1f886 100755 --- a/scripts/ci/logcheck/check-restart-logs_test.bats +++ b/scripts/ci/logcheck/check-restart-logs_test.bats @@ -85,6 +85,17 @@ function setup() { [ "$status" -eq 0 ] } +@test "scanner restart during upgrade postgres bounce is OK" { + run "$CMD" "gke-upgrade-tests-central" "${TEST_FIXTURES}/upgrade-scanner-previous.log" + [ "$status" -eq 0 ] + [ "${lines[1]}" = "Ignoring this restart due to: scanner restart due to DB connection loss during postgres bounce in upgrade test" ] +} + +@test "scanner restart during upgrade postgres bounce is NOT OK for non-upgrade jobs" { + run "$CMD" "gke-nongroovy-e2e-tests" "${TEST_FIXTURES}/upgrade-scanner-previous.log" + [ "$status" -eq 2 ] +} + teardown () { echo "$BATS_TEST_NAME -------- diff --git a/scripts/ci/logcheck/restart-ok-patterns.json b/scripts/ci/logcheck/restart-ok-patterns.json index 4ad976448e98e..c82990ef3a4c4 100644 --- a/scripts/ci/logcheck/restart-ok-patterns.json +++ b/scripts/ci/logcheck/restart-ok-patterns.json @@ -76,5 +76,11 @@ "job": "compatibility", "logfile": "400\\.(9|10)\\..*matcher-previous", "logline": "unable to connect to database.*connection refused" + }, + { + "comment": "scanner restart due to DB connection loss during postgres bounce in upgrade test", + "job": "upgrade", + "logfile": "scanner-previous", + "logline": "Failed to initialize updater" } ] diff --git a/scripts/ci/logcheck/test_fixtures/upgrade-scanner-previous.log b/scripts/ci/logcheck/test_fixtures/upgrade-scanner-previous.log new file mode 100644 index 0000000000000..b0e258118b420 --- /dev/null +++ b/scripts/ci/logcheck/test_fixtures/upgrade-scanner-previous.log @@ -0,0 +1,3 @@ +{"Event":"searchKeyValue","Level":"info","Location":"pgsql.go:190","Time":"2026-07-23 07:30:25.123456"} +{"Description":"searchKeyValue","Event":"Handled Database Error","Level":"error","Location":"pgsql.go:266","Time":"2026-07-23 07:31:06.037214","error":"dial tcp 34.118.224.96:5432: connect: connection refused"} +{"Event":"Failed to initialize updater","Level":"fatal","Location":"main.go:169","Time":"2026-07-23 07:31:06.037376","error":"getting last updated time from DB: getting last updated time from DB: dial tcp 34.118.224.96:5432: connect: connection refused"} From 2fd1b04e5f243de02d028782e4c29078153584c2 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 20 Aug 2026 13:40:08 +0200 Subject: [PATCH 020/638] ROX-36272: migrate compatibility tests to roxie (#22268) --- .openshift-ci/compatibility_test.py | 11 +- .../get_compatibility_test_tuples.py | 145 +++++++--------- .../get_latest_helm_chart_versions.py | 96 ++++++----- .openshift-ci/pre_tests.py | 13 -- .../test_get_compatibility_test_tuples.py | 53 ++++-- ROXIE_VERSION | 2 +- qa-tests-backend/scripts/run-compatibility.sh | 13 +- tests/e2e/lib.sh | 158 ++++++++++++------ tests/e2e/run-compatibility.sh | 11 +- 9 files changed, 267 insertions(+), 235 deletions(-) diff --git a/.openshift-ci/compatibility_test.py b/.openshift-ci/compatibility_test.py index ca6ffea982d95..00a0967fe7375 100644 --- a/.openshift-ci/compatibility_test.py +++ b/.openshift-ci/compatibility_test.py @@ -9,7 +9,6 @@ from pre_tests import ( PreSystemTests, - CollectionMethodOverridePreTest ) from post_tests import PostClusterTest, FinalPost from runners import ClusterTestSetsRunner, TestSet @@ -23,7 +22,7 @@ def run_compatibility_tests(testfunc, cluster_name): # start logging logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - # Get the test tuples (central_version, sensor_version) for supported versions with available helm charts + # Get the test tuples (central_version, sensor_version) for supported versions test_tuples = get_compatibility_test_tuples() if len(test_tuples) > 0: @@ -32,9 +31,6 @@ def run_compatibility_tests(testfunc, cluster_name): os.environ["ROX_TELEMETRY_STORAGE_KEY_V1"] = 'DISABLED' test_versions = f'{test_tuple.central_version}--{test_tuple.sensor_version}' - # expected version string is like 74.x.x for ACS 3.74 versions - is_3_74_sensor = test_tuple.sensor_version.startswith('74') - logging.info("Running compatibility tests for central-v%s, sensor-v%s with function %s", test_tuple.central_version, test_tuple.sensor_version, testfunc.__name__) @@ -43,12 +39,9 @@ def run_compatibility_tests(testfunc, cluster_name): f'version compatibility tests: {test_versions}', test=testfunc(test_tuple.central_version, test_tuple.sensor_version), post=PostClusterTest( - collect_collector_metrics=not is_3_74_sensor, check_stackrox_logs=True, artifact_destination_prefix=test_versions, - ), - # Collection not supported on 3.74 - pre=CollectionMethodOverridePreTest("NO_COLLECTION" if is_3_74_sensor else "core_bpf") + ) ) ) ClusterTestSetsRunner( diff --git a/.openshift-ci/get_compatibility_test_tuples.py b/.openshift-ci/get_compatibility_test_tuples.py index abe1512a220e3..02c5ff99df27e 100755 --- a/.openshift-ci/get_compatibility_test_tuples.py +++ b/.openshift-ci/get_compatibility_test_tuples.py @@ -4,6 +4,7 @@ Returns the central-sensor version tuples to be used for compatibility testing. """ import logging +import re import subprocess import sys from collections import namedtuple @@ -11,7 +12,6 @@ from get_latest_helm_chart_versions import ( get_supported_helm_chart_versions, - get_latest_helm_chart_version_for_specific_release, ) @@ -19,17 +19,17 @@ # get_compatibility_test_tuples.py provides the function get_compatibility_test_tuples() which is called in our # compatibility tests that returns all tuples of central and sensor versions for which: # 1. Central is latest and Sensor is a supported older version OR -# 2. Sensor is latest and Central is a supported older version OR -# 3. Is a support exception -# These are returned only if Helm charts for both versions could be found. +# 2. Sensor is latest and Central is a supported older version +# These are returned only if charts for both versions could be found. # After running get_compatibility_test_tuples.py I received the following output: # INFO:root:Listing supported versions tuples: -# INFO:root:Tuple 1: {Central v4.6.x-736-g48077a980e-dirty - Sensor v400.5.3} -# INFO:root:Tuple 2: {Central v4.6.x-736-g48077a980e-dirty - Sensor v400.4.5} -# INFO:root:Tuple 3: {Central v400.5.3 - Sensor v4.6.x-736-g48077a980e-dirty} -# INFO:root:Tuple 4: {Central v400.4.5 - Sensor v4.6.x-736-g48077a980e-dirty} -# INFO:root:Tuple 5: {Central v4.6.x-736-g48077a980e-dirty - Sensor v74.9.0} -# If no supported versions with available Helm charts are found, an empty list is returned. +# INFO:root:Tuple 1: {Central v4.12.x-793-gf6bf3cc40e - Sensor v4.11.2} +# INFO:root:Tuple 2: {Central v4.12.x-793-gf6bf3cc40e - Sensor v4.10.6} +# INFO:root:Tuple 3: {Central v4.12.x-793-gf6bf3cc40e - Sensor v4.9.10} +# INFO:root:Tuple 4: {Central v4.11.2 - Sensor v4.12.x-793-gf6bf3cc40e} +# INFO:root:Tuple 5: {Central v4.10.6 - Sensor v4.12.x-793-gf6bf3cc40e} +# INFO:root:Tuple 6: {Central v4.9.10 - Sensor v4.12.x-793-gf6bf3cc40e} +# If no supported versions with available charts are found, an empty list is returned. def main(): logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) test_tuples = get_compatibility_test_tuples() @@ -44,51 +44,40 @@ def main(): ) -# Returns True if the helm_version is newer than the current_version -def is_newer_version(current_version: str, helm_version: str): - helm_version_split = helm_version.split(sep='.') - current_version_split = current_version.split(sep='.') - - # Parse helm version format using numeric encoding: - # - New format: X00MM where X=major digit, 00=padding, MM=minor (zero-padded) - # Example: 40009 → major=4, minor=09 (version 4.9) - # Example: 40011 → major=4, minor=11 (version 4.11) - # Formula: major = n // 10000, minor = n % 100 - # - Old format: X00 where X=major digit, 00=padding (version 4.0.x) - # Formula: major = n // 100 - helm_major_num = int(helm_version_split[0]) - if helm_major_num >= 10000: - # New format: extract major (first digit) and minor (last 2 digits) - helm_major = helm_major_num // 10000 - helm_minor = helm_major_num % 100 - helm_version_split = [str(helm_major), str(helm_minor)] + helm_version_split[1:] - else: - # Old format: extract major by removing padding - helm_version_split[0] = str(helm_major_num // 100) - - # Remove commit hash from the current version - current_version_split = current_version_split[:-1] - # If we are in a release branch, we will have patch version with '-rc' - if len(current_version_split) > 2: - # Remove '-rc' if present - current_version_split[2] = str(current_version_split[2]).rstrip("-rc") - - for (current, helm) in zip(current_version_split, helm_version_split): - if int(current) > int(helm): +# Returns True if the product_version is newer than the current_version. +# +# current_version examples: '4.11.x-736-g48077a980e', '4.11.1-rc.2', '4.11.1' +# product_version examples: '4.9.3', '4.11.0' +def is_newer_version(current_version: str, product_version: str): + product_parts = [int(x) for x in product_version.split('.')] + + # Parse the numeric major.minor[.patch] prefix from current_version, stopping + # at any component that does not begin with a digit (e.g. 'x-736-ghash'). + # This correctly handles: + # dev: '4.11.x-736-ghash' -> [4, 11] + # rc: '4.11.1-rc.2' -> [4, 11, 1] + # release: '4.11.1' -> [4, 11, 1] + current_parts = [] + for part in current_version.split('.')[:3]: + m = re.match(r'^(\d+)', part) + if not m: break - if int(current) < int(helm): + current_parts.append(int(m.group(1))) + + for (current, product) in zip(current_parts, product_parts): + if current > product: + return False + if current < product: return True return False def get_compatibility_test_tuples(): - Release = namedtuple("Release", ["major", "minor"]) - # start logging logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - central_chart_versions, sensor_chart_versions = get_supported_helm_chart_versions() + central_versions, sensor_versions = get_supported_helm_chart_versions() makefile_path = Path(__file__).parent.parent latest_tag = subprocess.check_output( @@ -104,57 +93,35 @@ def get_compatibility_test_tuples(): # versions. # There is no risk in excluding newer versions as the compatibility tests in # their respective branches will test against older versions. - central_chart_versions = [i for i in central_chart_versions - if not - is_newer_version(current_version=latest_tag, - helm_version=i)] - sensor_chart_versions = [i for i in sensor_chart_versions - if not - is_newer_version(current_version=latest_tag, - helm_version=i)] - - if len(central_chart_versions) == 0: - logging.info("Found no older central chart versions to test against according to the product lifecycles API.") - if len(sensor_chart_versions) == 0: - logging.info("Found no older sensor chart versions to test against according to the product lifecycles API.") - if len(central_chart_versions) == 0 or len(sensor_chart_versions) == 0: - logging.info("However versions with support exceptions will still be tested against.") - - ChartVersions = namedtuple( - "Chart_versions", ["central_version", "sensor_version"]) - - # Latest central vs sensor versions in sensor_chart_versions + central_versions = [i for i in central_versions + if not is_newer_version(current_version=latest_tag, + product_version=i)] + sensor_versions = [i for i in sensor_versions + if not is_newer_version(current_version=latest_tag, + product_version=i)] + + if len(central_versions) == 0: + logging.info("Found no older central versions to test against according to the product lifecycles API.") + if len(sensor_versions) == 0: + logging.info("Found no older sensor versions to test against according to the product lifecycles API.") + + VersionTuple = namedtuple("VersionTuple", ["central_version", "sensor_version"]) + + # Latest central vs older sensor versions test_tuples = [ - ChartVersions(central_version=latest_tag, - sensor_version=sensor_chart_version) - for sensor_chart_version in sensor_chart_versions + VersionTuple(central_version=latest_tag, + sensor_version=sensor_version) + for sensor_version in sensor_versions ] - # Latest sensor vs central versions in central_chart_versions + # Older central versions vs latest sensor test_tuples.extend( [ - ChartVersions(central_version=central_chart_version, - sensor_version=latest_tag) - for central_chart_version in central_chart_versions + VersionTuple(central_version=central_version, + sensor_version=latest_tag) + for central_version in central_versions ] ) - # Currently there are no support exceptions, the last one expired on 2024-06-30, see: - # https://issues.redhat.com/browse/ROX-18223 - # however a new support exception is being negotiated, add it here when it's ready - support_exceptions = [ - ChartVersions( - central_version=latest_tag, - sensor_version=get_latest_helm_chart_version_for_specific_release( - "stackrox-secured-cluster-services", Release(major=3, minor=74) - ), - ) - ] - - test_tuples.extend( - support_exception - for support_exception in support_exceptions - if support_exception not in test_tuples - ) return test_tuples diff --git a/.openshift-ci/get_latest_helm_chart_versions.py b/.openshift-ci/get_latest_helm_chart_versions.py index ae5c4b0f5d33f..c61a18049c978 100755 --- a/.openshift-ci/get_latest_helm_chart_versions.py +++ b/.openshift-ci/get_latest_helm_chart_versions.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Looks up N previous releases and outputs a Helm chart version for the most +Looks up supported releases and outputs a product version for the most recent patch for each found release. """ @@ -46,11 +46,13 @@ # with the current cadence and support period. NUM_RELEASES_DEFAULT = 3 -# For support exceptions we may need to get the latest patch for a specific -# release that is not within the last N versions. In that case -# get_latest_helm_chart_version_for_specific_release will provide the latest -# patch of the input release. -sample_support_exception = Release(major=3, minor=74) +# From this release onward the stackrox-operator chart is used instead of the +# per-component legacy charts (stackrox-central-services, +# stackrox-secured-cluster-services), which are not published for new releases. +# Drop legacy chart inspection can be dropped, once 4.10 goes EOL. +OPERATOR_CHART_FIRST_RELEASE = Release(major=4, minor=11) + +OPERATOR_CHART_NAME = "stackrox-operator" def main(argv): @@ -60,25 +62,29 @@ def main(argv): "stackrox-secured-cluster-services", num_releases ) logging.info( - f"Helm chart versions for the latest {num_releases} releases:") + f"Product versions for the latest {num_releases} releases:") logging.info("\n".join(helm_versions)) - helm_version_specific = get_latest_helm_chart_version_for_specific_release( - "stackrox-secured-cluster-services", sample_support_exception - ) - logging.info( - f"Latest chart version for the {sample_support_exception} " - f"releases is {helm_version_specific}" - ) supported_versions_string = [[f"{version.major}.{version.minor}"] for version in get_supported_releases()] supported_central_versions_from_api, supported_sensor_versions_from_api = get_supported_helm_chart_versions() logging.info( f"\nThe product lifecycles API denotes support for the following versions: {supported_versions_string}\n" - f"Found helm charts for the following supported versions: " + f"Found product versions for the following supported versions: " f"central{supported_central_versions_from_api} - sensor{supported_sensor_versions_from_api}" ) +def _is_legacy_release(release: Release) -> bool: + """Returns True for releases that use per-component charts (pre-4.11).""" + return release < OPERATOR_CHART_FIRST_RELEASE + + +def _chart_to_product_version(chart: dict) -> str: + """Return a product version string (e.g. '4.9.3') from a chart dict.""" + av = chart["parsed_app_version"] + return f"{av.major}.{av.minor}.{av.patch}" + + def get_latest_helm_chart_versions(chart_name, num_releases=NUM_RELEASES_DEFAULT): add_helm_repo() try: @@ -110,22 +116,42 @@ def __get_supported_helm_chart_versions(): supported_central_versions = [] supported_sensor_versions = [] + charts = read_charts() # one helm search, reused for all releases + supported_releases = get_supported_releases() for release in supported_releases: - if __does_chart_exist("stackrox-central-services", release): - supported_central_versions.append(__get_latest_helm_chart_version_for_specific_release( - "stackrox-central-services", release) - ) - else: - logging.debug(f"Supported version \"{release.major}.{release.minor}\" has no corresponding helm chart for " - f"stackrox-central-services.") - if __does_chart_exist("stackrox-secured-cluster-services", release): - supported_sensor_versions.append(__get_latest_helm_chart_version_for_specific_release( - "stackrox-secured-cluster-services", release) - ) + if _is_legacy_release(release): + # Pre-4.11: separate charts for central and sensor. + for chart_name, version_list in [ + ("stackrox-central-services", supported_central_versions), + ("stackrox-secured-cluster-services", supported_sensor_versions), + ]: + filtered = filter_charts_by_name(charts, chart_name) + if any(version_to_release(c["parsed_app_version"]) == release + for c in filtered): + chart = get_latest_chart_for_specific_release(filtered, release) + version_list.append(_chart_to_product_version(chart)) + else: + logging.debug( + f"Supported version \"{release.major}.{release.minor}\" has no " + f"corresponding chart for {chart_name}." + ) else: - logging.debug(f"Supported version \"{release.major}.{release.minor}\" has no corresponding helm chart for " - f"stackrox-secured-cluster-services.") + # 4.11+: one operator chart covers both central and sensor. + filtered = filter_charts_by_name(charts, OPERATOR_CHART_NAME) + if any(version_to_release(c["parsed_app_version"]) == release + for c in filtered): + chart = get_latest_chart_for_specific_release(filtered, release) + version = _chart_to_product_version(chart) + supported_central_versions.append(version) + supported_sensor_versions.append(version) + else: + logging.warning( + f"Supported version \"{release.major}.{release.minor}\" has no " + f"corresponding chart for {OPERATOR_CHART_NAME}. " + f"This release will be skipped in compatibility testing." + ) + return supported_central_versions, supported_sensor_versions @@ -164,15 +190,6 @@ def __get_data_from_product_lifecycles_api(): return [] -def __does_chart_exist(chart_name, release): - charts = read_charts() - filtered_charts = filter_charts_by_name(charts, chart_name) - for fchart in filtered_charts: - if version_to_release(fchart["parsed_app_version"]) == release: - return True - return False - - def __get_latest_helm_chart_versions(chart_name, num_releases): charts = read_charts() logging.info(f"Discovered total {len(charts)} charts") @@ -187,8 +204,7 @@ def __get_latest_helm_chart_versions(chart_name, num_releases): logging.debug( f"Identified these charts as {num_releases} latest: {latest_charts}") - # Specifically remove 400.1.6 which is affected by a max message size bug, but is no longer supported. - return [c["version"] for c in latest_charts if c["version"] != "400.1.6"] + return [_chart_to_product_version(c) for c in latest_charts] def __get_latest_helm_chart_version_for_specific_release(chart_name, release): @@ -204,7 +220,7 @@ def __get_latest_helm_chart_version_for_specific_release(chart_name, release): logging.debug( f"Identified {latest_chart} as latest version of release {release}") - return latest_chart["version"] + return _chart_to_product_version(latest_chart) def read_charts(): diff --git a/.openshift-ci/pre_tests.py b/.openshift-ci/pre_tests.py index 8b4d16e4e29d6..1902074fcc020 100644 --- a/.openshift-ci/pre_tests.py +++ b/.openshift-ci/pre_tests.py @@ -2,7 +2,6 @@ PreTests - something to run before test but after resource provisioning. """ -import os import subprocess @@ -57,15 +56,3 @@ def run(self): check=True, timeout=self.START_PREFETCH_TIMEOUT, ) - - -class CollectionMethodOverridePreTest: - """ - CollectionPreTest - allows finer control over collection method - for individual test jobs - """ - def __init__(self, method): - self._collection_method = method - - def run(self): - os.environ['COLLECTION_METHOD'] = self._collection_method diff --git a/.openshift-ci/tests/test_get_compatibility_test_tuples.py b/.openshift-ci/tests/test_get_compatibility_test_tuples.py index 17e2669bfecfc..fabf092c277a9 100644 --- a/.openshift-ci/tests/test_get_compatibility_test_tuples.py +++ b/.openshift-ci/tests/test_get_compatibility_test_tuples.py @@ -19,77 +19,77 @@ class TestIsNewerVersion(unittest.TestCase): def test_double_digit_minor_version_not_newer(self): """4.11 should NOT consider 4.9 as newer (this was the bug)""" self.assertFalse( - is_newer_version('4.11.x-94-g75a2cb6b34', '40009.5.3'), + is_newer_version('4.11.x-94-g75a2cb6b34', '4.9.5'), "4.9 should not be considered newer than 4.11" ) def test_double_digit_minor_version_4_10_vs_4_9(self): """4.10 should NOT consider 4.9 as newer""" self.assertFalse( - is_newer_version('4.10.x-50-ghash', '40009.5.3'), + is_newer_version('4.10.x-50-ghash', '4.9.5'), "4.9 should not be considered newer than 4.10" ) def test_double_digit_minor_version_4_11_vs_4_10(self): """4.11 should NOT consider 4.10 as newer""" self.assertFalse( - is_newer_version('4.11.x-94-ghash', '40010.2.1'), + is_newer_version('4.11.x-94-ghash', '4.10.2'), "4.10 should not be considered newer than 4.11" ) def test_double_digit_minor_version_is_newer(self): """4.10 should consider 4.11 as newer""" self.assertTrue( - is_newer_version('4.10.x-50-ghash', '40011.1.0'), + is_newer_version('4.10.x-50-ghash', '4.11.1'), "4.11 should be considered newer than 4.10" ) def test_single_digit_minor_version_not_newer(self): """4.8 should NOT consider 4.7 as newer (existing behavior)""" self.assertFalse( - is_newer_version('4.8.x-325-ghash', '40007.0.0'), + is_newer_version('4.8.x-325-ghash', '4.7.0'), "4.7 should not be considered newer than 4.8" ) def test_single_digit_minor_version_is_newer(self): """4.6 should consider 4.9 as newer (existing behavior)""" self.assertTrue( - is_newer_version('4.6.x-736-ghash', '40009.5.3'), + is_newer_version('4.6.x-736-ghash', '4.9.5'), "4.9 should be considered newer than 4.6" ) def test_equal_major_minor_versions(self): """Equal major.minor versions should not be considered newer""" self.assertFalse( - is_newer_version('4.9.x-100-ghash', '40009.0.0'), + is_newer_version('4.9.x-100-ghash', '4.9.0'), "Same version should not be considered newer" ) def test_major_version_difference(self): """Higher major version should be considered newer""" self.assertTrue( - is_newer_version('4.11.x-94-ghash', '50000.0.0'), + is_newer_version('4.11.x-94-ghash', '5.0.0'), "5.0 should be considered newer than 4.11" ) def test_major_version_not_newer(self): """Lower major version should not be considered newer""" self.assertFalse( - is_newer_version('4.11.x-94-ghash', '30074.9.0'), + is_newer_version('4.11.x-94-ghash', '3.74.9'), "3.74 should not be considered newer than 4.11" ) def test_release_candidate_version(self): """Test with release candidate versions""" self.assertTrue( - is_newer_version('4.11.1-rc.2', '40012.0.0'), + is_newer_version('4.11.1-rc.2', '4.12.0'), "4.12 should be considered newer than 4.11.1-rc.2" ) def test_release_candidate_not_newer(self): """Test with release candidate versions - not newer""" self.assertFalse( - is_newer_version('4.11.1-rc.2', '40010.5.0'), + is_newer_version('4.11.1-rc.2', '4.10.5'), "4.10 should not be considered newer than 4.11.1-rc.2" ) @@ -97,7 +97,7 @@ def test_patch_version_differences(self): """Test that patch versions are handled correctly""" # 4.11.2 should not consider 4.11.1 as newer self.assertFalse( - is_newer_version('4.11.2-10-ghash', '40011.1.0'), + is_newer_version('4.11.2-10-ghash', '4.11.1'), "4.11.1 should not be considered newer than 4.11.2" ) @@ -105,17 +105,38 @@ def test_three_digit_minor_version(self): """Test edge case with hypothetical three-digit minor versions""" # If we ever reach 4.100, this should work self.assertFalse( - is_newer_version('4.100.x-1-ghash', '40099.0.0'), + is_newer_version('4.100.x-1-ghash', '4.99.0'), "4.99 should not be considered newer than 4.100" ) - def test_helm_version_with_different_patch_levels(self): - """Test comparison with different helm chart patch versions""" + def test_product_version_with_different_patch_levels(self): + """Test comparison with different patch versions""" self.assertFalse( - is_newer_version('4.9.x-100-ghash', '40009.10.5'), + is_newer_version('4.9.x-100-ghash', '4.9.10'), "4.9.10 should not be considered newer than 4.9" ) + def test_plain_release_tag_patch_newer(self): + """Plain release tag: newer patch should be detected""" + self.assertTrue( + is_newer_version('4.11.1', '4.11.5'), + "4.11.5 should be considered newer than plain release 4.11.1" + ) + + def test_plain_release_tag_patch_not_newer(self): + """Plain release tag: older patch should not be considered newer""" + self.assertFalse( + is_newer_version('4.11.5', '4.11.1'), + "4.11.1 should not be considered newer than plain release 4.11.5" + ) + + def test_plain_release_tag_same_version(self): + """Plain release tag: same version is not newer""" + self.assertFalse( + is_newer_version('4.11.1', '4.11.1'), + "Same plain release version should not be considered newer" + ) + if __name__ == '__main__': unittest.main() diff --git a/ROXIE_VERSION b/ROXIE_VERSION index 2b7c5ae01848a..cb498ab2c89f4 100644 --- a/ROXIE_VERSION +++ b/ROXIE_VERSION @@ -1 +1 @@ -0.4.2 +0.4.8 diff --git a/qa-tests-backend/scripts/run-compatibility.sh b/qa-tests-backend/scripts/run-compatibility.sh index a9441309069af..625d37dab1466 100755 --- a/qa-tests-backend/scripts/run-compatibility.sh +++ b/qa-tests-backend/scripts/run-compatibility.sh @@ -47,7 +47,6 @@ _compatibility_test() { local short_sensor_tag="$4" export_test_environment - ci_export CENTRAL_PERSISTENCE_NONE "true" if [[ "${SKIP_DEPLOY:-false}" = "false" ]]; then if [[ "${CI:-false}" = "true" ]]; then @@ -58,14 +57,20 @@ _compatibility_test() { setup_deployment_env false false setup_podsecuritypolicies_config - remove_existing_stackrox_resources + "$ROOT/scripts/roxie.sh" teardown all --single-namespace || true setup_default_TLS_certs deploy_stackrox_with_custom_central_and_sensor_versions "${central_version}" "${sensor_version}" - echo "Stackrox deployed" kubectl -n stackrox get deploy,ds -o wide - deploy_default_psp + # Wait for scanner v2 deployment to be ready so that it can register its + # Clairify integration with Central before CertExpiryTest runs. + # TODO(https://github.com/stackrox/roxie/issues/269): replace with --early-readiness=false roxie flag, + # once we no longer need to deploy 4.9 + if retrying_kubectl /dev/null 2>&1; then + wait_for_ready_deployment stackrox scanner 600 + fi + deploy_webhook_server get_ECR_docker_pull_password fi diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 4c72d0ba13340..917c2aff19c92 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -275,77 +275,125 @@ gen_admin_password() { head -c 20 " + die "usage: deploy_stackrox_with_custom_central_and_sensor_versions " fi local central_version="$1" local sensor_version="$2" - ci_export DEPLOY_STACKROX_VIA_OPERATOR "false" - ci_export OUTPUT_FORMAT "helm" + local roxie="${TEST_ROOT}/scripts/roxie.sh" - # Repo name can't be too long or `helm search repo [REPO_NAME] -l` cuts off part of the name and the regex below fails. - local helm_repo_name="tmp-srox-compat" - local helm_chart_url="https://raw.githubusercontent.com/stackrox/helm-charts/main/opensource" - if ! helm repo list -o json | jq -e --arg name "$helm_repo_name" --arg url "$helm_chart_url" \ - 'any(.[]; .name == $name and .url == $url)'; then - helm repo add --force-update "${helm_repo_name}" "${helm_chart_url}" - fi + info "Deploying for compatibility test: Central ${central_version}, Sensor ${sensor_version}" + + local namespace="stackrox" + local config_file; config_file="$(mktemp)" + local roxie_envrc; roxie_envrc="$(mktemp)" + + # Fix the cluster name so setup_generated_certs_for_test can look it up by name. + merge_yaml "$config_file" < "$overlay_tmp" <<'OVERLAY' +central: + spec: + overlays: + - apiVersion: v1 + kind: ConfigMap + name: central-endpoints + patches: + - path: data.endpoints\.yaml + verbatim: | +OVERLAY + sed 's/^/ /' "${ROXDEPLOY_CONFIG_FILE_MAP}" >> "$overlay_tmp" + merge_yaml "$config_file" < "$overlay_tmp" + rm -f "$overlay_tmp" + fi + + # Add the test CA so Central accepts client-cert auth during endpoints_test.go. + if [[ -n "${TRUSTED_CA_FILE:-}" && -f "${TRUSTED_CA_FILE}" ]]; then + local trusted_ca_content + trusted_ca_content="$(jq -Rs . < "${TRUSTED_CA_FILE}")" + merge_yaml "$config_file" <&2 "${chart_name} helm chart for version ${central_version} not found in ${helm_repo_name} repo nor is it the current tag." - exit 1 + retrying_kubectl -n "$namespace" create secret tls "$tls_secret_name" \ + --cert="$ROX_DEFAULT_TLS_CERT_FILE" \ + --key="$ROX_DEFAULT_TLS_KEY_FILE" \ + --dry-run=client -o yaml &2 "Reported roxctl version $(roxctl version) is different from requested tag ${current_tag}. It won't be possible to get helm charts for ${current_tag}. Please check test setup." - exit 1 - fi - ci_export SENSOR_CHART_DIR_OVERRIDE "${charts_dir}/${chart_name}" - local sensor_image_flavor - sensor_image_flavor="${ROXCTL_ROX_IMAGE_FLAVOR:-$(make --quiet --no-print-directory -C "${TEST_ROOT}" image-flavor)}" - roxctl helm output secured-cluster-services --image-defaults="${sensor_image_flavor}" --output-dir "${SENSOR_CHART_DIR_OVERRIDE}" --remove - echo "Downloaded ${chart_name} helm chart for version ${sensor_version} to ${SENSOR_CHART_DIR_OVERRIDE} (image-defaults=${sensor_image_flavor})" - else - echo >&2 "${chart_name} helm chart for version ${sensor_version} not found in ${helm_repo_name} repo nor is it the latest tag." - exit 1 + ROX_ADMIN_PASSWORD="$(gen_admin_password)" + export ROX_ADMIN_PASSWORD + + "$roxie" --verbose deploy \ + --single-namespace \ + --tag "${central_version}" \ + --secured-cluster-tag "${sensor_version}" \ + --resources ci \ + --pause-reconciliation \ + --config "$config_file" \ + --envrc "$roxie_envrc" + + + extend_roxie_envrc "$roxie_envrc" + if [[ -n "${BASH_ENV:-}" ]]; then + cat "$roxie_envrc" >> "$BASH_ENV" fi + # shellcheck source=/dev/null + source "$roxie_envrc" + ci_export API_ENDPOINT "$API_ENDPOINT" + ci_export ROX_ADMIN_PASSWORD "$ROX_ADMIN_PASSWORD" - deploy_stackrox + rm -f "$config_file" "$roxie_envrc" - rm -rf "$charts_dir" + # TODO(https://github.com/stackrox/roxie/issues/269): replace with --early-readiness=false roxie flag, + # once we no longer need to deploy 4.9 + wait_for_collectors_to_be_operational stackrox - ci_export CENTRAL_CHART_DIR_OVERRIDE "" - ci_export SENSOR_CHART_DIR_OVERRIDE "" + info "Stackrox deployed with Central version: ${central_version}, Sensor version: ${sensor_version}" } # export_test_environment() - Persist environment variables for the remainder of @@ -1846,7 +1894,7 @@ _EO_DETAILS_ *gke-upgrade-tests) record_upgrade_test_progess ;; - *operator-e2e-tests) + *operator-e2e-tests|*-version-compatibility-tests|*-nongroovy-compatibility-tests) check_deployment=false ;; *) diff --git a/tests/e2e/run-compatibility.sh b/tests/e2e/run-compatibility.sh index ec533e6766e1e..2cc54e5fc25bc 100755 --- a/tests/e2e/run-compatibility.sh +++ b/tests/e2e/run-compatibility.sh @@ -45,23 +45,18 @@ _run_compatibility_tests() { require_environment "KUBECONFIG" export_test_environment - ci_export CENTRAL_PERSISTENCE_NONE "true" - - export SENSOR_HELM_DEPLOY=true - export ROX_ACTIVE_VULN_REFRESH_INTERVAL=1m - export ROX_NETPOL_FIELDS=true - export ROX_SENSOR_UPGRADER_ENABLED=false test_preamble setup_deployment_env false false - remove_existing_stackrox_resources + "$ROOT/scripts/roxie.sh" teardown all --single-namespace || true setup_default_TLS_certs info "Creating mocked compliance operator data for compliance v1 tests" "$ROOT/tests/complianceoperator/create.sh" deploy_stackrox_with_custom_central_and_sensor_versions "${central_version}" "${sensor_version}" - echo "Stackrox deployed with Central version - ${central_version}, Sensor version - ${sensor_version}" + + setup_client_TLS_certs rm -f FAIL From 1df7c5e42ea51ba08d33411a41c10f6d805d45fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:32:58 +0200 Subject: [PATCH 021/638] chore(deps): bump github.com/adhocore/gronx from 1.20.1 to 1.20.3 (#22378) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d3da3e27b50c4..95ce589f17e94 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/RoaringBitmap/roaring/v2 v2.24.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 - github.com/adhocore/gronx v1.20.1 + github.com/adhocore/gronx v1.20.3 github.com/andygrunwald/go-jira v1.17.0 github.com/aws/aws-sdk-go-v2 v1.43.4 github.com/aws/aws-sdk-go-v2/config v1.32.35 diff --git a/go.sum b/go.sum index b50c1aa557f73..fd34e62bd5cbd 100644 --- a/go.sum +++ b/go.sum @@ -183,8 +183,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= -github.com/adhocore/gronx v1.20.1 h1:knBhN6BrPz5zUI/S9xKAkXVHdojISoF0HyC5NuVrtG4= -github.com/adhocore/gronx v1.20.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.20.3 h1:DjvHxCqyz9WDzpodceOU814Z4GNpTvSpZs7oeX4ToKU= +github.com/adhocore/gronx v1.20.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= From ad837de881892494c830953d448d5609d214858f Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 20 Aug 2026 12:50:23 +0000 Subject: [PATCH 022/638] ROX-36211: bake signing key watch interval into deploy config (#22247) Co-authored-by: Claude Opus 4.6 --- deploy/common/k8sbased.sh | 16 ++++++++++ tests/e2e/lib.sh | 2 ++ tests/redhat_signing_key_test.go | 52 +++++++++++++++++++------------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index 256a4ef972363..33018eb4b4b8a 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -381,6 +381,9 @@ function launch_central { ) fi + # Shorten signing key watcher poll for e2e tests (production default is 4h). + helm_args+=(--set customize.central.envVars.ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL=5s) + if [[ -n "$POD_SECURITY_POLICIES" ]]; then helm_args+=( --set system.enablePodSecurityPolicies="${POD_SECURITY_POLICIES}" @@ -500,6 +503,19 @@ function launch_central { ROX_NAMESPACE="${central_namespace}" "${unzip_dir}/central/scripts/setup.sh" fi central_scripts_dir="$unzip_dir/central/scripts" + + # Shorten signing key watcher poll for e2e tests (production default is 4h). + # Bake it into the manifest so Central starts with fast polling instead of + # requiring a second rollout via a post-launch `set env`. + central_deployment="${unzip_dir}/central/01-central-13-deployment.yaml" + if [[ -f "${central_deployment}" ]]; then + ${ORCH_CMD} set env --local -o yaml -f "${central_deployment}" -c central \ + ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL=5s > "${central_deployment}.tmp" + mv "${central_deployment}.tmp" "${central_deployment}" + else + echo >&2 "WARNING: ${central_deployment} not found; Central will deploy with the default signing key watch interval and rely on the test's set env fallback." + fi + launch_service "${unzip_dir}" central echo diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 917c2aff19c92..0f5e0467fe36b 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -571,6 +571,8 @@ deploy_central_via_operator() { customize_envVars+=$'\n value: "'"${ROX_TELEMETRY_STORAGE_KEY_V1:-DISABLED}"'"' customize_envVars+=$'\n - name: ROX_RISK_REPROCESSING_INTERVAL' customize_envVars+=$'\n value: "15s"' + customize_envVars+=$'\n - name: ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL' + customize_envVars+=$'\n value: "5s"' customize_envVars+=$'\n - name: ROX_COMPLIANCE_ENHANCEMENTS' customize_envVars+=$'\n value: "true"' customize_envVars+=$'\n - name: ROX_COMPLIANCE_REPORTING' diff --git a/tests/redhat_signing_key_test.go b/tests/redhat_signing_key_test.go index bf931bb4841d7..6b7befb31b467 100644 --- a/tests/redhat_signing_key_test.go +++ b/tests/redhat_signing_key_test.go @@ -71,28 +71,24 @@ func TestRedHatSigningKey(t *testing.T) { func (s *RedHatSigningKeySuite) SetupSuite() { s.KubernetesSuite.SetupSuite() - s.conn = centralgrpc.GRPCConnectionToCentral(s.T()) - // Set once for the whole suite instead of per sub-test: every sub-test - // needs the watcher to poll quickly, and each Central restart costs - // ~30s, so sharing this one restart across sub-tests instead of paying - // it twice per sub-test saves several minutes of suite time. + // Deploy scripts (tests/e2e/lib.sh, deploy/common/k8sbased.sh) set + // watchIntervalEnv=5s at deploy time, so Central already polls quickly. + // This is a same-value no-op safety net for setups that deploy Central + // without the baked-in env var (e.g. local dev clusters). ns := namespaces.StackRox ctx, cancel := context.WithTimeout(context.Background(), waitTimeout+time.Minute) defer cancel() - s.logf("Setting %s=%s on central for the whole suite", watchIntervalEnv, shortWatchInterval) - s.mustSetDeploymentEnvVal(ctx, ns, "central", "central", watchIntervalEnv, shortWatchInterval) - s.waitUntilK8sDeploymentReady(ctx, ns, "central") -} + s.logf("Ensuring %s=%s on central", watchIntervalEnv, shortWatchInterval) + currentVal, _ := s.getDeploymentEnvVal(ctx, ns, "central", "central", watchIntervalEnv) + if currentVal == shortWatchInterval { + s.logf("%s already set to %s, skipping patch", watchIntervalEnv, shortWatchInterval) + } else { + s.mustSetDeploymentEnvVal(ctx, ns, "central", "central", watchIntervalEnv, shortWatchInterval) + } -// TearDownSuite reverts the watch-interval override applied in SetupSuite. -func (s *RedHatSigningKeySuite) TearDownSuite() { - ns := namespaces.StackRox - ctx, cancel := context.WithTimeout(context.Background(), waitTimeout+time.Minute) - defer cancel() - s.logf("Cleanup: removing %s env var", watchIntervalEnv) - s.mustDeleteDeploymentEnvVar(ctx, ns, "central", watchIntervalEnv) - s.waitUntilK8sDeploymentReady(ctx, ns, "central") + s.conn = centralgrpc.GRPCConnectionToCentral(s.T()) + s.waitForCentralReady(ctx) } func (s *RedHatSigningKeySuite) siClient() v1.SignatureIntegrationServiceClient { @@ -103,6 +99,20 @@ func (s *RedHatSigningKeySuite) listIntegrations(ctx context.Context) (*v1.ListS return s.siClient().ListSignatureIntegrations(ctx, &v1.Empty{}) } +// waitForCentralReady waits for Central's k8s deployment readiness AND gRPC +// API responsiveness. On OCP with HAProxy Routes, gRPC connection recovery +// after a backend restart lags 10-30s beyond k8s deployment readiness. +func (s *RedHatSigningKeySuite) waitForCentralReady(ctx context.Context) { + ns := namespaces.StackRox + s.waitUntilK8sDeploymentReady(ctx, ns, "central") + mustEventually(s.T(), ctx, func() error { + rpcCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + _, err := s.listIntegrations(rpcCtx) + return err + }, 2*time.Second, "Central gRPC API not yet responsive after restart") +} + // waitForIntegrationKeys polls until the Red Hat integration has exactly the expected key names. func (s *RedHatSigningKeySuite) waitForIntegrationKeys(ctx context.Context, expectedNames []string, description string) { t := s.T() @@ -431,7 +441,7 @@ func (s *RedHatSigningKeySuite) TestUpdaterDownloadsBundleFromHTTP() { defer func() { s.logf("Cleanup: removing updater env var from Central") s.mustDeleteDeploymentEnvVar(overallCtx, ns, "central", bundleURLEnv) - s.waitUntilK8sDeploymentReady(overallCtx, ns, "central") + s.waitForCentralReady(overallCtx) }() // The update interval isn't set: it always gets clamped to a 5-minute @@ -439,7 +449,7 @@ func (s *RedHatSigningKeySuite) TestUpdaterDownloadsBundleFromHTTP() { // depends on the unconditional first download at startup (see above). s.logf("Setting %s on central", bundleURLEnv) s.mustSetDeploymentEnvVal(testCtx, ns, "central", "central", bundleURLEnv, bundleURL) - s.waitUntilK8sDeploymentReady(testCtx, ns, "central") + s.waitForCentralReady(testCtx) s.logf("Waiting for updater to download the bundle and watcher to upsert keys") s.waitForIntegrationKeys(testCtx, []string{"updater-key-1", "updater-key-2"}, @@ -567,7 +577,7 @@ func (s *RedHatSigningKeySuite) TestOfflineModeIgnoresHTTPUpdater() { s.logf("Cleanup: removing env vars from Central") s.mustDeleteDeploymentEnvVar(overallCtx, ns, "central", offlineModeEnv) s.mustDeleteDeploymentEnvVar(overallCtx, ns, "central", bundleURLEnv) - s.waitUntilK8sDeploymentReady(overallCtx, ns, "central") + s.waitForCentralReady(overallCtx) }() // The update interval isn't set here for the same reason as in @@ -578,7 +588,7 @@ func (s *RedHatSigningKeySuite) TestOfflineModeIgnoresHTTPUpdater() { // --- Step 3: Wait for Central to restart --- - s.waitUntilK8sDeploymentReady(testCtx, ns, "central") + s.waitForCentralReady(testCtx) // --- Step 4: Verify the HTTP updater did NOT fetch the decoy bundle --- From 79eac644dbede12ac9caf3f4848924d9e33f07ca Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 20 Aug 2026 14:03:52 +0000 Subject: [PATCH 023/638] fix(ci): Always mention author on unit test failures (#22382) Co-authored-by: Claude Sonnet 4.5 --- .github/workflows/unit-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index fd943d81c93be..d5ca5b28812be 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -585,7 +585,7 @@ jobs: - name: Slack message env: GITHUB_CONTEXT: ${{ toJSON(github) }} - mention_author: ${{ needs.go.outputs.new-jiras || needs.go-postgres.outputs.new-jiras || needs.local-roxctl-tests.outputs.new-jiras || needs.ui.outputs.new-jiras || needs.go.outputs.new-jiras || needs.shell-unit-tests.outputs.new-jiras || needs.sensor-integration-tests.outputs.new-jiras }} + mention_author: true run: | source scripts/ci/lib.sh slack_workflow_failure From 059cda19ef292bd7a015aa5f26486dd08b0a88dd Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:49:28 -0500 Subject: [PATCH 024/638] ROX-36100: Node reports page scaffolding (#22185) --- .../platform/src/Containers/MainPage/Body.tsx | 8 +++ .../NodeVulnerabilityReportingLayout.tsx | 67 +++++++++++++++++++ .../NodeVulnerabilityReportingPage.tsx | 31 +++++++++ ui/apps/platform/src/routePaths.ts | 9 +++ 4 files changed, 115 insertions(+) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx diff --git a/ui/apps/platform/src/Containers/MainPage/Body.tsx b/ui/apps/platform/src/Containers/MainPage/Body.tsx index e7cf9d4e56cd3..5bd1480d5e434 100644 --- a/ui/apps/platform/src/Containers/MainPage/Body.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Body.tsx @@ -52,6 +52,7 @@ import { vulnerabilitiesUserWorkloadsPath, vulnerabilitiesVirtualMachineCvesPath, vulnerabilitiesWorkloadCvesPath, + vulnerabilityNodeReportsPath, vulnerabilityReportsPath, } from 'routePaths'; import type { RouteKey } from 'routePaths'; @@ -291,6 +292,13 @@ const routeComponentMap: Record = { component: makeVulnMgmtUserWorkloadView('images-without-cves'), path: vulnerabilitiesImagesWithoutCvesPath, }, + 'vulnerabilities/node-reports': { + component: asyncComponent( + () => + import('Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage') + ), + path: vulnerabilityNodeReportsPath, + }, 'vulnerabilities/reports': { component: asyncComponent( () => import('Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage') diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx new file mode 100644 index 0000000000000..cb18ddab84e19 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx @@ -0,0 +1,67 @@ +import { PageSection, Tab, TabContent, Tabs, Title } from '@patternfly/react-core'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom-v5-compat'; + +import PageTitle from 'Components/PageTitle'; +import usePermissions from 'hooks/usePermissions'; +import { + vulnerabilityNodeConfigurationReportsPath, + vulnerabilityNodeViewBasedReportsPath, +} from 'routePaths'; + +function NodeVulnerabilityReportingLayout() { + const location = useLocation(); + const navigate = useNavigate(); + + const { hasReadAccess } = usePermissions(); + const hasAccessForConfiguration = hasReadAccess('WorkflowAdministration'); + + const activeKey = location.pathname.startsWith(vulnerabilityNodeConfigurationReportsPath) + ? 'configuration' + : 'view-based'; + + return ( + <> + + + Node vulnerability reports + + + { + navigate( + tabKey === 'configuration' + ? vulnerabilityNodeConfigurationReportsPath + : vulnerabilityNodeViewBasedReportsPath + ); + }} + usePageInsets + > + {hasAccessForConfiguration && ( + + )} + + + + + + + + ); +} + +export default NodeVulnerabilityReportingLayout; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx new file mode 100644 index 0000000000000..68c0977ee38d3 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx @@ -0,0 +1,31 @@ +import { Navigate, Route, Routes } from 'react-router-dom-v5-compat'; + +import usePermissions from 'hooks/usePermissions'; + +import NodeVulnerabilityReportingLayout from './NodeVulnerabilityReportingLayout'; + +function NodeVulnerabilityReportingPage() { + const { hasReadAccess } = usePermissions(); + const hasAccessForConfiguration = hasReadAccess('WorkflowAdministration'); + + return ( + + }> + } /> + List view + ) : ( + + ) + } + /> + View-based reports} /> + + + ); +} + +export default NodeVulnerabilityReportingPage; diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 92fa55b5b2b39..2935d471d0827 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -102,6 +102,10 @@ export const vulnerabilityReportsPath = `${vulnerabilitiesBasePath}/reports`; export const vulnerabilityConfigurationReportsPath = `${vulnerabilityReportsPath}/configuration`; export const vulnerabilityViewBasedReportsPath = `${vulnerabilityReportsPath}/view-based`; +export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/node`; +export const vulnerabilityNodeConfigurationReportsPath = `${vulnerabilityNodeReportsPath}/configuration`; +export const vulnerabilityNodeViewBasedReportsPath = `${vulnerabilityNodeReportsPath}/view-based`; + // Vulnerability Management 1.0 path for links from Dashboard: export const vulnManagementImagesPath = `${vulnManagementPath}/images`; @@ -193,6 +197,7 @@ export type RouteKey = | 'violations' | 'vulnerabilities/exception-management' | 'vulnerabilities/node-cves' + | 'vulnerabilities/node-reports' | 'vulnerabilities/reports' | 'vulnerabilities/user-workloads' | 'vulnerabilities/platform' @@ -365,6 +370,10 @@ const routeRequirementsMap: Record = { 'vulnerabilities/node-cves': { resourceAccessRequirements: everyResource(['Cluster', 'Node']), }, + 'vulnerabilities/node-reports': { + featureFlagRequirements: allEnabled(['ROX_NODE_VULNERABILITY_REPORTS']), + resourceAccessRequirements: everyResource(['Node']), + }, 'vulnerabilities/platform-cves': { featureFlagRequirements: allEnabled(['ROX_LEGACY_SCANNER']), resourceAccessRequirements: everyResource(['Cluster']), From 9ec768f9dff355adac325cf8be72ea1f37834e63 Mon Sep 17 00:00:00 2001 From: David Vail Date: Thu, 20 Aug 2026 12:30:12 -0400 Subject: [PATCH 025/638] ROX-35787: Add a deprecation banner to config mgmt (#22186) --- .../ConfigManagement/ConfigManagementRoutes.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ui/apps/platform/src/Containers/ConfigManagement/ConfigManagementRoutes.jsx b/ui/apps/platform/src/Containers/ConfigManagement/ConfigManagementRoutes.jsx index 64da6705b0354..34365225fe104 100644 --- a/ui/apps/platform/src/Containers/ConfigManagement/ConfigManagementRoutes.jsx +++ b/ui/apps/platform/src/Containers/ConfigManagement/ConfigManagementRoutes.jsx @@ -1,6 +1,7 @@ import { memo } from 'react'; import { Route, Routes } from 'react-router-dom-v5-compat'; import isEqual from 'lodash/isEqual'; +import { Alert } from '@patternfly/react-core'; import PageNotFound from 'Components/PageNotFound'; import searchContext from 'Containers/searchContext'; import { searchParams } from 'constants/searchParams'; @@ -14,6 +15,15 @@ const entityPath = `:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:enti const ConfigManagementRoutes = () => ( + + Security configuration data will integrate directly into risk and policy management + workflows to enhance visibility without relying on a standalone dashboard. + } /> } /> From 3fd218286647b80d930eab001872bcc985703057 Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:50:03 -0400 Subject: [PATCH 026/638] ROX-34928: Add CISA KEV column to vulnerability reports (#21752) --- central/cve/converter/utils/convert_utils.go | 4 ++-- .../imagev2/datastore/store/postgres/store.go | 2 ++ .../scheduler/v2/reportgenerator/csv_gen.go | 23 +++++++++++++++++-- .../v2/reportgenerator/report_gen_impl.go | 2 ++ .../scheduler/v2/reportgenerator/types.go | 5 ++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/central/cve/converter/utils/convert_utils.go b/central/cve/converter/utils/convert_utils.go index cddd6d82bf5b8..64f0284b2631a 100644 --- a/central/cve/converter/utils/convert_utils.go +++ b/central/cve/converter/utils/convert_utils.go @@ -214,7 +214,7 @@ func ImageCVEToEmbeddedVulnerability(vuln *storage.ImageCVE) *storage.EmbeddedVu NvdCvss: vuln.GetNvdcvss(), Epss: vuln.GetCveBaseInfo().GetEpss(), Exploit: vuln.GetCveBaseInfo().GetExploit(), - CisaKev: vuln.GetCveBaseInfo().GetExploit() != nil, + CisaKev: vuln.GetCveBaseInfo().GetCisaKev(), } if vuln.GetCveBaseInfo().GetCvssV3() != nil { embeddedCVE.ScoreVersion = storage.EmbeddedVulnerability_V3 @@ -302,7 +302,7 @@ func EmbeddedVulnerabilityToImageCVE(os string, from *storage.EmbeddedVulnerabil CvssV3: from.GetCvssV3(), Epss: from.GetEpss(), Exploit: from.GetExploit(), - CisaKev: from.GetExploit() != nil, + CisaKev: from.GetCisaKev(), }, Cvss: from.GetCvss(), Nvdcvss: nvdCvss, diff --git a/central/imagev2/datastore/store/postgres/store.go b/central/imagev2/datastore/store/postgres/store.go index bd475da9c840c..4997af72493c6 100644 --- a/central/imagev2/datastore/store/postgres/store.go +++ b/central/imagev2/datastore/store/postgres/store.go @@ -320,6 +320,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta "cvebaseinfo_publishedon", "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", + "cvebaseinfo_cisakev", "cvss", "severity", "impactscore", @@ -361,6 +362,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta protocompat.NilOrTime(obj.GetCveBaseInfo().GetPublishedOn()), protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), + obj.GetCveBaseInfo().GetCisaKev(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 8880451710b1b..42b115b6776b1 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -4,12 +4,14 @@ import ( "archive/zip" "bytes" "fmt" + "slices" "strconv" "strings" "time" "github.com/pkg/errors" "github.com/stackrox/rox/pkg/csv" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/stringutils" ) @@ -38,7 +40,13 @@ var ( // GenerateCSV takes in the results of vuln report query, converts to CSV and returns zipped data func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*bytes.Buffer, error) { // add header for component version - csvWriter := csv.NewGenericWriter(csvHeader, true) + csvHeaderCols := csvHeader + if features.KnownExploitedVulnerabilities.Enabled() { + csvHeaderCols = append(csvHeaderCols[:0:0], csvHeader...) + epssIdx := slices.Index(csvHeaderCols, "EPSS Probability Percentage") + csvHeaderCols = slices.Insert(csvHeaderCols, epssIdx+1, "CISA KEV") + } + csvWriter := csv.NewGenericWriter(csvHeaderCols, true) for _, r := range cveResponses { var epssScore string @@ -61,11 +69,22 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt strconv.FormatFloat(r.GetCVSS(), 'f', 2, 64), strconv.FormatFloat(r.GetNVDCVSS(), 'f', 2, 64), epssScore, + } + if features.KnownExploitedVulnerabilities.Enabled() { + var cisaKev string + if r.GetCisaKev() != nil { + cisaKev = strconv.FormatBool(*r.GetCisaKev()) + } else { + cisaKev = "Not Available" + } + row = append(row, cisaKev) + } + row = append(row, r.GetDiscoveredAtImage(), r.Link, r.GetAdvisoryName(), r.GetAdvisoryLink(), - } + ) csvWriter.AddValue(row) } diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index c9f0fd98a514b..3a6bd2c511160 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -555,6 +555,7 @@ func getSelectsWatchedImages() []*v1.QuerySelect { search.NewQuerySelect(search.NVDCVSS).Proto(), search.NewQuerySelect(search.FirstImageOccurrenceTimestamp).Proto(), search.NewQuerySelect(search.EPSSProbablity).Proto(), + search.NewQuerySelect(search.CisaKev).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), } @@ -578,6 +579,7 @@ func getSelectsDeployedImages() []*v1.QuerySelect { search.NewQuerySelect(search.Namespace).Proto(), search.NewQuerySelect(search.DeploymentName).Proto(), search.NewQuerySelect(search.EPSSProbablity).Proto(), + search.NewQuerySelect(search.CisaKev).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), } diff --git a/central/reports/scheduler/v2/reportgenerator/types.go b/central/reports/scheduler/v2/reportgenerator/types.go index 248b4669a995e..5f0fce8f81582 100644 --- a/central/reports/scheduler/v2/reportgenerator/types.go +++ b/central/reports/scheduler/v2/reportgenerator/types.go @@ -45,6 +45,7 @@ type ImageCVEQueryResponse struct { CVSS *float64 `db:"cvss"` NVDCVSS *float64 `db:"nvd_cvss"` EPSSProbability *float64 `db:"epss_probability"` + CisaKev *bool `db:"cisa_kev"` DiscoveredAtImage *time.Time `db:"first_image_occurrence_timestamp"` AdvisoryName *string `db:"advisory_name"` AdvisoryLink *string `db:"advisory_link"` @@ -147,6 +148,10 @@ func (res *ImageCVEQueryResponse) GetEPSSProbability() *float64 { return res.EPSSProbability } +func (res *ImageCVEQueryResponse) GetCisaKev() *bool { + return res.CisaKev +} + func (res *ImageCVEQueryResponse) GetAdvisoryName() string { if res.AdvisoryName == nil { return "Not Available" From 6b03a46337efc8be964b03070dae8cbbe673997b Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Thu, 20 Aug 2026 16:13:14 -0300 Subject: [PATCH 027/638] ROX-36140: per-bundle error isolation in runMultiBundleUpdate (#22312) --- scanner/matcher/updater/vuln/updater.go | 28 +++-- scanner/matcher/updater/vuln/updater_test.go | 103 +++++++++++++++++++ 2 files changed, 123 insertions(+), 8 deletions(-) diff --git a/scanner/matcher/updater/vuln/updater.go b/scanner/matcher/updater/vuln/updater.go index 809dd492e7891..04379f7abee5a 100644 --- a/scanner/matcher/updater/vuln/updater.go +++ b/scanner/matcher/updater/vuln/updater.go @@ -373,9 +373,6 @@ func (u *Updater) Update(ctx context.Context) error { err error ) updated, err = u.runMultiBundleUpdate(ctx) - if err != nil { - return err - } // Only bother running the GC when it's not disabled // and when the vulnerabilities have been updated. @@ -386,7 +383,7 @@ func (u *Updater) Update(ctx context.Context) error { slog.InfoContext(ctx, "no vulnerability updates: skipping GC") } - return nil + return err } // runMultiBundleUpdate updates the vulnerability data with a multi-bundle and @@ -446,14 +443,29 @@ func (u *Updater) runMultiBundleUpdate(ctx context.Context) (bool, error) { slog.InfoContext(ctx, "pre-registered vulnerability bundles", "count", len(bundles)) // Process each vulnerability bundle in the .zip archive. + // Errors are collected so all bundles are attempted even when some fail. + // + // TODO(ROX-36437): succeeded counts lock-skipped and already-current + // bundles as successes; distinguish genuinely imported bundles from + // skipped ones. + var bundleErrs []error + succeeded := 0 for _, bundleF := range bundles { bundleCtx := log.With(ctx, "bundle", bundleF.Name) slog.InfoContext(bundleCtx, "starting bundle update") if err := u.updateBundle(bundleCtx, bundleF, zipTime, prevTime); err != nil { slog.ErrorContext(bundleCtx, "updating bundle failed", "reason", err) - return false, fmt.Errorf("updating bundle %s: %w", bundleF.Name, err) + bundleErrs = append(bundleErrs, fmt.Errorf("bundle %s: %w", bundleF.Name, err)) + continue } slog.InfoContext(bundleCtx, "completed bundle update") + succeeded++ + } + + // Skip GC and distribution update only when every bundle that was + // attempted returned an error (i.e., no partial success to build on). + if len(bundleErrs) > 0 && succeeded == 0 { + return false, errors.Join(bundleErrs...) } // Clean updaters that were deleted (not in the zip and older than this update). @@ -464,17 +476,17 @@ func (u *Updater) runMultiBundleUpdate(ctx context.Context) (bool, error) { } err = u.metadataStore.GCVulnerabilityUpdates(ctx, names, zipTime) if err != nil { - return false, fmt.Errorf("cleaning vuln updates: %w", err) + return false, errors.Join(fmt.Errorf("cleaning vuln updates: %w", err), errors.Join(bundleErrs...)) } err = u.distManager.update(ctx) if err != nil { - return false, fmt.Errorf("updating known-distributions: %w", err) + return false, errors.Join(fmt.Errorf("updating known-distributions: %w", err), errors.Join(bundleErrs...)) } _ = u.Initialized(ctx) - return true, nil + return true, errors.Join(bundleErrs...) } func (u *Updater) updateBundle(ctx context.Context, zipF *zip.File, zipTime time.Time, prevTime time.Time) error { diff --git a/scanner/matcher/updater/vuln/updater_test.go b/scanner/matcher/updater/vuln/updater_test.go index bf3ab7505ba36..d69527916bc17 100644 --- a/scanner/matcher/updater/vuln/updater_test.go +++ b/scanner/matcher/updater/vuln/updater_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/klauspost/compress/zstd" "github.com/quay/claircore" "github.com/quay/claircore/datastore" "github.com/quay/claircore/libvuln/driver" @@ -426,6 +427,108 @@ func TestIsBundleAllowed(t *testing.T) { } } +func TestRunMultiBundleUpdate_PartialFailure(t *testing.T) { + bundleNames := []string{"alpine.json.zst", "nvd.json.zst", "rhel-vex.json.zst"} + + // Create a minimal valid zstd frame around empty content. + makeZstd := func() []byte { + var buf bytes.Buffer + w, err := zstd.NewWriter(&buf) + require.NoError(t, err) + require.NoError(t, w.Close()) + return buf.Bytes() + } + + srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for _, name := range bundleNames { + f, err := zw.Create(name) + require.NoError(t, err) + _, err = f.Write(makeZstd()) + require.NoError(t, err) + } + require.NoError(t, zw.Close()) + return bytes.NewReader(buf.Bytes()) + }) + + ctrl := gomock.NewController(t) + store := mocks.NewMockMatcherStore(ctrl) + metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) + + prevTime := now.Add(-time.Minute) + + // importFunc fails for the second call (nvd), succeeds for the others. + importCallCount := 0 + u := &Updater{ + locker: &testLocker{locker: updates.NewLocalLockSource()}, + store: store, + metadataStore: metadataStore, + client: srv.Client(), + urls: []string{srv.URL}, + root: t.TempDir(), + skipGC: true, + importFunc: func(_ context.Context, _ io.Reader) error { + importCallCount++ + if importCallCount == 2 { + return errors.New("fake import error") + } + return nil + }, + retryDelay: 1 * time.Second, + retryMax: 1, + distManager: newDistManager(store), + } + + // Start of runMultiBundleUpdate. + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(prevTime, nil) + + // Pre-registration phase: one call per bundle. + preReg := make([]*gomock.Call, len(bundleNames)) + for i, name := range bundleNames { + preReg[i] = metadataStore.EXPECT(). + GetOrSetLastVulnerabilityUpdate(gomock.Any(), name, prevTime). + Return(prevTime, nil) + } + + // Processing phase: return prevTime so each bundle proceeds to import. + for _, name := range bundleNames { + call := metadataStore.EXPECT(). + GetOrSetLastVulnerabilityUpdate(gomock.Any(), name, prevTime). + Return(prevTime, nil) + for _, pre := range preReg { + call.After(pre) + } + } + + // SetLastVulnerabilityUpdate is called only for the two bundles that succeed. + metadataStore.EXPECT(). + SetLastVulnerabilityUpdate(gomock.Any(), "alpine.json.zst", now). + Return(nil) + metadataStore.EXPECT(). + SetLastVulnerabilityUpdate(gomock.Any(), "rhel-vex.json.zst", now). + Return(nil) + + // GC and distribution update run because at least one bundle succeeded. + metadataStore.EXPECT(). + GCVulnerabilityUpdates(gomock.Any(), gomock.Any(), now). + Return(nil) + store.EXPECT(). + Distributions(gomock.Any()). + Return(nil, nil) + + // Initialized check at end of runMultiBundleUpdate. + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(now, nil) + + err := u.Update(test.Logging(t)) + assert.ErrorContains(t, err, "nvd.json.zst") + assert.Equal(t, 3, importCallCount, "all three bundles must be attempted") +} + func TestIsRetryableDialError(t *testing.T) { testCases := map[string]struct { err error From af9a106e91b0ece866087042a12db464848efed8 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:14:18 -0500 Subject: [PATCH 028/638] ROX-36101: Node reports list view (#22320) --- .../NodeConfigReportsTab.tsx | 226 ++++++++++++++++++ .../NodeVulnerabilityReportingPage.tsx | 3 +- .../nodeVulnerabilityReports.types.ts | 24 -- ui/apps/platform/src/routePaths.ts | 1 + .../src/services/NodeReportsService.ts | 41 ++++ .../src/services/ReportsService.types.ts | 39 ++- ui/apps/platform/src/utils/searchUtils.ts | 9 +- 7 files changed, 298 insertions(+), 45 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/nodeVulnerabilityReports.types.ts create mode 100644 ui/apps/platform/src/services/NodeReportsService.ts diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx new file mode 100644 index 0000000000000..11b2412fa98c1 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx @@ -0,0 +1,226 @@ +import { useCallback, useState } from 'react'; +import { Link, generatePath } from 'react-router-dom-v5-compat'; +import { + Button, + Content, + Flex, + FlexItem, + PageSection, + Pagination, + SearchInput, + Toolbar, + ToolbarContent, + ToolbarItem, +} from '@patternfly/react-core'; +import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; + +import HelpIconTh from 'Components/HelpIconTh'; +import PageTitle from 'Components/PageTitle'; +import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import usePermissions from 'hooks/usePermissions'; +import useRestQuery from 'hooks/useRestQuery'; +import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; +import useURLSort from 'hooks/useURLSort'; +import { + vulnerabilityNodeConfigurationReportDetailsPath, + vulnerabilityNodeConfigurationReportsPath, +} from 'routePaths'; +import { + fetchNodeReportConfigurations, + fetchNodeReportConfigurationsCount, +} from 'services/NodeReportsService'; +import { getTableUIState } from 'utils/getTableUIState'; + +const CreateReportsButton = () => { + return ( + + + + ); +}; + +const reportNameSearchKey = 'Report Name'; + +const sortOptions = { + sortFields: [reportNameSearchKey], + defaultSortOption: { field: reportNameSearchKey, direction: 'asc' } as const, +}; + +function NodeConfigReportsTab() { + const { hasReadWriteAccess, hasReadAccess } = usePermissions(); + const hasWriteAccessForReport = + hasReadWriteAccess('WorkflowAdministration') && hasReadAccess('Integration'); + + const { page, perPage, setPage, setPerPage } = useURLPagination(10); + const { sortOption, getSortParams } = useURLSort(sortOptions); + const { searchFilter, setSearchFilter } = useURLSearch(); + const [searchValue, setSearchValue] = useState(() => { + const value = searchFilter?.[reportNameSearchKey]; + return typeof value === 'string' ? value : ''; + }); + + const fetchReportConfigurationsCallback = useCallback( + () => + fetchNodeReportConfigurations({ + searchFilter, + page, + perPage, + sortOption, + }), + [searchFilter, page, perPage, sortOption] + ); + const { + data: reportConfigurations, + isLoading: isListLoading, + error: listError, + } = useRestQuery(fetchReportConfigurationsCallback); + + const fetchReportConfigurationsCountCallback = useCallback( + () => fetchNodeReportConfigurationsCount(searchFilter), + [searchFilter] + ); + const { data: countData } = useRestQuery(fetchReportConfigurationsCountCallback); + + // table should still load even if countData is not available + const totalReports = countData?.count ?? 0; + + const tableState = getTableUIState({ + isLoading: isListLoading, + data: reportConfigurations, + error: listError, + searchFilter, + }); + + const colSpan = hasWriteAccessForReport ? 5 : 4; + + function onClearFilters() { + setSearchValue(''); + setSearchFilter({}); + setPage(1); + } + + return ( + <> + + + + + + Configure scheduled reports for node vulnerabilities. + + + {reportConfigurations && + reportConfigurations.length > 0 && + hasWriteAccessForReport && ( + + + + )} + + + + + + + setSearchValue(value)} + onSearch={(_event, value) => { + setSearchValue(value); + setSearchFilter({ [reportNameSearchKey]: value }); + setPage(1); + }} + onClear={onClearFilters} + /> + + + setPage(newPage)} + onPerPageSelect={(_, newPerPage) => setPerPage(newPerPage)} + isCompact + /> + + + + + + + + + A set of user-configured rules to select nodes by Custom + scope + + } + > + Resources + + + + {hasWriteAccessForReport && + + : undefined, + }} + filteredEmptyProps={{ onClearFilters }} + renderer={({ data }) => + data.map((report) => { + const reportURL = generatePath( + vulnerabilityNodeConfigurationReportDetailsPath, + { + reportId: report.id, + } + ); + + return ( + + + + + + + {hasWriteAccessForReport && ( + + )} + + + ); + }) + } + /> +
ReportDescriptionMy last job status} +
+ {report.name} + Custom scope + {report.description || '-'} + ROX-36102 + +
+
+ + ); +} + +export default NodeConfigReportsTab; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx index 68c0977ee38d3..7aa347acbfe00 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx @@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from 'react-router-dom-v5-compat'; import usePermissions from 'hooks/usePermissions'; +import NodeConfigReportsTab from './NodeConfigReportsTab'; import NodeVulnerabilityReportingLayout from './NodeVulnerabilityReportingLayout'; function NodeVulnerabilityReportingPage() { @@ -16,7 +17,7 @@ function NodeVulnerabilityReportingPage() { path="configuration" element={ hasAccessForConfiguration ? ( -
List view
+ ) : ( ) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/nodeVulnerabilityReports.types.ts b/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/nodeVulnerabilityReports.types.ts deleted file mode 100644 index c4e214aee730c..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/nodeVulnerabilityReports.types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { CvesSince, EntityScope, NotifierConfiguration } from 'services/ReportsService.types'; -import type { Schedule } from 'types/schedule.proto'; - -export type NodeVulnerabilityResourcesConfiguration = { - resourceScope: { - entityScope: EntityScope; - }; -}; - -export type NodeVulnerabilityFiltersConfiguration = { - vulnReportFilters: { - query: string; - } & CvesSince; -}; - -export type NodeVulnerabilityReportConfiguration = { - id: string; - name: string; - description: string; - type: 'NODE_VULNERABILITY'; - notifiers: NotifierConfiguration[]; - schedule: Schedule | null; -} & NodeVulnerabilityResourcesConfiguration & - NodeVulnerabilityFiltersConfiguration; diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 2935d471d0827..f688a185caad4 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -104,6 +104,7 @@ export const vulnerabilityViewBasedReportsPath = `${vulnerabilityReportsPath}/vi export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/node`; export const vulnerabilityNodeConfigurationReportsPath = `${vulnerabilityNodeReportsPath}/configuration`; +export const vulnerabilityNodeConfigurationReportDetailsPath = `${vulnerabilityNodeConfigurationReportsPath}/:reportId`; export const vulnerabilityNodeViewBasedReportsPath = `${vulnerabilityNodeReportsPath}/view-based`; // Vulnerability Management 1.0 path for links from Dashboard: diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts new file mode 100644 index 0000000000000..d577cf5f28267 --- /dev/null +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -0,0 +1,41 @@ +import queryString from 'qs'; + +import type { SearchFilter, SearchQueryOptions } from 'types/search'; +import { getListQueryParams, getRequestQueryStringForSearchFilter } from 'utils/searchUtils'; + +import { makeCancellableAxiosRequest } from './cancellationUtils'; +import type { CancellableRequest } from './cancellationUtils'; +import type { NodeVulnerabilityReportConfiguration } from './ReportsService.types'; +import axios from './instance'; + +export function fetchNodeReportConfigurations({ + searchFilter, + page, + perPage, + sortOption, +}: SearchQueryOptions): CancellableRequest { + const params = getListQueryParams({ searchFilter, sortOption, page, perPage }); + return makeCancellableAxiosRequest((signal) => + axios + .get<{ + reportConfigs: NodeVulnerabilityReportConfiguration[]; + }>(`/v2/reports/node/configurations?${params}`, { signal }) + .then((response) => response?.data?.reportConfigs ?? []) + ); +} + +export function fetchNodeReportConfigurationsCount( + searchFilter: SearchFilter +): CancellableRequest<{ count: number }> { + const params = queryString.stringify( + { query: getRequestQueryStringForSearchFilter(searchFilter) }, + { arrayFormat: 'repeat' } + ); + return makeCancellableAxiosRequest((signal) => + axios + .get<{ count: number }>(`/v2/reports/node/configuration-count?${params}`, { signal }) + .then((response) => { + return response.data; + }) + ); +} diff --git a/ui/apps/platform/src/services/ReportsService.types.ts b/ui/apps/platform/src/services/ReportsService.types.ts index a5f18bcd461cb..e3cb5cce3972e 100644 --- a/ui/apps/platform/src/services/ReportsService.types.ts +++ b/ui/apps/platform/src/services/ReportsService.types.ts @@ -10,12 +10,20 @@ export type ReportConfigurationBase = { name: string; description: string; notifiers: NotifierConfiguration[]; - schedule: Schedule | null; + schedule: ReportSchedule | null; }; -export type GenericVulnerabilityReportConfiguration = { +export type NodeVulnerabilityReportFilters = { + allVuln: boolean; + query: string; +}; + +// Not exactly 1:1 with the proto, which uses a oneOf that technically allows mismatched +// type/filters (e.g. node type with image filters). Backend confirmed that can't happen, so +// we pin `type` to its matching filters to simplify the typing. +export type NodeVulnerabilityReportConfiguration = { type: 'NODE_VULNERABILITY'; - vulnReportFilters: GenericVulnerabilityReportFilters; + nodeVulnReportFilters: NodeVulnerabilityReportFilters; resourceScope: { entityScope: EntityScope; }; @@ -74,10 +82,6 @@ export type CvesSince = sinceStartDate: string; // in the format of google.protobuf.Timestamp}; }; -export type GenericVulnerabilityReportFilters = { - query: string; -} & CvesSince; - export type ViewBasedVulnerabilityReportFilters = { query: string; }; @@ -99,7 +103,7 @@ export type DaysOfMonth = { export type Interval = DaysOfWeek | DaysOfMonth; -export type Schedule = +export type ReportSchedule = | { intervalType: 'WEEKLY'; hour: number; @@ -111,6 +115,11 @@ export type Schedule = hour: number; minute: number; daysOfMonth: DaysOfMonth; + } + | { + intervalType: 'DAILY'; + hour: number; + minute: number; }; // Notification types @@ -127,9 +136,13 @@ export type NotifierConfiguration = { // Resource scope types -export type ResourceScope = { - collectionScope: CollectionScope; -}; +export type ResourceScope = + | { + collectionScope: CollectionScope; + } + | { + entityScope: EntityScope; + }; export type CollectionScope = { collectionId: string; @@ -184,7 +197,7 @@ export type ViewBasedReportSnapshot = Snapshot & { // TODO temporary disjunction until snamshot has type property. type VulnerabilityReportFilters = - | GenericVulnerabilityReportFilters + | NodeVulnerabilityReportFilters | ImageVulnerabilityReportFiltersForCollection | ImageVulnerabilityReportFiltersForEntity; @@ -193,7 +206,7 @@ export type ConfiguredReportSnapshot = Snapshot & { reportConfigId: string; vulnReportFilters: VulnerabilityReportFilters; collectionSnapshot: CollectionSnapshot; - schedule: Schedule | null; + schedule: ReportSchedule | null; notifiers: NotifierConfiguration[]; }; diff --git a/ui/apps/platform/src/utils/searchUtils.ts b/ui/apps/platform/src/utils/searchUtils.ts index 82f329ca838de..6fa8ec55c075d 100644 --- a/ui/apps/platform/src/utils/searchUtils.ts +++ b/ui/apps/platform/src/utils/searchUtils.ts @@ -220,16 +220,11 @@ export function flattenFilterValue( * @param options.perPage The number of items per page */ export function getListQueryParams({ - searchFilter, + searchFilter = {}, sortOption, page, perPage, -}: { - searchFilter: SearchFilter; - sortOption: ApiSortOption; - page: number; - perPage: number; -}): string { +}: SearchQueryOptions): string { const query = getRequestQueryStringForSearchFilter(searchFilter); return qs.stringify( { From d4c8d02097ab49daacdb77e39e751092161e5bc2 Mon Sep 17 00:00:00 2001 From: Cong Du Date: Thu, 20 Aug 2026 20:43:50 -0700 Subject: [PATCH 029/638] ROX-36393: fix policy Category autocomplete returning no options (#22342) Co-authored-by: Claude Sonnet 4.6 (1M context) --- central/globalindex/mapping/mapping.go | 8 ++++++- central/search/service/service_impl_test.go | 9 ++++++++ pkg/search/policycategory/transform.go | 10 +++++++++ .../src/test/groovy/AutocompleteTest.groovy | 22 +++++++++++++++---- .../policies/policiesTable.test.js | 22 ++++++++++++++++++- 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/central/globalindex/mapping/mapping.go b/central/globalindex/mapping/mapping.go index 7226709613ab4..a62952f6a41e6 100644 --- a/central/globalindex/mapping/mapping.go +++ b/central/globalindex/mapping/mapping.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/postgres/schema" "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/search/policycategory" ) // GetEntityOptionsMap is a mapping from search categories to the options @@ -52,6 +53,11 @@ func GetEntityOptionsMap() map[v1.SearchCategory]search.OptionsMap { schema.RoleBindingsSchema.OptionsMap, ) + policiesSearchOptions := search.CombineOptionsMaps( + policycategory.CategorySearchOptions, + schema.PoliciesSchema.OptionsMap, + ) + // EntityOptionsMap is a mapping from search categories to the options map for that category. // search document maps are also built off this map entityOptionsMap := map[v1.SearchCategory]search.OptionsMap{ @@ -69,7 +75,7 @@ func GetEntityOptionsMap() map[v1.SearchCategory]search.OptionsMap { v1.SearchCategory_NODE_VULNERABILITIES: nodeToVulnerabilitySearchOptions, v1.SearchCategory_NODES: nodeToVulnerabilitySearchOptions, v1.SearchCategory_PODS: schema.PodsSchema.OptionsMap, - v1.SearchCategory_POLICIES: schema.PoliciesSchema.OptionsMap, + v1.SearchCategory_POLICIES: policiesSearchOptions, v1.SearchCategory_POLICY_CATEGORIES: schema.PolicyCategoriesSchema.OptionsMap, v1.SearchCategory_PROCESS_BASELINES: schema.ProcessBaselinesSchema.OptionsMap, v1.SearchCategory_PROCESS_INDICATORS: schema.ProcessIndicatorsSchema.OptionsMap, diff --git a/central/search/service/service_impl_test.go b/central/search/service/service_impl_test.go index 38a54393161eb..025e495188e3c 100644 --- a/central/search/service/service_impl_test.go +++ b/central/search/service/service_impl_test.go @@ -52,6 +52,15 @@ func TestSearchCategoryToOptionsMultiMap(t *testing.T) { } } +// TestPoliciesCategoryAutocompleteFieldPath verifies that the POLICIES multimap resolves "Category" +// to the PolicyCategoryName field path so autocomplete works via the policy_categories join. +func TestPoliciesCategoryAutocompleteFieldPath(t *testing.T) { + policiesMultimap := categoryToOptionsMultimap[v1.SearchCategory_POLICIES] + fields := policiesMultimap.GetAll(search.Category.String()) + assert.Len(t, fields, 1, `POLICIES multimap must have exactly one field for the "Category" key`) + assert.Equal(t, "policycategory.name", fields[0].GetFieldPath()) +} + func TestGetSearchFuncs_FlattenImageDataRoutesImageSearch(t *testing.T) { ctx := context.Background() q := &v1.Query{} diff --git a/pkg/search/policycategory/transform.go b/pkg/search/policycategory/transform.go index f06a8c00d38b7..cdfe4a418982b 100644 --- a/pkg/search/policycategory/transform.go +++ b/pkg/search/policycategory/transform.go @@ -2,9 +2,19 @@ package policycategory import ( v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/pkg/postgres/schema" "github.com/stackrox/rox/pkg/search" ) +// CategorySearchOptions maps the "Category" label to PolicyCategoryName for POLICIES autocomplete, +var CategorySearchOptions search.OptionsMap + +// init populates CategorySearchOptions from the PolicyCategoriesSchema at startup. +func init() { + CategorySearchOptions = search.NewOptionsMap(v1.SearchCategory_POLICIES). + Add(search.Category, schema.PolicyCategoriesSchema.OptionsMap.MustGet(search.PolicyCategoryName.String())) +} + // TransformCategoryNameFieldsQuery transforms category name fields for the new data layout for categories in postgres. func TransformCategoryNameFieldsQuery(q *v1.Query) *v1.Query { // Local copy to avoid changing input. diff --git a/qa-tests-backend/src/test/groovy/AutocompleteTest.groovy b/qa-tests-backend/src/test/groovy/AutocompleteTest.groovy index bee052cca32e2..707a47ada77c2 100644 --- a/qa-tests-backend/src/test/groovy/AutocompleteTest.groovy +++ b/qa-tests-backend/src/test/groovy/AutocompleteTest.groovy @@ -36,11 +36,25 @@ class AutocompleteTest extends BaseSpecification { "Data inputs are: " query | category | contains - "Subject:system:auth" | [] | "system:authenticated" + "Subject:system:auth" | [] | "system:authenticated" "Subject:system:auth" | [SearchCategory.SUBJECTS] | "system:authenticated" - "Subject Kind:GROUP" | [] | GROUP_AUTOCOMPLETE - "Subject Kind:group" | [] | GROUP_AUTOCOMPLETE - "Subject Kind:gr" | [] | GROUP_AUTOCOMPLETE + "Subject Kind:GROUP" | [] | GROUP_AUTOCOMPLETE + "Subject Kind:group" | [] | GROUP_AUTOCOMPLETE + "Subject Kind:gr" | [] | GROUP_AUTOCOMPLETE + } + + @Tag("BAT") + def "Verify Autocomplete: Category returns results for POLICIES"() { + when: + SearchServiceOuterClass.AutocompleteResponse resp = SearchService.autocomplete( + RawSearchRequest.newBuilder() + .addAllCategories([SearchCategory.POLICIES]) + .setQuery("Category:") + .build() + ) + + then: + !resp.valuesList.isEmpty() } @Unroll diff --git a/ui/apps/platform/cypress/integration/policies/policiesTable.test.js b/ui/apps/platform/cypress/integration/policies/policiesTable.test.js index 2f40501a7a041..11f86e0c3e53c 100644 --- a/ui/apps/platform/cypress/integration/policies/policiesTable.test.js +++ b/ui/apps/platform/cypress/integration/policies/policiesTable.test.js @@ -1,6 +1,10 @@ import * as api from '../../constants/apiEndpoints'; import withAuth from '../../helpers/basicAuth'; -import { addCheckboxSelectFilter } from '../../helpers/compoundFilters'; +import { + addCheckboxSelectFilter, + selectAttribute, + selectEntity, +} from '../../helpers/compoundFilters'; import { generateNameWithDate } from '../../helpers/formHelpers'; import { changePolicyStatusInTable, @@ -172,6 +176,22 @@ describe('Policies table', () => { cy.get(`${selectors.table.severityCell}:contains("Critical")`).should('not.exist'); }); + it('should show autocomplete options when filtering by category', () => { + visitPolicies(); + + selectEntity('Policy'); + selectAttribute('Category'); + + // open the autocomplete dropdown + cy.get('button[aria-label="Menu toggle"]').click(); + + // the dropdown must not be empty — this was fixed by ROX-36393 + cy.get('ul[aria-label="Filter results select menu"] li').should( + 'have.length.greaterThan', + 0 + ); + }); + it('should have expected lifecycle values', () => { visitPolicies(); From 3a130e0aaec3fac9ef9c8812ea140040ea17d75a Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:29:55 +0000 Subject: [PATCH 030/638] Refresh RPM lockfiles (#22341) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 96 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 6ff8a1d04a017..f055fdb10cc19 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.5-1.el9_8 sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.39.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2866853 - checksum: sha256:fb3b89778b21461f4f4610fbb3068e7a62d0b250cc7f5c243acfe50015aff179 + size: 2872021 + checksum: sha256:badc73a635a0552aa4d86eeaba804ccbbe811065e4fbbf3531fe1f9cf646fcd9 name: kernel-headers - evr: 5.14.0-687.39.1.el9_8 - sourcerpm: kernel-5.14.0-687.39.1.el9_8.src.rpm + evr: 5.14.0-687.41.1.el9_8 + sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -1279,12 +1279,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.39.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152374943 - checksum: sha256:7002d750cf1966a61a9131f2b5b24887816d989509efd27e5a4ed0dc8243e002 + size: 152395202 + checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec name: kernel - evr: 5.14.0-687.39.1.el9_8 + evr: 5.14.0-687.41.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1622,10 +1622,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/f44ff824c6f9e9f05e33561ff199a3df2d92abc697f59cd95be5fb8bfbdcbdf5-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/d82babc1f602404451bbd77e9b374581493872d17676995a9c26bfbb619f67e2-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 63516 - checksum: sha256:f44ff824c6f9e9f05e33561ff199a3df2d92abc697f59cd95be5fb8bfbdcbdf5 + size: 64613 + checksum: sha256:d82babc1f602404451bbd77e9b374581493872d17676995a9c26bfbb619f67e2 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1698,13 +1698,13 @@ arches: name: golang-src evr: 1.26.5-1.el9_8 sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.39.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2890621 - checksum: sha256:0214763708cdc25b5a92387531ba51bca96f3094e30dc919d40813ba2c70f653 + size: 2895853 + checksum: sha256:77cd0483c762c111190ae1b006ec02cb176aba064c1846f733c7edf859eed410 name: kernel-headers - evr: 5.14.0-687.39.1.el9_8 - sourcerpm: kernel-5.14.0-687.39.1.el9_8.src.rpm + evr: 5.14.0-687.41.1.el9_8 + sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -2910,12 +2910,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.39.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152374943 - checksum: sha256:7002d750cf1966a61a9131f2b5b24887816d989509efd27e5a4ed0dc8243e002 + size: 152395202 + checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec name: kernel - evr: 5.14.0-687.39.1.el9_8 + evr: 5.14.0-687.41.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3259,10 +3259,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/f1b49c35f6842532482b562da4d87cf2f1f45b976bf446e411adeaa17a8d695d-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/f8f33029fdd64d396f30ae5d7b714e83dd8326e22b357e8a2818d78dbadbb8b6-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 63801 - checksum: sha256:f1b49c35f6842532482b562da4d87cf2f1f45b976bf446e411adeaa17a8d695d + size: 64720 + checksum: sha256:f8f33029fdd64d396f30ae5d7b714e83dd8326e22b357e8a2818d78dbadbb8b6 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3335,13 +3335,13 @@ arches: name: golang-src evr: 1.26.5-1.el9_8 sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.39.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2896829 - checksum: sha256:d709861af2dce7bface9aa0106f30694a80600ef047e627b4b4bcf0e79aea3c5 + size: 2902017 + checksum: sha256:5078b1c5b552c0f2f738e8aae65d87bac7d3060f4eaa028d3d6c31b69603b292 name: kernel-headers - evr: 5.14.0-687.39.1.el9_8 - sourcerpm: kernel-5.14.0-687.39.1.el9_8.src.rpm + evr: 5.14.0-687.41.1.el9_8 + sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -4534,12 +4534,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.39.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152374943 - checksum: sha256:7002d750cf1966a61a9131f2b5b24887816d989509efd27e5a4ed0dc8243e002 + size: 152395202 + checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec name: kernel - evr: 5.14.0-687.39.1.el9_8 + evr: 5.14.0-687.41.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4877,10 +4877,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/ab6506c601ab067e63da15a86b61a3819cc64cba9795e4f3cb0756fc878d535e-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/9191bef9cd1cd3847a15e36de138e212935ac4d2dcc070c1dc20cff4e1b2fc60-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 62126 - checksum: sha256:ab6506c601ab067e63da15a86b61a3819cc64cba9795e4f3cb0756fc878d535e + size: 62927 + checksum: sha256:9191bef9cd1cd3847a15e36de138e212935ac4d2dcc070c1dc20cff4e1b2fc60 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4960,13 +4960,13 @@ arches: name: golang-src evr: 1.26.5-1.el9_8 sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.39.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2906125 - checksum: sha256:978a5d84f2c2e74a7ec915f71eea4dd5fb3a72a15fe9aea192cc47be32e7f671 + size: 2911313 + checksum: sha256:7fab4963bb2f80e238051586a6df2b0ffea787daf7ef59659ba8d0fa3648f46e name: kernel-headers - evr: 5.14.0-687.39.1.el9_8 - sourcerpm: kernel-5.14.0-687.39.1.el9_8.src.rpm + evr: 5.14.0-687.41.1.el9_8 + sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -6144,12 +6144,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.39.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152374943 - checksum: sha256:7002d750cf1966a61a9131f2b5b24887816d989509efd27e5a4ed0dc8243e002 + size: 152395202 + checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec name: kernel - evr: 5.14.0-687.39.1.el9_8 + evr: 5.14.0-687.41.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6487,7 +6487,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/ce4d5043429e82a8a9959c9c95551a1c77a370bbef5959ea4dd5db83c2379b09-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/742742f4aeb2fd9c8093031004809b35f2dceb373b6b0d083f00845e374cb570-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 66091 - checksum: sha256:ce4d5043429e82a8a9959c9c95551a1c77a370bbef5959ea4dd5db83c2379b09 + size: 67289 + checksum: sha256:742742f4aeb2fd9c8093031004809b35f2dceb373b6b0d083f00845e374cb570 From 8c5f4269cf2aab73652f3ec5b4fd2b3ccdccb10a Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:33:02 +0000 Subject: [PATCH 031/638] Update Konflux references to v0.4.0 (#22393) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 2 +- .tekton/main-pipeline.yaml | 2 +- .tekton/operator-bundle-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 39feafa2c2f69..5baff41e3f74b 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -395,7 +395,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.3.2@sha256:f5b4415db9ac1fba3e11d993a617e0b275d1f0ed2fc669b12c400ed848c39174 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 68b264578f391..8dcfc450a1db2 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -417,7 +417,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.3.2@sha256:f5b4415db9ac1fba3e11d993a617e0b275d1f0ed2fc669b12c400ed848c39174 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index fd39c0237e30e..9797991fd5eb7 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -596,7 +596,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.3.2@sha256:f5b4415db9ac1fba3e11d993a617e0b275d1f0ed2fc669b12c400ed848c39174 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 2c05a1dcf70bf..8c8e1ea0be9c9 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -415,7 +415,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.3.2@sha256:f5b4415db9ac1fba3e11d993a617e0b275d1f0ed2fc669b12c400ed848c39174 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab - name: kind value: task resolver: bundles From e4f1210a93a79fc92a6eb297c5b2644f93499957 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:33:54 +0000 Subject: [PATCH 032/638] Update all dependencies (#22324) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 4 ++-- image/postgres/konflux.Dockerfile | 2 +- image/rhel/konflux.Dockerfile | 4 ++-- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/db/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 50b73602e26a5..ea988575b6639 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:692953368d8e630f40a3c0a6135163f8824fdafc26e0400b9a6c8d7fac850366 + image: registry.redhat.io/ubi9-minimal:latest@sha256:8eb2830d0936237fc13a1f2f7e45aecf90d69043380ad167fad0343632937f41 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index d10db40f447ac..92af5f053f26f 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:7c372902c8d211db2d25c8277ba534a73b92742a334874dced829a63b0f21221 + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:8eb2830d0936237fc13a1f2f7e45aecf90d69043380ad167fad0343632937f41 imagePullPolicy: IfNotPresent script: | #!/bin/bash @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1786522985@sha256:444e81b3e88d8a68b92a081a7b3abc7d3fed2450f8473ea6b3caebf3bb73a0b8 + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787080706@sha256:71e89a1a51ab32cc30634d89ee4dc8ea40ad9991057fa1eae3b1af32bc7db73f imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index fc1103136d0de..ac4ccc99f4142 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:7194048e972bd3da04a53e3f9537b123eb6b1b2cc3388268896b8923f86e04d4 AS final +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:034554e4e112a6754c1b9be3e0661d42231751da43fb8173a82e953de3d01057 AS final USER root diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 9fbb839df271f..46c6f6846c664 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -37,7 +37,7 @@ RUN mkdir -p image/rhel/docs/api/v1 && \ RUN make copy-go-binaries-to-image-dir -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:d1f88101a85776886e459995eacdd7ebaa97c6e0c80b35c5a07a8a7b938ea9a3 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:4c44b8f1cfbfc36b900216809eeb6ae49f4f7b90fe1d47d66ab6be1cb5ee58d4 as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -61,7 +61,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:bcbf1fe577df63bd975ea19ebae604b5f072e1833b2bde417a0ffd4afd938b96 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 06c467e9f080f..434c3e6e1e22b 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:bcbf1fe577df63bd975ea19ebae604b5f072e1833b2bde417a0ffd4afd938b96 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index c7a0ef7a1ac86..0cdd1097ca8be 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:bcbf1fe577df63bd975ea19ebae604b5f072e1833b2bde417a0ffd4afd938b96 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index 5b29c214e31c0..1abe997f15f1b 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:7194048e972bd3da04a53e3f9537b123eb6b1b2cc3388268896b8923f86e04d4 +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:034554e4e112a6754c1b9be3e0661d42231751da43fb8173a82e953de3d01057 ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index 67fa951d5725e..2807f8d5c84cb 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:bcbf1fe577df63bd975ea19ebae604b5f072e1833b2bde417a0ffd4afd938b96 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer COPY --from=ubi-micro-base / /out/ From c7c4d8c363ddc6360dd6efd4f47443927afbe6d4 Mon Sep 17 00:00:00 2001 From: Gustavo Alkmim Date: Thu, 20 Aug 2026 22:34:28 -0700 Subject: [PATCH 033/638] ROX-36137: use per-entry ZIP timestamps for last_vuln_update (#22352) Update updateBundle() in scanner/matcher/updater/vuln/updater.go to use zipF.Modified (the per-entry ZIP modification time) instead of the global zipTime (HTTP Last-Modified of the whole ZIP) when calling SetLastVulnerabilityUpdate(). --- scanner/matcher/updater/vuln/updater.go | 28 +- scanner/matcher/updater/vuln/updater_test.go | 297 ++++++++++++++++++- 2 files changed, 300 insertions(+), 25 deletions(-) diff --git a/scanner/matcher/updater/vuln/updater.go b/scanner/matcher/updater/vuln/updater.go index 04379f7abee5a..6356a2df3a498 100644 --- a/scanner/matcher/updater/vuln/updater.go +++ b/scanner/matcher/updater/vuln/updater.go @@ -453,7 +453,7 @@ func (u *Updater) runMultiBundleUpdate(ctx context.Context) (bool, error) { for _, bundleF := range bundles { bundleCtx := log.With(ctx, "bundle", bundleF.Name) slog.InfoContext(bundleCtx, "starting bundle update") - if err := u.updateBundle(bundleCtx, bundleF, zipTime, prevTime); err != nil { + if err := u.updateBundle(bundleCtx, bundleF, prevTime); err != nil { slog.ErrorContext(bundleCtx, "updating bundle failed", "reason", err) bundleErrs = append(bundleErrs, fmt.Errorf("bundle %s: %w", bundleF.Name, err)) continue @@ -470,13 +470,17 @@ func (u *Updater) runMultiBundleUpdate(ctx context.Context) (bool, error) { // Clean updaters that were deleted (not in the zip and older than this update). // Safe to be run concurrently. - names := make([]string, 0, len(bundles)) - for _, f := range bundles { - names = append(names, f.Name) - } - err = u.metadataStore.GCVulnerabilityUpdates(ctx, names, zipTime) - if err != nil { - return false, errors.Join(fmt.Errorf("cleaning vuln updates: %w", err), errors.Join(bundleErrs...)) + if len(bundles) > 0 { + names := make([]string, 0, len(bundles)) + for _, f := range bundles { + names = append(names, f.Name) + } + err = u.metadataStore.GCVulnerabilityUpdates(ctx, names, zipTime) + if err != nil { + return false, errors.Join(fmt.Errorf("cleaning vuln updates: %w", err), errors.Join(bundleErrs...)) + } + } else { + slog.WarnContext(ctx, "skipping vuln update cleanup: no bundle was processed") } err = u.distManager.update(ctx) @@ -489,7 +493,7 @@ func (u *Updater) runMultiBundleUpdate(ctx context.Context) (bool, error) { return true, errors.Join(bundleErrs...) } -func (u *Updater) updateBundle(ctx context.Context, zipF *zip.File, zipTime time.Time, prevTime time.Time) error { +func (u *Updater) updateBundle(ctx context.Context, zipF *zip.File, prevTime time.Time) error { // Use TryLock to prevent simultaneous updates for the same bundle. lCtx, lDone := u.locker.TryLock(ctx, zipF.Name) defer lDone() @@ -504,8 +508,8 @@ func (u *Updater) updateBundle(ctx context.Context, zipF *zip.File, zipTime time if err != nil { return fmt.Errorf("querying last update: %w", err) } - if !lastTime.Before(zipTime) { - slog.InfoContext(ctx, "skipping: last update time is greater or equal to the zip archive time", "last_update_time", lastTime, "update_time", zipTime) + if !lastTime.Before(zipF.Modified) { + slog.InfoContext(ctx, "skipping: last update time is greater or equal to the zip archive time", "last_update_time", lastTime, "update_time", zipF.Modified) return nil } @@ -527,7 +531,7 @@ func (u *Updater) updateBundle(ctx context.Context, zipF *zip.File, zipTime time return fmt.Errorf("importing vulnerabilities: %w", err) } - err = u.metadataStore.SetLastVulnerabilityUpdate(lCtx, zipF.Name, zipTime) + err = u.metadataStore.SetLastVulnerabilityUpdate(lCtx, zipF.Name, zipF.Modified) if err != nil { return fmt.Errorf("updating timestamp (import was successful): %w", err) } diff --git a/scanner/matcher/updater/vuln/updater_test.go b/scanner/matcher/updater/vuln/updater_test.go index d69527916bc17..2f1b8a4693deb 100644 --- a/scanner/matcher/updater/vuln/updater_test.go +++ b/scanner/matcher/updater/vuln/updater_test.go @@ -125,9 +125,6 @@ func TestMultiBundleUpdate(t *testing.T) { metadataStore.EXPECT(). GetLastVulnerabilityUpdate(gomock.Any()). Return(now.Add(-time.Minute), nil) - metadataStore.EXPECT(). - GCVulnerabilityUpdates(gomock.Any(), gomock.Any(), now). - Return(nil) metadataStore.EXPECT(). GetLastVulnerabilityUpdate(gomock.Any()). Return(now, nil) @@ -150,14 +147,87 @@ func TestMultiBundleUpdate(t *testing.T) { assert.Equal(t, dists, u.KnownDistributions()) } +func TestMultiBundleUpdate_SkipsCleanupWhenNoBundleProcessed(t *testing.T) { + tests := map[string]struct { + bundleNames []string + allowlist []string + }{ + "empty archive": { + bundleNames: nil, + }, + "every entry filtered out by allowlist": { + bundleNames: []string{"alpine.json.zst"}, + allowlist: []string{"not-in-the-archive"}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for _, n := range tc.bundleNames { + _, err := zw.Create(n) + require.NoError(t, err) + } + require.NoError(t, zw.Close()) + return bytes.NewReader(buf.Bytes()) + }) + + ctrl := gomock.NewController(t) + store := mocks.NewMockMatcherStore(ctrl) + metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) + + u := &Updater{ + locker: &testLocker{locker: updates.NewLocalLockSource()}, + store: store, + metadataStore: metadataStore, + client: srv.Client(), + urls: []string{srv.URL}, + root: t.TempDir(), + skipGC: true, + importFunc: func(_ context.Context, _ io.Reader) error { return nil }, + retryDelay: 1 * time.Second, + retryMax: 1, + distManager: newDistManager(store), + vulnBundleAllowlist: set.NewFrozenSet(tc.allowlist...), + } + + prevTime := now.Add(-time.Hour) + + // No GetOrSetLastVulnerabilityUpdate (pre-registration/processing), and + // critically no GCVulnerabilityUpdates call: with zero allowed bundles, + // nothing should be pre-registered, processed, or cleaned up. + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(prevTime, nil) + store.EXPECT(). + Distributions(gomock.Any()). + Return(nil, nil) + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(now, nil) + + err := u.Update(test.Logging(t)) + assert.NoError(t, err) + }) + } +} + func TestMultiBundleUpdate_PreRegistration(t *testing.T) { bundleNames := []string{"alpine.json.zst", "nvd.json.zst", "rhel-vex.json.zst"} + entryModified, err := http.ParseTime(time.Now().Add(-2 * time.Hour).UTC().Format(http.TimeFormat)) + require.NoError(t, err) srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { var buf bytes.Buffer zw := zip.NewWriter(&buf) for _, name := range bundleNames { - _, err := zw.Create(name) + _, err := zw.CreateHeader(&zip.FileHeader{ + Name: name, + Method: zip.Deflate, + Modified: entryModified, + }) require.NoError(t, err) } require.NoError(t, zw.Close()) @@ -168,7 +238,7 @@ func TestMultiBundleUpdate_PreRegistration(t *testing.T) { store := mocks.NewMockMatcherStore(ctrl) metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) - prevTime := now.Add(-time.Minute) + prevTime := entryModified.Add(-time.Minute) u := &Updater{ locker: &testLocker{locker: updates.NewLocalLockSource()}, @@ -199,12 +269,12 @@ func TestMultiBundleUpdate_PreRegistration(t *testing.T) { } // Processing phase: each updateBundle calls GetOrSetLastVulnerabilityUpdate. - // Return zipTime (now) so updateBundle skips actual import (no zst decoding needed). + // Return entryModified so updateBundle skips actual import (no zst decoding needed). // Constrained to happen AFTER all pre-registration calls. for _, name := range bundleNames { call := metadataStore.EXPECT(). GetOrSetLastVulnerabilityUpdate(gomock.Any(), name, prevTime). - Return(now, nil) + Return(entryModified, nil) for _, pre := range preReg { call.After(pre) } @@ -221,10 +291,200 @@ func TestMultiBundleUpdate_PreRegistration(t *testing.T) { GetLastVulnerabilityUpdate(gomock.Any()). Return(now, nil) - err := u.Update(test.Logging(t)) + err = u.Update(test.Logging(t)) assert.NoError(t, err) } +func TestUpdateBundle_UsesPerEntryZipModifiedTimestamp(t *testing.T) { + const bundleName = "alpine.json.zst" + + // Older than the archive zipTime + entryModified, err := http.ParseTime(time.Now().Add(-2 * time.Hour).UTC().Format(http.TimeFormat)) + require.NoError(t, err) + + srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + w, err := zw.CreateHeader(&zip.FileHeader{ + Name: bundleName, + Method: zip.Deflate, + Modified: entryModified, + }) + require.NoError(t, err) + _, err = w.Write([]byte("fake-compressed-content")) + require.NoError(t, err) + require.NoError(t, zw.Close()) + return bytes.NewReader(buf.Bytes()) + }) + require.True(t, entryModified.Before(now), "test setup: entry timestamp must be older than the archive zipTime") + + ctrl := gomock.NewController(t) + store := mocks.NewMockMatcherStore(ctrl) + metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) + + prevTime := entryModified.Add(-time.Hour) + + u := &Updater{ + locker: &testLocker{locker: updates.NewLocalLockSource()}, + store: store, + metadataStore: metadataStore, + client: srv.Client(), + urls: []string{srv.URL}, + root: t.TempDir(), + skipGC: true, + importFunc: func(_ context.Context, _ io.Reader) error { return nil }, + retryDelay: 1 * time.Second, + retryMax: 1, + distManager: newDistManager(store), + } + + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(prevTime, nil) + + // Pre-registration phase. + preReg := metadataStore.EXPECT(). + GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleName, prevTime). + Return(prevTime, nil) + + // Processing phase: lastTime (prevTime) is before the entry's Modified time, so it runs. + metadataStore.EXPECT(). + GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleName, prevTime). + Return(prevTime, nil). + After(preReg) + + // Saved timestamp is the entry's Modified time, not zipTime. + metadataStore.EXPECT(). + SetLastVulnerabilityUpdate(gomock.Any(), bundleName, gomock.Cond(func(got time.Time) bool { + return got.Equal(entryModified) + })). + Return(nil) + + // GC and Initialized at end of runMultiBundleUpdate. + metadataStore.EXPECT(). + GCVulnerabilityUpdates(gomock.Any(), gomock.Any(), now). + Return(nil) + store.EXPECT(). + Distributions(gomock.Any()). + Return(nil, nil) + metadataStore.EXPECT(). + GetLastVulnerabilityUpdate(gomock.Any()). + Return(now, nil) + + err = u.Update(test.Logging(t)) + assert.NoError(t, err) +} + +func TestMultiBundleUpdate_UnchangedBundleNotReimported(t *testing.T) { + const bundleA = "alpine.json.zst" + const bundleB = "nvd.json.zst" + + buildZip := func(t *testing.T, modA, modB time.Time) []byte { + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + for _, e := range []struct { + name string + mod time.Time + }{{bundleA, modA}, {bundleB, modB}} { + w, err := zw.CreateHeader(&zip.FileHeader{ + Name: e.name, + Method: zip.Deflate, + Modified: e.mod, + }) + require.NoError(t, err) + _, err = w.Write([]byte("fake-compressed-content")) + require.NoError(t, err) + } + require.NoError(t, zw.Close()) + return buf.Bytes() + } + + var requestCount int + var zip1, zip2 []byte + srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { + requestCount++ + if requestCount == 1 { + return bytes.NewReader(zip1) + } + return bytes.NewReader(zip2) + }) + + modA, err := http.ParseTime(now.Add(-3 * time.Hour).Format(http.TimeFormat)) + require.NoError(t, err) + modB1, err := http.ParseTime(now.Add(-3 * time.Hour).Format(http.TimeFormat)) + require.NoError(t, err) + modB2, err := http.ParseTime(now.Add(-1 * time.Hour).Format(http.TimeFormat)) + require.NoError(t, err) + + // Cycle 1: both bundles are new. Cycle 2: bundleA's entry is unchanged + // (still modA), bundleB's entry changed (modB2 > modB1). + zip1 = buildZip(t, modA, modB1) + zip2 = buildZip(t, modA, modB2) + + ctrl := gomock.NewController(t) + store := mocks.NewMockMatcherStore(ctrl) + metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) + + var importCount int + u := &Updater{ + locker: &testLocker{locker: updates.NewLocalLockSource()}, + store: store, + metadataStore: metadataStore, + client: srv.Client(), + urls: []string{srv.URL}, + root: t.TempDir(), + skipGC: true, + importFunc: func(_ context.Context, _ io.Reader) error { + importCount++ + return nil + }, + retryDelay: 1 * time.Second, + retryMax: 1, + distManager: newDistManager(store), + } + + prevTime1 := now.Add(-4 * time.Hour) + prevTime2 := modA // aggregate MIN after cycle 1, since both bundles landed on modA==modB1. + + gomock.InOrder( + // Cycle 1: both bundles are new, both import. + metadataStore.EXPECT().GetLastVulnerabilityUpdate(gomock.Any()).Return(prevTime1, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleA, prevTime1).Return(prevTime1, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleB, prevTime1).Return(prevTime1, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleA, prevTime1).Return(prevTime1, nil), + metadataStore.EXPECT().SetLastVulnerabilityUpdate(gomock.Any(), bundleA, gomock.Cond(func(got time.Time) bool { + return got.Equal(modA) + })).Return(nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleB, prevTime1).Return(prevTime1, nil), + metadataStore.EXPECT().SetLastVulnerabilityUpdate(gomock.Any(), bundleB, gomock.Cond(func(got time.Time) bool { + return got.Equal(modB1) + })).Return(nil), + metadataStore.EXPECT().GCVulnerabilityUpdates(gomock.Any(), gomock.Any(), now).Return(nil), + store.EXPECT().Distributions(gomock.Any()).Return(nil, nil), + metadataStore.EXPECT().GetLastVulnerabilityUpdate(gomock.Any()).Return(modA, nil), // Initialized() caches true from here on. + + // Cycle 2: bundleA unchanged (must NOT reimport), bundleB changed (must reimport). + metadataStore.EXPECT().GetLastVulnerabilityUpdate(gomock.Any()).Return(prevTime2, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleA, prevTime2).Return(modA, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleB, prevTime2).Return(modB1, nil), + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleA, prevTime2).Return(modA, nil), + // No SetLastVulnerabilityUpdate for bundleA: it must be skipped, not reimported. + metadataStore.EXPECT().GetOrSetLastVulnerabilityUpdate(gomock.Any(), bundleB, prevTime2).Return(modB1, nil), + metadataStore.EXPECT().SetLastVulnerabilityUpdate(gomock.Any(), bundleB, gomock.Cond(func(got time.Time) bool { + return got.Equal(modB2) + })).Return(nil), + metadataStore.EXPECT().GCVulnerabilityUpdates(gomock.Any(), gomock.Any(), now).Return(nil), + store.EXPECT().Distributions(gomock.Any()).Return(nil, nil), + // Initialized() short-circuits on the cached value: no further GetLastVulnerabilityUpdate call. + ) + + require.NoError(t, u.Update(test.Logging(t))) + assert.Equal(t, 2, importCount, "cycle 1 should import both bundles") + + require.NoError(t, u.Update(test.Logging(t))) + assert.Equal(t, 3, importCount, "cycle 2 should only reimport the changed bundle (bundleB), not the unchanged bundleA") +} + func TestFetch(t *testing.T) { srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { return strings.NewReader("test") @@ -439,11 +699,18 @@ func TestRunMultiBundleUpdate_PartialFailure(t *testing.T) { return buf.Bytes() } + entryModified, err := http.ParseTime(time.Now().Add(-2 * time.Hour).UTC().Format(http.TimeFormat)) + require.NoError(t, err) + srv, now := testHTTPServer(t, func(_ *http.Request) io.ReadSeeker { var buf bytes.Buffer zw := zip.NewWriter(&buf) for _, name := range bundleNames { - f, err := zw.Create(name) + f, err := zw.CreateHeader(&zip.FileHeader{ + Name: name, + Method: zip.Deflate, + Modified: entryModified, + }) require.NoError(t, err) _, err = f.Write(makeZstd()) require.NoError(t, err) @@ -456,7 +723,7 @@ func TestRunMultiBundleUpdate_PartialFailure(t *testing.T) { store := mocks.NewMockMatcherStore(ctrl) metadataStore := mocks.NewMockMatcherMetadataStore(ctrl) - prevTime := now.Add(-time.Minute) + prevTime := entryModified.Add(-time.Minute) // importFunc fails for the second call (nvd), succeeds for the others. importCallCount := 0 @@ -505,10 +772,14 @@ func TestRunMultiBundleUpdate_PartialFailure(t *testing.T) { // SetLastVulnerabilityUpdate is called only for the two bundles that succeed. metadataStore.EXPECT(). - SetLastVulnerabilityUpdate(gomock.Any(), "alpine.json.zst", now). + SetLastVulnerabilityUpdate(gomock.Any(), "alpine.json.zst", gomock.Cond(func(got time.Time) bool { + return got.Equal(entryModified) + })). Return(nil) metadataStore.EXPECT(). - SetLastVulnerabilityUpdate(gomock.Any(), "rhel-vex.json.zst", now). + SetLastVulnerabilityUpdate(gomock.Any(), "rhel-vex.json.zst", gomock.Cond(func(got time.Time) bool { + return got.Equal(entryModified) + })). Return(nil) // GC and distribution update run because at least one bundle succeeded. @@ -524,7 +795,7 @@ func TestRunMultiBundleUpdate_PartialFailure(t *testing.T) { GetLastVulnerabilityUpdate(gomock.Any()). Return(now, nil) - err := u.Update(test.Logging(t)) + err = u.Update(test.Logging(t)) assert.ErrorContains(t, err, "nvd.json.zst") assert.Equal(t, 3, importCallCount, "all three bundles must be attempted") } From 54a2d2eb50ac2ff1ce658d31c7159db70c31fe56 Mon Sep 17 00:00:00 2001 From: Vlad Bologa Date: Fri, 21 Aug 2026 10:18:40 +0200 Subject: [PATCH 034/638] fix(ci): drop obsolete OpenShift Sensor CPU patch (#22384) --- tests/e2e/lib.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 0f5e0467fe36b..966289392a7d5 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -703,14 +703,6 @@ deploy_sensor() { ROX_CA_CERT_FILE="" # force sensor.sh to fetch the actual cert. CENTRAL_NAMESPACE="${central_namespace}" SENSOR_NAMESPACE="${sensor_namespace}" "${ROOT}/${DEPLOY_DIR}/sensor.sh" fi - - if [[ "${ORCHESTRATOR_FLAVOR}" == "openshift" ]]; then - # Sensor is CPU starved under OpenShift causing all manner of test failures: - # https://stack-rox.atlassian.net/browse/ROX-5334 - # https://stack-rox.atlassian.net/browse/ROX-6891 - # et al. - retrying_kubectl Date: Fri, 21 Aug 2026 12:24:31 +0200 Subject: [PATCH 035/638] ROX-33973: Filter for ACM UserPermissions response data (#22239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann Co-authored-by: Claude Opus 4.6 (1M context) --- go.mod | 1 + go.sum | 2 + pkg/sac/externalrolebroker/filter.go | 82 +++ pkg/sac/externalrolebroker/filter_test.go | 520 ++++++++++++++++++ .../externalrolebroker/resource_mapping.go | 62 +++ .../resource_mapping_test.go | 23 + 6 files changed, 690 insertions(+) create mode 100644 pkg/sac/externalrolebroker/filter.go create mode 100644 pkg/sac/externalrolebroker/filter_test.go create mode 100644 pkg/sac/externalrolebroker/resource_mapping.go create mode 100644 pkg/sac/externalrolebroker/resource_mapping_test.go diff --git a/go.mod b/go.mod index 95ce589f17e94..7b613408a5bdb 100644 --- a/go.mod +++ b/go.mod @@ -132,6 +132,7 @@ require ( github.com/stackrox/k8s-overlay-patch v0.0.0-20250224110925-13b5b47fd812 github.com/stackrox/pkcs7 v0.0.0-20240314170115-841ca6b5f88d github.com/stackrox/scanner v0.0.0-20240830165150-d133ba942d59 + github.com/stolostron/cluster-lifecycle-api v0.0.0-20260330032750-43755d6ceb09 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.19.0 github.com/tkuchiki/go-timezone v0.2.3 diff --git a/go.sum b/go.sum index fd34e62bd5cbd..bd0c746136a7e 100644 --- a/go.sum +++ b/go.sum @@ -1326,6 +1326,8 @@ github.com/stackrox/yaml/v3 v3.0.0/go.mod h1:360StwOazy3cplMWHzBAA9y6InmKu/v7WB0 github.com/stackrox/zap v1.18.2-0.20240314134248-5f932edd0404 h1:j2qhsZjUBpN4yaqEGkNrATdw3fE3vgMrVOhd44cUJDY= github.com/stackrox/zap v1.18.2-0.20240314134248-5f932edd0404/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stolostron/cluster-lifecycle-api v0.0.0-20260330032750-43755d6ceb09 h1:vM6k7Atzuhygf/rTytmRdTEs3HdnIuhkpdKyVrgHX7Q= +github.com/stolostron/cluster-lifecycle-api v0.0.0-20260330032750-43755d6ceb09/go.mod h1:6V51MeHkiYrmQVSFAhc4rfgZk2/AxtZMNSgNN8DjvRQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/pkg/sac/externalrolebroker/filter.go b/pkg/sac/externalrolebroker/filter.go new file mode 100644 index 0000000000000..cb6885bf8bacd --- /dev/null +++ b/pkg/sac/externalrolebroker/filter.go @@ -0,0 +1,82 @@ +package externalrolebroker + +import ( + "slices" + "strings" + + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + rbacv1 "k8s.io/api/rbac/v1" +) + +const rbacWildcard = "*" + +// FilterUserPermissionsForSupportedK8sResources filters a list of UserPermission objects +// to return only those that reference resources supported by ACS +// (some core Kubernetes resources, some Kubernetes RBAC resources and Stackrox API resources). +// +// A UserPermission is included if its ClusterRoleDefinition contains at least one PolicyRule +// that references one or more of the supported resources with matching API groups. +func FilterUserPermissionsForSupportedK8sResources( + permissions []clusterviewv1alpha1.UserPermission, +) []clusterviewv1alpha1.UserPermission { + filtered := make([]clusterviewv1alpha1.UserPermission, 0, len(permissions)) + + for _, permission := range permissions { + if slices.ContainsFunc( + permission.Status.ClusterRoleDefinition.Rules, + ruleHasSupportedK8sResource, + ) { + filtered = append(filtered, permission) + } + } + + return filtered +} + +// ruleHasSupportedK8sResource checks if a PolicyRule includes any of +// the supported Kubernetes resources (a resource here is considered +// with the API group it belongs to). +// +// This function supports wildcards ("*") in the policy rule. +func ruleHasSupportedK8sResource(rule rbacv1.PolicyRule) bool { + // Handle empty APIGroups - no match + if len(rule.APIGroups) == 0 { + return false + } + if len(rule.ResourceNames) > 0 { + return false + } + for _, resource := range rule.Resources { + baseResource := getBaseResourceFromResource(resource) + for _, apiGroup := range rule.APIGroups { + if isSupportedResourceInAPIGroup(baseResource, apiGroup) { + return true + } + } + } + return false +} + +func isSupportedResourceInAPIGroup(resource string, apiGroup string) bool { + if resource == rbacWildcard && apiGroup == rbacWildcard { + return true + } + if apiGroup == rbacWildcard { + return supportedK8sRawResources.Contains(resource) + } + if resource == rbacWildcard { + return supportedK8sAPIGroups.Contains(apiGroup) + } + resourceWithApiGroup := strings.Join([]string{resource, apiGroup}, ".") + if _, found := resourceMapping[resourceWithApiGroup]; found { + return true + } + return false +} + +func getBaseResourceFromResource(resource string) string { + if prefix, _, found := strings.Cut(resource, "/"); found { + return prefix + } + return resource +} diff --git a/pkg/sac/externalrolebroker/filter_test.go b/pkg/sac/externalrolebroker/filter_test.go new file mode 100644 index 0000000000000..8b3a1eb57dd1f --- /dev/null +++ b/pkg/sac/externalrolebroker/filter_test.go @@ -0,0 +1,520 @@ +package externalrolebroker + +import ( + "testing" + + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + "github.com/stretchr/testify/assert" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFilterUserPermissionsForSupportedK8sResources(t *testing.T) { + tests := map[string]struct { + permissions []clusterviewv1alpha1.UserPermission + expected int + }{ + "empty list": { + permissions: []clusterviewv1alpha1.UserPermission{}, + expected: 0, + }, + "permission with namespace access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-1", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 1, + }, + "permission with secret access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-2", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list", "create"}, + }, + }), + }, + expected: 1, + }, + "permission with role and rolebinding access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-3", []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"get", "list", "create", "update", "delete"}, + }, + }), + }, + expected: 1, + }, + "permission with clusterrole and clusterrolebinding access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-3b", []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterrolebindings"}, + Verbs: []string{"get", "list", "create", "update", "delete"}, + }, + }), + }, + expected: 1, + }, + "permission with serviceaccount access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-4", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"serviceaccounts"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 1, + }, + "permission with wildcard resources in core group": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-5", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"*"}, + Verbs: []string{"*"}, + }, + }), + }, + expected: 1, + }, + "permission with wildcard apiGroups": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-6", []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"secrets"}, + Verbs: []string{"get"}, + }, + }), + }, + expected: 1, + }, + "permission with non-base resources only": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-7", []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments", "statefulsets"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 0, + }, + "permission with pods only (not a base resource)": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("test-8", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 0, + }, + "mixed permissions - some match, some don't": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("match-1", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get"}, + }, + }), + makeUserPermission("no-match-1", []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments"}, + Verbs: []string{"get"}, + }, + }), + makeUserPermission("match-2", []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + Verbs: []string{"create"}, + }, + }), + makeUserPermission("no-match-2", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + Verbs: []string{"get"}, + }, + }), + }, + expected: 2, + }, + "permission with multiple rules - one matches": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("multi-rule", []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments"}, + Verbs: []string{"get"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, // This one matches + Verbs: []string{"get"}, + }, + }), + }, + expected: 1, + }, + "permission with subresources": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("subresource", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets/status"}, // Should still match "secrets" + Verbs: []string{"get"}, + }, + }), + }, + expected: 1, + }, + "permission with empty APIGroups": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("empty-apigroups", []rbacv1.PolicyRule{ + { + APIGroups: []string{}, + Resources: []string{"secrets"}, + Verbs: []string{"get"}, + }, + }), + }, + expected: 0, + }, + "permission with all base resources": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("all-base", []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces", "secrets", "serviceaccounts"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 1, + }, + "permission with StackRox API alert access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("stackrox-alert", []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"alerts"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 1, + }, + "permission with StackRox API deployment access": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("stackrox-deployment", []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"deployments"}, + Verbs: []string{"get", "list", "create"}, + }, + }), + }, + expected: 1, + }, + "permission with multiple StackRox API resources": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("stackrox-multi", []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"compliances", "detections", "networkgraphs"}, + Verbs: []string{"get", "list"}, + }, + }), + }, + expected: 1, + }, + "permission with vulnerability management resources": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("stackrox-vuln", []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"vulnerabilitymanagementrequests", "vulnerabilitymanagementapprovals"}, + Verbs: []string{"get", "list", "create"}, + }, + }), + }, + expected: 1, + }, + "permission with StackRox API wrong API group": { + permissions: []clusterviewv1alpha1.UserPermission{ + makeUserPermission("wrong-apigroup", []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"alerts"}, + Verbs: []string{"get"}, + }, + }), + }, + expected: 0, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := FilterUserPermissionsForSupportedK8sResources(tc.permissions) + assert.Len(t, result, tc.expected, "Expected %d permissions to be filtered, got %d", tc.expected, len(result)) + }) + } +} + +func TestRuleHasSupportedK8sResource(t *testing.T) { + tests := map[string]struct { + rule rbacv1.PolicyRule + expected bool + }{ + "core API group with namespace": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + expected: true, + }, + "core API group with secret": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"secrets"}, + }, + expected: true, + }, + "core API group with serviceaccount": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"serviceaccounts"}, + }, + expected: true, + }, + "rbac API group with role": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + }, + expected: true, + }, + "rbac API group with rolebinding": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + }, + expected: true, + }, + "rbac API group with clusterrole": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, + }, + expected: true, + }, + "rbac API group with clusterrolebinding": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings"}, + }, + expected: true, + }, + "wildcard API group with configured resource": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"*"}, + Resources: []string{"secrets"}, + }, + expected: true, + }, + "wildcard resource with core API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"*"}, + }, + expected: true, + }, + "wildcard resource with rbac API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"*"}, + }, + expected: true, + }, + "wildcard resource and API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }, + expected: true, + }, + "non-configured resource": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"pods"}, + }, + expected: false, + }, + "wrong API group for resource": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"apps"}, + Resources: []string{"secrets"}, + }, + expected: false, + }, + "rbac resource with wrong API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"roles"}, + }, + expected: false, + }, + "core resource with wrong API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"secrets"}, + }, + expected: false, + }, + "empty API groups": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{}, + Resources: []string{"secrets"}, + }, + expected: false, + }, + "subresource for secrets with correct API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"secrets/status"}, + }, + expected: true, + }, + "multiple API groups with one matching": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"apps", ""}, + Resources: []string{"secrets"}, + }, + expected: true, + }, + "multiple resources with one configured": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{"pods", "secrets", "configmaps"}, + }, + expected: true, + }, + "empty resources": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{""}, + Resources: []string{}, + }, + expected: false, + }, + "StackRox API group with alert": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"alerts"}, + }, + expected: true, + }, + "StackRox API group with deployment": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"deployments"}, + }, + expected: true, + }, + "StackRox API group with compliance": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"compliance"}, + }, + expected: true, + }, + "StackRox API group with detection": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"detection"}, + }, + expected: true, + }, + "StackRox API group with networkgraph": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"networkgraphs"}, + }, + expected: true, + }, + "StackRox API group with vulnerabilitymanagementrequest": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"vulnerabilitymanagementrequests"}, + }, + expected: true, + }, + "StackRox API group with vulnerabilitymanagementapproval": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"vulnerabilitymanagementapprovals"}, + }, + expected: true, + }, + "StackRox API resources with wrong API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"apps"}, + Resources: []string{"alerts"}, + }, + expected: false, + }, + "wildcard resource with StackRox API group": { + rule: rbacv1.PolicyRule{ + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"*"}, + }, + expected: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := ruleHasSupportedK8sResource(tc.rule) + assert.Equal(t, tc.expected, result) + }) + } +} + +// Helper function to create a UserPermission with given rules +func makeUserPermission(name string, rules []rbacv1.PolicyRule) clusterviewv1alpha1.UserPermission { + return clusterviewv1alpha1.UserPermission{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: rules, + }, + }, + } +} diff --git a/pkg/sac/externalrolebroker/resource_mapping.go b/pkg/sac/externalrolebroker/resource_mapping.go new file mode 100644 index 0000000000000..3e716ca9860c2 --- /dev/null +++ b/pkg/sac/externalrolebroker/resource_mapping.go @@ -0,0 +1,62 @@ +package externalrolebroker + +import ( + "strings" + + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/sac/resources" + "github.com/stackrox/rox/pkg/set" +) + +var ( + resourceMapping = map[string]permissions.ResourceMetadata{ + // Core Kubernetes resources + "namespaces.": resources.Namespace, + "nodes.": resources.Node, + "secrets.": resources.Secret, + "serviceaccounts.": resources.ServiceAccount, + // Kubernetes RBAC resources + "clusterroles.rbac.authorization.k8s.io": resources.K8sRole, + "roles.rbac.authorization.k8s.io": resources.K8sRole, + "clusterrolebindings.rbac.authorization.k8s.io": resources.K8sRoleBinding, + "rolebindings.rbac.authorization.k8s.io": resources.K8sRoleBinding, + // Direct resource mapping + "accesses.api.stackrox.io": resources.Access, + "administration.api.stackrox.io": resources.Administration, + "alerts.api.stackrox.io": resources.Alert, + "cves.api.stackrox.io": resources.CVE, + "clusters.api.stackrox.io": resources.Cluster, + "compliance.api.stackrox.io": resources.Compliance, + "deployments.api.stackrox.io": resources.Deployment, + "deploymentextensions.api.stackrox.io": resources.DeploymentExtension, + "detection.api.stackrox.io": resources.Detection, + "images.api.stackrox.io": resources.Image, + "imageadministration.api.stackrox.io": resources.ImageAdministration, + "integrations.api.stackrox.io": resources.Integration, + "k8ssubjects.api.stackrox.io": resources.K8sSubject, + "networkgraphs.api.stackrox.io": resources.NetworkGraph, + "networkpolicies.api.stackrox.io": resources.NetworkPolicy, + "nodes.api.stackrox.io": resources.Node, + "virtualmachines.api.stackrox.io": resources.VirtualMachine, + "vulnerabilitymanagementapprovals.api.stackrox.io": resources.VulnerabilityManagementApprovals, + "vulnerabilitymanagementrequests.api.stackrox.io": resources.VulnerabilityManagementRequests, + "watchedimages.api.stackrox.io": resources.WatchedImage, + "workflowadministration.api.stackrox.io": resources.WorkflowAdministration, + } + + supportedK8sAPIGroups, supportedK8sRawResources = listSupportedK8sGroupsAndResources(resourceMapping) +) + +func listSupportedK8sGroupsAndResources(mapping map[string]permissions.ResourceMetadata) (set.StringSet, set.StringSet) { + groups := set.NewStringSet() + rawResources := set.NewStringSet() + for key := range mapping { + resource, apiGroup, found := strings.Cut(key, ".") + if !found { + continue + } + groups.Add(apiGroup) + rawResources.Add(resource) + } + return groups, rawResources +} diff --git a/pkg/sac/externalrolebroker/resource_mapping_test.go b/pkg/sac/externalrolebroker/resource_mapping_test.go new file mode 100644 index 0000000000000..bd8f88b67efcc --- /dev/null +++ b/pkg/sac/externalrolebroker/resource_mapping_test.go @@ -0,0 +1,23 @@ +package externalrolebroker + +import ( + "testing" + + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/sac/resources" + "github.com/stackrox/rox/pkg/set" + "github.com/stretchr/testify/assert" +) + +func TestAllResourcesAreMapped(t *testing.T) { + mappedResources := set.NewSet[permissions.Resource]() + for _, md := range resourceMapping { + mappedResources.Add(md.GetResource()) + } + + for _, md := range resources.ListAllMetadata() { + res := md.GetResource() + assert.True(t, mappedResources.Contains(res), + "Resource %q is defined in resources.ListAllMetadata() but has no entry in resourceMapping.", res) + } +} From c327498d48b386798ce9f24d9304821176a3f7d6 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Fri, 21 Aug 2026 12:27:03 +0200 Subject: [PATCH 036/638] ROX-35080: set default URL for Red Hat signing key bundle (#22391) Co-authored-by: Claude Opus 4.6 --- central/signatureintegration/datastore/singleton.go | 2 +- pkg/env/signature.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/central/signatureintegration/datastore/singleton.go b/central/signatureintegration/datastore/singleton.go index 6a4a5946478ff..cfe48dced7f69 100644 --- a/central/signatureintegration/datastore/singleton.go +++ b/central/signatureintegration/datastore/singleton.go @@ -71,7 +71,7 @@ func startKeyBundleUpdater() { rawURL := env.RedHatSigningKeyBundleURL.Setting() if rawURL == "" { - log.Info("ROX_REDHAT_SIGNING_KEY_BUNDLE_URL not set, key bundle updater will not start") + log.Info("ROX_REDHAT_SIGNING_KEY_BUNDLE_URL is empty, key bundle updater will not start") return } bundleURL := urlfmt.FormatURL(rawURL, urlfmt.HTTPS, urlfmt.HonorInputSlash) diff --git a/pkg/env/signature.go b/pkg/env/signature.go index dedf96c06bea9..cee8747a1db0c 100644 --- a/pkg/env/signature.go +++ b/pkg/env/signature.go @@ -8,7 +8,8 @@ var ( // RedHatSigningKeyBundleURL is the remote URL of the key bundle JSON. // If empty, the key bundle updater does not start. - RedHatSigningKeyBundleURL = RegisterSetting("ROX_REDHAT_SIGNING_KEY_BUNDLE_URL", AllowEmpty()) + RedHatSigningKeyBundleURL = RegisterSetting("ROX_REDHAT_SIGNING_KEY_BUNDLE_URL", + WithDefault("https://definitions.stackrox.io/signing-keys/bundle.json"), AllowEmpty()) // RedHatSigningKeyUpdateInterval controls how often the updater re-downloads the bundle. RedHatSigningKeyUpdateInterval = registerDurationSetting("ROX_REDHAT_SIGNING_KEY_UPDATE_INTERVAL", 4*time.Hour) From 13fcd5b5e419b4024d492bb06284fb2c79fa9cf5 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Fri, 21 Aug 2026 13:26:04 +0200 Subject: [PATCH 037/638] perf: add missing pullNumber field to Orion cluster-density config (#22386) --- tests/performance/scale/config/orion-acs-cluster-density-v2.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/performance/scale/config/orion-acs-cluster-density-v2.yml b/tests/performance/scale/config/orion-acs-cluster-density-v2.yml index 19c9ff985dca6..074142cabc914 100644 --- a/tests/performance/scale/config/orion-acs-cluster-density-v2.yml +++ b/tests/performance/scale/config/orion-acs-cluster-density-v2.yml @@ -13,6 +13,7 @@ tests : ocpVersion: {{ version }} networkType: OVNKubernetes jobType: {{ jobtype | default('periodic') }} + pullNumber: "{{ pull_number | default(0) }}" not: stream: okd From 9842a0244a7f89eebe802e66c242f8cc9ba5731e Mon Sep 17 00:00:00 2001 From: Jan Schnathmeier <102952141+jschnath@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:36:28 +0200 Subject: [PATCH 038/638] ROX-36469: Fix pubsub concurrent lane goroutine leak (#22383) --- pkg/testutils/goleak/assert_no_leak.go | 40 +++++++++--- sensor/common/pubsub/consumer/buffered.go | 4 ++ .../common/pubsub/consumer/buffered_test.go | 7 +++ sensor/common/pubsub/consumer/default.go | 9 ++- sensor/common/pubsub/consumer/default_test.go | 12 ++-- sensor/common/pubsub/interfaces.go | 7 +++ sensor/common/pubsub/lane/concurrent.go | 21 ++----- sensor/common/pubsub/lane/concurrent_test.go | 61 +++++++++++++++++++ 8 files changed, 132 insertions(+), 29 deletions(-) diff --git a/pkg/testutils/goleak/assert_no_leak.go b/pkg/testutils/goleak/assert_no_leak.go index 327b44a3d5d16..c958941cba39a 100644 --- a/pkg/testutils/goleak/assert_no_leak.go +++ b/pkg/testutils/goleak/assert_no_leak.go @@ -6,15 +6,39 @@ import ( "go.uber.org/goleak" ) +// Option re-exports goleak.Option so callers don't need to import +// go.uber.org/goleak directly (disallowed by roxvet, see validateimports). +type Option = goleak.Option + +// IgnoreCurrent re-exports goleak.IgnoreCurrent, for tests that need to check +// for leaks before test cleanup runs rather than via AssertNoGoroutineLeaks. +func IgnoreCurrent() Option { + return goleak.IgnoreCurrent() +} + +// Find re-exports goleak.Find, for tests that need to check for leaks before +// test cleanup runs rather than via AssertNoGoroutineLeaks. +func Find(options ...Option) error { + return goleak.Find(options...) +} + +// CommonIgnores returns the goleak options for known, unrelated background +// goroutines. Shared so that tests calling IgnoreCurrent/Find directly stay in +// sync with AssertNoGoroutineLeaks instead of maintaining their own copy of +// this list. +func CommonIgnores() []Option { + return []goleak.Option{ + // Ignore a known leak: https://github.com/DataDog/dd-trace-go/issues/1469 + goleak.IgnoreTopFunction("github.com/golang/glog.(*fileSink).flushDaemon"), + // Ignore a known leak caused by importing the GCP cscc SDK. + goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), + // Ignore a known leak from https://github.com/hashicorp/golang-lru/blob/v2.0.7/expirable/expirable_lru.go#L77-L80 + goleak.IgnoreTopFunction("github.com/hashicorp/golang-lru/v2/expirable.NewLRU[...].func1"), + } +} + func AssertNoGoroutineLeaks(t testing.TB) { t.Cleanup(func() { - goleak.VerifyNone(t, - // Ignore a known leak: https://github.com/DataDog/dd-trace-go/issues/1469 - goleak.IgnoreTopFunction("github.com/golang/glog.(*fileSink).flushDaemon"), - // Ignore a known leak caused by importing the GCP cscc SDK. - goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), - // Ignore a known leak from https://github.com/hashicorp/golang-lru/blob/v2.0.7/expirable/expirable_lru.go#L77-L80 - goleak.IgnoreTopFunction("github.com/hashicorp/golang-lru/v2/expirable.NewLRU[...].func1"), - ) + goleak.VerifyNone(t, CommonIgnores()...) }) } diff --git a/sensor/common/pubsub/consumer/buffered.go b/sensor/common/pubsub/consumer/buffered.go index 7b29e28564bb7..2b5edaac0d409 100644 --- a/sensor/common/pubsub/consumer/buffered.go +++ b/sensor/common/pubsub/consumer/buffered.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/safe" "github.com/stackrox/rox/sensor/common/pubsub" pubsubErrors "github.com/stackrox/rox/sensor/common/pubsub/errors" @@ -13,6 +14,8 @@ import ( const ( defaultBufferSize = 1000 + + bufferedConsumerLoggingRateLimiter = "pubsub-buffered-consumer" ) // bufferedEvent wraps an event with its error channel to pipe callback errors back to the caller @@ -155,6 +158,7 @@ func (c *BufferedConsumer) handleEvent(wrappedEv *bufferedEvent) { // Callback completed - send error if present, otherwise just close errC if err != nil { operation = metrics.ConsumerError + logging.GetRateLimitedLogger().ErrorL(bufferedConsumerLoggingRateLimiter, "unable to handle event on lane %s, topic %s: %v", c.laneID, c.topic, err) wrappedEv.errC <- err } // On success (err == nil), defer close handles it without sending diff --git a/sensor/common/pubsub/consumer/buffered_test.go b/sensor/common/pubsub/consumer/buffered_test.go index 3890a97634e0c..16c828d3325d3 100644 --- a/sensor/common/pubsub/consumer/buffered_test.go +++ b/sensor/common/pubsub/consumer/buffered_test.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/safe" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/pkg/testutils/goleak" @@ -58,6 +59,12 @@ func TestBufferedConsumer_ConsumeSuccess(t *testing.T) { func TestBufferedConsumer_CallbackError(t *testing.T) { defer goleak.AssertNoGoroutineLeaks(t) + // Initialize the rate-limited logger before entering the synctest bubble. + // It uses a hashicorp LRU that spawns a background timer goroutine; + // if created inside the bubble, synctest panics on exit because the + // goroutine remains blocked. + logging.GetRateLimitedLogger() + synctest.Test(t, func(t *testing.T) { expectedErr := errors.New("callback error") diff --git a/sensor/common/pubsub/consumer/default.go b/sensor/common/pubsub/consumer/default.go index fc0e99c0c41c3..bd8fdea3e19e7 100644 --- a/sensor/common/pubsub/consumer/default.go +++ b/sensor/common/pubsub/consumer/default.go @@ -5,11 +5,14 @@ import ( "github.com/pkg/errors" "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/sensor/common/pubsub" pubsubErrors "github.com/stackrox/rox/sensor/common/pubsub/errors" "github.com/stackrox/rox/sensor/common/pubsub/metrics" ) +const defaultConsumerLoggingRateLimiter = "pubsub-default-consumer" + func NewDefaultConsumer() pubsub.NewConsumer { return func(laneID pubsub.LaneID, topic pubsub.Topic, consumerID pubsub.ConsumerID, callback pubsub.EventCallback) (pubsub.Consumer, error) { if callback == nil { @@ -32,7 +35,10 @@ type DefaultConsumer struct { } func (c *DefaultConsumer) Consume(waitable concurrency.Waitable, event pubsub.Event) <-chan error { - errC := make(chan error) + // Buffered so the send below never blocks on a reader: callers (e.g. concurrentLane) + // are not required to drain this channel, and must not need to in order for this + // goroutine to exit. + errC := make(chan error, 1) go func() { defer close(errC) start := time.Now() @@ -43,6 +49,7 @@ func (c *DefaultConsumer) Consume(waitable concurrency.Waitable, event pubsub.Ev err := c.callback(event) if err != nil { operation = metrics.ConsumerError + logging.GetRateLimitedLogger().ErrorL(defaultConsumerLoggingRateLimiter, "unable to handle event on lane %s, topic %s: %v", c.laneID, c.topic, err) } return err }(): diff --git a/sensor/common/pubsub/consumer/default_test.go b/sensor/common/pubsub/consumer/default_test.go index a89d665a79367..346dee2f08b55 100644 --- a/sensor/common/pubsub/consumer/default_test.go +++ b/sensor/common/pubsub/consumer/default_test.go @@ -27,22 +27,26 @@ func (s *defaultConsumerSuite) TestConsume() { s.Assert().Error(err) s.Assert().Nil(c) }) - s.Run("should unblock if waitable is done", func() { + s.Run("should not leak even if nobody ever reads errC", func() { + // concurrentLane fires Consume() and discards the returned channel without + // reading it, relying on the consumer to report its own errors. The internal + // goroutine must still exit on its own (errC is buffered so the send never + // blocks on a reader) rather than depend on the waitable firing. callbackDone := concurrency.NewSignal() c, err := NewDefaultConsumer()(pubsub.DefaultLane, pubsub.DefaultTopic, pubsub.DefaultConsumer, func(_ pubsub.Event) error { defer callbackDone.Signal() return errors.New("some error") }) s.Assert().NoError(err) - ctx, cancel := context.WithCancel(context.Background()) + ctx := context.Background() _ = c.Consume(ctx, &testEvent{}) select { case <-callbackDone.Done(): case <-time.After(500 * time.Millisecond): s.FailNow("callback should be done") } - cancel() - // The test will fail if there are goroutine leaks + // errC is intentionally never read here. + // The test will fail if there are goroutine leaks. }) s.Run("consume event error", func() { data := "some data" diff --git a/sensor/common/pubsub/interfaces.go b/sensor/common/pubsub/interfaces.go index 512402577c7cb..e0d11e65795a3 100644 --- a/sensor/common/pubsub/interfaces.go +++ b/sensor/common/pubsub/interfaces.go @@ -28,6 +28,13 @@ type Lane interface { type NewConsumer func(laneID LaneID, topic Topic, consumerID ConsumerID, callback EventCallback) (Consumer, error) type Consumer interface { + // Consume processes event and reports the outcome on the returned channel. + // Implementations must not rely on that channel being read: concurrentLane + // dispatches Consume() without waiting on or draining the result, so a + // consumer that only reports errors through this channel will have them + // silently dropped there. Implementations should report their own errors + // (e.g. logging/metrics) rather than depending solely on the caller reading + // this channel. Consume(concurrency.Waitable, Event) <-chan error Stop() } diff --git a/sensor/common/pubsub/lane/concurrent.go b/sensor/common/pubsub/lane/concurrent.go index 35a63a0ba77ca..08a40692bf883 100644 --- a/sensor/common/pubsub/lane/concurrent.go +++ b/sensor/common/pubsub/lane/concurrent.go @@ -120,22 +120,11 @@ func (l *concurrentLane) handleEvent(event pubsub.Event) { return } for _, c := range consumers { - errC := c.Consume(l.stopper.Client().Stopped(), event) - // Spawning go routine here to not block other consumers - go l.handleConsumerError(errC) - } -} - -func (l *concurrentLane) handleConsumerError(errC <-chan error) { - // This blocks until the consumer finishes the processing - // TODO: Consider adding a timeout here - select { - case err := <-errC: - if err != nil { - // TODO: consider adding a callback to inform of the error - rateLimitedLog.ErrorL(l.id.String(), "unable to handle event: %v", err) - } - case <-l.stopper.Flow().StopRequested(): + // Consume() is expected to return promptly; consumers are responsible for + // reporting their own errors (metrics/logs), so the result is not awaited here. + // Waiting on it would require a per-event goroutine that outlives this call + // whenever the consumer stalls, with no way to bound how many accumulate. + c.Consume(l.stopper.Client().Stopped(), event) } } diff --git a/sensor/common/pubsub/lane/concurrent_test.go b/sensor/common/pubsub/lane/concurrent_test.go index c6bd8f29500ef..a5dd8e846481a 100644 --- a/sensor/common/pubsub/lane/concurrent_test.go +++ b/sensor/common/pubsub/lane/concurrent_test.go @@ -316,6 +316,46 @@ func TestErrorHandling(t *testing.T) { }) } +func TestHandleEvent_StalledConsumerDoesNotLeakGoroutines(t *testing.T) { + defer goleak.AssertNoGoroutineLeaks(t) + + const numEvents = 50 + consumeCalled := make(chan struct{}, numEvents) + lane := NewConcurrentLane(pubsub.DefaultLane, WithConcurrentLaneConsumer(newStalledConsumer(consumeCalled))).NewLane() + require.NotNil(t, lane) + defer lane.Stop() + + require.NoError(t, lane.RegisterConsumer(pubsub.DefaultConsumer, pubsub.DefaultTopic, func(_ pubsub.Event) error { + return nil + })) + + // Snapshot after the lane's own goroutines exist (run loop). Find must run + // before Stop(): the old watcher also selected on the stop signal, so + // checking only after Stop() would hide a reintroduced leak. + ignoreExisting := goleak.IgnoreCurrent() + + for range numEvents { + require.NoError(t, lane.Publish(&concurrentTestEvent{})) + } + + // Wait until the lane has actually dispatched Consume() for every published + // event before checking for leaks - otherwise the check could race ahead of + // dispatch and pass trivially regardless of what handleEvent does. + for i := range numEvents { + select { + case <-consumeCalled: + case <-time.After(time.Second): + t.Fatalf("timed out waiting for stalledConsumer.Consume to be called for event %d/%d", i+1, numEvents) + } + } + + // This must be checked while the lane is still running, i.e. before Stop(). + // stalledConsumer's errC is never resolved, so any per-event watcher + // goroutine would still be alive here. + require.NoError(t, goleak.Find(append([]goleak.Option{ignoreExisting}, goleak.CommonIgnores()...)...), + "concurrentLane must not spawn a goroutine per stalled consumer event") +} + func TestStop(t *testing.T) { defer goleak.AssertNoGoroutineLeaks(t) t.Run("stop should clean up resources", func(t *testing.T) { @@ -382,3 +422,24 @@ func (m *mockConsumer) Stop() { m.stopFn() } } + +// newStalledConsumer builds a pubsub.NewConsumer factory whose Consume() returns +// a channel that is never written to and never closed, simulating a consumer +// whose processing never resolves. Every Consume() call is reported on +// consumeCalled, so callers can deterministically wait for dispatch. +func newStalledConsumer(consumeCalled chan<- struct{}) pubsub.NewConsumer { + return func(_ pubsub.LaneID, _ pubsub.Topic, _ pubsub.ConsumerID, _ pubsub.EventCallback) (pubsub.Consumer, error) { + return &stalledConsumer{consumeCalled: consumeCalled}, nil + } +} + +type stalledConsumer struct { + consumeCalled chan<- struct{} +} + +func (c *stalledConsumer) Consume(_ concurrency.Waitable, _ pubsub.Event) <-chan error { + c.consumeCalled <- struct{}{} + return make(chan error) +} + +func (c *stalledConsumer) Stop() {} From 3fdb214e7fb5d147cedcc5f7ef0d2f2f77705139 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 07:06:49 -0600 Subject: [PATCH 039/638] chore(deps): bump github.com/Azure/azure-sdk-for-go-extensions from 0.6.0 to 0.7.0 (#22396) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7b613408a5bdb..b2f3f8044d64d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( cloud.google.com/go/securitycenter v1.46.0 cloud.google.com/go/storage v1.64.0 dario.cat/mergo v1.0.2 - github.com/Azure/azure-sdk-for-go-extensions v0.6.0 + github.com/Azure/azure-sdk-for-go-extensions v0.7.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 diff --git a/go.sum b/go.sum index bd0c746136a7e..3857077a753f9 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go-extensions v0.6.0 h1:LzJ4iAk3ZBZ0Y27uUm66XBQntbgMr3QXn2KIDb4Mx04= -github.com/Azure/azure-sdk-for-go-extensions v0.6.0/go.mod h1:f/wRrqvvh197V5r4jGADV7528UdO/zfL+/Ud92BMSag= +github.com/Azure/azure-sdk-for-go-extensions v0.7.0 h1:ko7jxarxFmfm3OAtIkKcnC2yK1H7a7wB7As38huaa4Y= +github.com/Azure/azure-sdk-for-go-extensions v0.7.0/go.mod h1:f/wRrqvvh197V5r4jGADV7528UdO/zfL+/Ud92BMSag= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 h1:4gRPBpN1f6xt88yi4WR26m7XaD9OlWtVT6bWPdGUIok= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0/go.mod h1:G7QVLxw1j1JVyrO1MA95S8m8HStaaleDZYTcfGgjB2o= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= From 80855840841f3e302e20add5d138792a277d1ebb Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 21 Aug 2026 19:02:08 +0200 Subject: [PATCH 040/638] ROX-36297, ROX-36298: drop Splunk TCP readiness probe in e2e (#22387) --- .../src/main/groovy/util/SplunkUtil.groovy | 3 ++- .../src/test/groovy/IntegrationsTest.groovy | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/qa-tests-backend/src/main/groovy/util/SplunkUtil.groovy b/qa-tests-backend/src/main/groovy/util/SplunkUtil.groovy index aee9924bf9a00..ae4138b0a63c7 100644 --- a/qa-tests-backend/src/main/groovy/util/SplunkUtil.groovy +++ b/qa-tests-backend/src/main/groovy/util/SplunkUtil.groovy @@ -191,6 +191,8 @@ class SplunkUtil { Service syslogSvc LocalPortForward splunkPortForward try { + // No TCP readiness probe: Splunk binds 8089 after the container is + // running, and waitForSplunkBoot already waits on the health API. deployment = new Deployment() .setNamespace(namespace) @@ -204,7 +206,6 @@ class SplunkUtil { .addLabel("app", deploymentName) .addRequest("cpu", "500m") .addLimits("memory", "3Gi") - .setReadinessProbeTcpPort(8089) orchestrator.createDeployment(deployment) collectorSvc = new Service("splunk-collector-" + uid, namespace) diff --git a/qa-tests-backend/src/test/groovy/IntegrationsTest.groovy b/qa-tests-backend/src/test/groovy/IntegrationsTest.groovy index cc612fe61dc4e..10c31990c17dc 100644 --- a/qa-tests-backend/src/test/groovy/IntegrationsTest.groovy +++ b/qa-tests-backend/src/test/groovy/IntegrationsTest.groovy @@ -185,12 +185,16 @@ class IntegrationsTest extends BaseSpecification { def "Verify Splunk Integration"() { given: "the integration is tested" - SplunkUtil.SplunkDeployment parts = SplunkUtil.createSplunk(orchestrator, Constants.ORCHESTRATOR_NAMESPACE) + SplunkUtil.SplunkDeployment parts = null + SplunkNotifier notifier = null + String policyId = null + Deployment nginxdeployment = null + parts = SplunkUtil.createSplunk(orchestrator, Constants.ORCHESTRATOR_NAMESPACE) SplunkUtil.waitForSplunkBoot(parts.splunkPortForward.localPort) when: "call the grpc API for the splunk integration." - SplunkNotifier notifier = new SplunkNotifier(parts.collectorSvc.name, parts.splunkPortForward.localPort) + notifier = new SplunkNotifier(parts.collectorSvc.name, parts.splunkPortForward.localPort) notifier.createNotifier() and: @@ -205,11 +209,11 @@ class IntegrationsTest extends BaseSpecification { .setKey("app") .setValue(nginxName))) .addNotifiers(notifier.getId()) - def policyId = PolicyService.createNewPolicy(policy.build()) + policyId = PolicyService.createNewPolicy(policy.build()) and: "Create a new deployment to trigger the violation against the policy" - Deployment nginxdeployment = + nginxdeployment = new Deployment() .setName(nginxName) .setImage("quay.io/rhacs-eng/qa-multi-arch-nginx:latest") @@ -223,16 +227,16 @@ class IntegrationsTest extends BaseSpecification { cleanup: "remove Deployment and services" - if (parts.deployment != null) { + if (nginxdeployment) { orchestrator.deleteDeployment(nginxdeployment) } - if (policy != null) { + if (policyId) { PolicyService.deletePolicy(policyId) } if (parts) { SplunkUtil.tearDownSplunk(orchestrator, parts) } - notifier.deleteNotifier() + notifier?.deleteNotifier() } @Unroll From dee4860aff33fb203cdee42b313d014497ba2f3c Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:47:38 -0700 Subject: [PATCH 041/638] ROX-36424: Add prescan-slow-path burst test mode (#22289) --- tools/admission-control/README.md | 81 ++++++- tools/admission-control/bench-reassess.sh | 2 +- tools/admission-control/burst-test.sh | 265 ++++++++++++++++++++-- tools/admission-control/lib.sh | 138 +++++++++-- 4 files changed, 438 insertions(+), 48 deletions(-) diff --git a/tools/admission-control/README.md b/tools/admission-control/README.md index 871afa557a611..9ad56a276561e 100644 --- a/tools/admission-control/README.md +++ b/tools/admission-control/README.md @@ -7,7 +7,8 @@ Central around image cache and reprocessor ("reassess all") workflows. - Kubernetes/OpenShift cluster with StackRox deployed - `admissionControl.enforcement` enabled in the SecuredCluster CR -- `spec.monitoring.exposeEndpoint: Enabled` in the SecuredCluster CR +- `spec.monitoring.exposeEndpoint: Enabled` on **both** the SecuredCluster and Central CRs + (AC metrics for all modes; Central metrics required for `prescan-slow-path`) ## Common Environment Variables @@ -49,6 +50,58 @@ All scripts follow the same pattern: 3. Run again, save output 4. `diff` the two outputs (or use `go tool pprof -diff_base` for profiles) +### Example: prescan-slow-path comparison + +This workflow proves whether Central skips Scanner `GetScan` for tag-only +admission after a Central pre-scan (`ScanImageInternalForAdmission`). + +**Policy (required):** enable **90-Day Image Age**, Deploy lifecycle, **Fail** +on admission. Spec-only policies (Privileged Container, Latest tag) will not +fetch images and the run is invalid. + +**Why these flags:** `UNIQUE_PCT=100` and `PARALLEL=1` make every review a +Central fetch of a distinct pre-scanned tag. Cache hits and coalescing no +longer move the averages. Failed prescans are dropped from the burst so the +PR cannot pick up extra Scanner calls for uncached tags. + +Scrape happens **after** prescan so `scan_duration_count` from +`POST /v1/images/scan` is not counted in the burst delta. + +```bash +# 1. Deploy master image. Enforce 90-Day Image Age (Fail on admission). +ROX_PASSWORD= ROX_CENTRAL_ADDRESS= \ + BURST_SIZE=20 UNIQUE_PCT=100 PARALLEL=1 \ + ./tools/admission-control/burst-test.sh prescan-slow-path \ + | tee /tmp/prescan-master.txt + +# 2. Redeploy Central + Sensor with PR branch image +# (AC pods will be restarted by the script itself). +# Re-export ROX_PASSWORD if the redeploy minted a new one. + +# 3. Run again on PR branch +ROX_PASSWORD= ROX_CENTRAL_ADDRESS= \ + BURST_SIZE=20 UNIQUE_PCT=100 PARALLEL=1 \ + ./tools/admission-control/burst-test.sh prescan-slow-path \ + | tee /tmp/prescan-pr.txt + +# 4. Compare +diff -y /tmp/prescan-master.txt /tmp/prescan-pr.txt +``` + +Pass bar (both runs must have `image_fetch_total` ≈ unique images): + +| Metric | Master | PR branch | +|--------|--------|-----------| +| `scan_duration_count` (Central) | ≈ unique images | **~0** | +| `image_fetch_duration (avg)` | metadata + GetScan | metadata only (lower) | +| `image_fetch_total` | ≈ unique | ≈ unique | +| `review_duration_seconds (avg)` | ignore | ignore (diluted by hits) | + +`scan_duration_count` is the Scanner-trip proof. Fetch duration is supporting +latency. Do not treat `review_duration_seconds` as a Scanner proxy: after +prescan, Scanner's index is warm so GetScan can be cheap, and review avg mixes +in cache hits. + --- ### `burst-test.sh` @@ -60,6 +113,7 @@ Run with `-h` for all options. |------|---------------|-------------------| | `fast-path` | Spec-only evaluation (no image fetching) | Privileged Container, Latest tag | | `slow-path` | Image coalescing + caching (enrichment) | Any enrichment-required (e.g. Image Age) | +| `prescan-slow-path` | Scan reuse for pre-scanned tag-only images | Any enrichment-required (e.g. Image Age) | Slow-path runs two phases automatically: **cold cache** (pods restarted) then **warm cache** (immediate re-burst). @@ -78,6 +132,31 @@ IMAGES_FILE=/tmp/my-images.txt BURST_SIZE=500 UNIQUE_PCT=50 ./burst-test.sh slow BURST_SIZE=1000 UNIQUE_PCT=60 ./burst-test.sh slow-path --no-dry-run ``` +**Prescan-slow-path** pre-scans unique images via Central's `POST /v1/images/scan` +API (warming the DB), drops failures, restarts AC (not Scanner), then bursts +**one tag-only deployment per successful prescan**. Central `scan_duration_count` +during the burst is the Scanner-trip proof; AC `image_fetch_duration_seconds` +is the latency proof. + +```bash +# Prescan + one-fetch-per-unique-image burst +ROX_PASSWORD= ROX_CENTRAL_ADDRESS= \ + BURST_SIZE=20 UNIQUE_PCT=100 PARALLEL=1 PRESCAN_PARALLEL=5 \ + ./tools/admission-control/burst-test.sh prescan-slow-path +``` + +Requires **90-Day Image Age** (or another enrichment-required policy) enabled +with **Fail on admission**. `UNIQUE_PCT` is forced to 100. + +Additional environment variables for `prescan-slow-path`: + +| Variable | Default | Description | +|----------|---------|-------------| +| `ROX_CENTRAL_ADDRESS` | *(required)* | Central host:port | +| `ROX_PASSWORD` | *(required)* | Central admin password | +| `ROX_ADMIN_USER` | `admin` | Central admin username | +| `PRESCAN_PARALLEL` | `5` | Max concurrent prescan API calls | + --- ### `bench-reassess.sh` diff --git a/tools/admission-control/bench-reassess.sh b/tools/admission-control/bench-reassess.sh index 4e7459b310007..ca59edb792147 100755 --- a/tools/admission-control/bench-reassess.sh +++ b/tools/admission-control/bench-reassess.sh @@ -79,7 +79,7 @@ read_metric() { # metric_delta [labels] metric_delta() { - echo "$(read_metric "$2" "$3" "$4" "${5:-}") - $(read_metric "$1" "$3" "$4" "${5:-}")" | bc + delta "$(read_metric "$1" "$3" "$4" "${5:-}")" "$(read_metric "$2" "$3" "$4" "${5:-}")" } # trigger_and_wait_for_reprocessor is now in lib.sh diff --git a/tools/admission-control/burst-test.sh b/tools/admission-control/burst-test.sh index 5497f4dd6a175..4fbd75b184754 100755 --- a/tools/admission-control/burst-test.sh +++ b/tools/admission-control/burst-test.sh @@ -5,12 +5,18 @@ # a live cluster, scrapes admission controller Prometheus metrics before and # after, and reports deltas. # -# Two modes: -# fast-path - Tests spec-only policy evaluation (no image fetching). -# Requires: Privileged Container + Latest tag policies enforced. -# slow-path - Tests image coalescing and caching in the enrichment path. -# Runs two phases (cold cache, then warm cache). -# Requires: at least one enrichment-required policy (e.g. Image Age). +# Three modes: +# fast-path - Tests spec-only policy evaluation (no image fetching). +# Requires: Privileged Container + Latest tag policies enforced. +# slow-path - Tests image coalescing and caching in the enrichment path. +# Runs two phases (cold cache, then warm cache). +# Requires: at least one enrichment-required policy (e.g. Image Age). +# prescan-slow-path - Pre-scans unique images via Central API, then bursts +# one tag-only deployment per pre-scanned image. +# Proves Scanner skip via Central scan_duration_count +# and fetch latency via AC image_fetch_duration_seconds. +# Run on master vs PR branch to compare. +# Requires: ROX_CENTRAL_ADDRESS, ROX_PASSWORD, enrichment policy. # # See README.md for full prerequisites and cross-branch comparison workflow. set -euo pipefail @@ -37,6 +43,8 @@ export DRY_RUN : "${METRICS_PORT:=9090}" : "${LOCAL_PORT:=9090}" : "${PARALLEL:=50}" +: "${PRESCAN_PARALLEL:=5}" +: "${ROX_ADMIN_USER:=admin}" WORK_DIR=$(mktemp -d) @@ -48,11 +56,18 @@ usage() { Usage: ./burst-test.sh [options] Modes: - fast-path Test spec-only policy evaluation (no image fetching). - Requires: Privileged Container + Latest tag policies enforced. - slow-path Test image coalescing and caching in the enrichment path. - Runs two phases: cold cache (pods restarted) then warm cache. - Requires: at least one enrichment-required policy (e.g. Image Age). + fast-path Test spec-only policy evaluation (no image fetching). + Requires: Privileged Container + Latest tag policies enforced. + slow-path Test image coalescing and caching in the enrichment path. + Runs two phases: cold cache (pods restarted) then warm cache. + Requires: at least one enrichment-required policy (e.g. Image Age). + prescan-slow-path Pre-scan unique images via Central API, restart AC, then + burst one tag-only deployment per pre-scanned image. + PRIMARY: Central scan_duration_count (Scanner GetScan calls). + SECONDARY: AC image_fetch_duration_seconds (per-fetch latency). + Forces UNIQUE_PCT=100. Use PARALLEL=1 for a clean comparison. + Requires: ROX_CENTRAL_ADDRESS, ROX_PASSWORD, enrichment policy + (e.g. 90-Day Image Age with Fail on admission). Options: --no-dry-run Apply deployments for real (replicas=0). Useful for creating @@ -60,18 +75,23 @@ Options: -h, --help Show this help message. Environment variables: - BURST_SIZE Number of deployments to create (default: 500) - VIOLATION_PCT % that violate policy (fast-path only) (default: 50) - UNIQUE_PCT % of BURST_SIZE used as distinct images (default: 25) - Unique count = BURST_SIZE * UNIQUE_PCT / 100. - IMAGES_FILE Path to a file with one image per line. - Overrides automatic pool generation. - NAMESPACE Namespace for test deployments (default: burst-test) - IMAGES Comma-separated images (fast-path only) (default: quay.io/centos/centos:7,...) - ROX_NAMESPACE StackRox namespace (default: stackrox) - METRICS_PORT Admission controller metrics port (default: 9090) - LOCAL_PORT Local port for kubectl port-forward (default: 9090) - PARALLEL Max concurrent kubectl creates (default: 50) + BURST_SIZE Number of deployments to create (default: 500) + VIOLATION_PCT % that violate policy (fast-path only) (default: 50) + UNIQUE_PCT % of BURST_SIZE used as distinct images (default: 25) + Unique count = BURST_SIZE * UNIQUE_PCT / 100. + IMAGES_FILE Path to a file with one image per line. + Overrides automatic pool generation. + NAMESPACE Namespace for test deployments (default: burst-test) + IMAGES Comma-separated images (fast-path only) (default: quay.io/centos/centos:7,...) + ROX_NAMESPACE StackRox namespace (default: stackrox) + METRICS_PORT Admission controller metrics port (default: 9090) + LOCAL_PORT Local port for kubectl port-forward (default: 9090) + PARALLEL Max concurrent kubectl creates (default: 50; + prescan-slow-path: use 1) + PRESCAN_PARALLEL Max concurrent prescan API calls (default: 5) + ROX_CENTRAL_ADDRESS Central host:port (prescan-slow-path) + ROX_PASSWORD Central admin password (prescan-slow-path) + ROX_ADMIN_USER Central admin username (default: admin) fast-path mode: Violating deployments use privileged: true + :latest tags. @@ -85,6 +105,17 @@ slow-path mode: Phase 2 reuses warm caches from phase 1. Reports coalesce ratio, cache hit rate, and fetch counts. With --no-dry-run, deployments are created with replicas=0. + +prescan-slow-path mode: + Forces UNIQUE_PCT=100 (one unique tag-only image per review). Failed + prescans are dropped so the burst only uses images stored in Central. + Restarts AC (not Scanner), then scrapes Central AND AC metrics AFTER + prescan and BEFORE the burst so prescan GetScan calls are excluded. + PRIMARY proof: rox_central_scan_duration_count delta (~unique on master, + ~0 on PR). SECONDARY: AC image_fetch_duration_seconds avg. + Do not use review_duration_seconds — cache hits dilute it. + Recommended: BURST_SIZE=20 PARALLEL=1 + Requires 90-Day Image Age (or similar) with Fail on admission. EOF exit 0 } @@ -93,17 +124,30 @@ if [[ "${MODE}" == "-h" || "${MODE}" == "--help" ]]; then usage fi -if [[ "${MODE}" != "fast-path" && "${MODE}" != "slow-path" ]]; then - echo "ERROR: mode must be 'fast-path' or 'slow-path'" >&2 +if [[ "${MODE}" != "fast-path" && "${MODE}" != "slow-path" && "${MODE}" != "prescan-slow-path" ]]; then + echo "ERROR: mode must be 'fast-path', 'slow-path', or 'prescan-slow-path'" >&2 echo "Run with -h for usage." >&2 exit 1 fi +if [[ "${MODE}" == "prescan-slow-path" ]]; then + : "${ROX_CENTRAL_ADDRESS:?ROX_CENTRAL_ADDRESS must be set for prescan-slow-path}" + : "${ROX_PASSWORD:?ROX_PASSWORD must be set for prescan-slow-path}" + if [[ "$UNIQUE_PCT" != "100" ]]; then + echo "NOTE: prescan-slow-path forces UNIQUE_PCT=100 (one unique image per review)." + UNIQUE_PCT=100 + fi + if [[ "$PARALLEL" -gt 1 ]]; then + echo "WARN: PARALLEL=${PARALLEL}. Use PARALLEL=1 so fetch/hit mix does not move averages." + fi +fi + for cmd in kubectl curl bc awk; do command -v "$cmd" &>/dev/null || { echo "ERROR: '$cmd' required but not found" >&2; exit 1; } done readonly METRICS_NETPOL="admission-control-metrics-allow" +readonly CENTRAL_METRICS_NETPOL="central-metrics-allow" trap cleanup EXIT # --------------------------------------------------------------------------- @@ -114,6 +158,8 @@ cleanup() { kill_port_forwards kubectl -n "$ROX_NAMESPACE" delete networkpolicy "$METRICS_NETPOL" \ --ignore-not-found &>/dev/null || true + kubectl -n "$ROX_NAMESPACE" delete networkpolicy "$CENTRAL_METRICS_NETPOL" \ + --ignore-not-found &>/dev/null || true kubectl delete namespace "$NAMESPACE" --ignore-not-found &>/dev/null || true [[ -n "${WORK_DIR:-}" ]] && rm -rf "$WORK_DIR" } @@ -122,6 +168,10 @@ allow_metrics_ingress() { allow_metrics_netpol "$METRICS_NETPOL" "admission-control" } +allow_central_metrics_ingress() { + allow_metrics_netpol "$CENTRAL_METRICS_NETPOL" "central" +} + restart_admission_control() { echo "Restarting admission-control pods..." kubectl -n "$ROX_NAMESPACE" rollout restart deployment/admission-control >/dev/null @@ -149,6 +199,18 @@ scrape_metrics() { scrape_component "admission-control" "$LOCAL_PORT" "$1" } +scrape_central_metrics() { + scrape_component "central" "$LOCAL_PORT" "$1" +} + +# scrape_prescan_pair +# Sequential scrapes (same LOCAL_PORT). Must run AFTER prescan so Central +# scan_duration from POST /v1/images/scan is in the baseline. +scrape_prescan_pair() { + scrape_metrics "$1" + scrape_central_metrics "$2" +} + # Backward-compatible wrapper used by reports. extract_counter() { extract "$@"; } @@ -344,6 +406,101 @@ EOF echo "" } +report_prescan_slow_path() { + local ac_before="$1" ac_after="$2" central_before="$3" central_after="$4" + local prescan_ok="$5" prescan_fail="$6" + compute_common_deltas "$ac_before" "$ac_after" + + local review_total + review_total=$(echo "${DENIED_D} + ${ALLOWED_D}" | bc) + + local scan_count_d scan_sum_d scan_avg_ms + scan_count_d=$(delta \ + "$(extract "$central_before" "rox_central_scan_duration_count")" \ + "$(extract "$central_after" "rox_central_scan_duration_count")") + scan_sum_d=$(delta \ + "$(extract "$central_before" "rox_central_scan_duration_sum")" \ + "$(extract "$central_after" "rox_central_scan_duration_sum")") + scan_avg_ms=$(avg_or_na "$scan_sum_d" "$scan_count_d") + + local fetch_dur_count_d fetch_dur_sum_d fetch_dur_avg + fetch_dur_count_d=$(delta \ + "$(extract "$ac_before" "rox_admission_control_image_fetch_duration_seconds_count")" \ + "$(extract "$ac_after" "rox_admission_control_image_fetch_duration_seconds_count")") + fetch_dur_sum_d=$(delta \ + "$(extract "$ac_before" "rox_admission_control_image_fetch_duration_seconds_sum")" \ + "$(extract "$ac_after" "rox_admission_control_image_fetch_duration_seconds_sum")") + fetch_dur_avg=$(avg_or_na "$fetch_dur_sum_d" "$fetch_dur_count_d") + + cat < 0)}'; then + echo " Scanner GetScan was invoked ${scan_count_d} times during the burst." + echo " Expected on master; unexpected on PR if prescan succeeded." + else + echo " Scanner GetScan was NOT invoked during the burst." + echo " Expected on PR; unexpected on master." + fi + echo "" + + local pass=true + if [[ "$prescan_fail" -gt 0 ]]; then + echo " WARN: ${prescan_fail} images failed to pre-scan and were dropped from the burst." + fi + if [[ "$FETCH_D" -eq 0 ]]; then + echo " FAIL: image_fetch_total delta is 0. Enforce an enrichment-required policy" + echo " (90-Day Image Age, Fail on admission) and retry." + pass=false + fi + if [[ "$review_total" -ne "$BURST_SIZE" ]]; then + echo " FAIL: expected ${BURST_SIZE} reviews, got ${review_total}" + pass=false + fi + if $pass; then + echo " PASS: ${review_total} reviews, ${FETCH_D} fetches, scan_duration_count=${scan_count_d}" + fi + echo "" +} + # --------------------------------------------------------------------------- # Main flow # --------------------------------------------------------------------------- @@ -464,4 +621,62 @@ EOF report_slow_path_phase "PHASE 2: WARM CACHE" \ "${WORK_DIR}/warm_before.prom" "${WORK_DIR}/warm_after.prom" + +elif [[ "$MODE" == "prescan-slow-path" ]]; then + allow_central_metrics_ingress + + UNIQUE_COUNT=$BURST_SIZE + [[ "$UNIQUE_COUNT" -lt 1 ]] && UNIQUE_COUNT=1 + load_image_pool "$UNIQUE_COUNT" + + cat </dev/null || true) + PRESCAN_FAIL=$(grep -c "^fail " "${WORK_DIR}/prescan.log" 2>/dev/null || true) + + filter_pool_to_prescan_success + write_slow_path_manifests + TOTAL_IMAGE_REFS=$BURST_SIZE + + # --- Phase 2: Cold-cache burst (Central scrape AFTER prescan) --- + echo "" + echo "=== Phase 2: Restart AC (cold cache) + burst ===" + restart_admission_control + + echo "Scraping baseline AC + Central metrics (post-prescan)..." + scrape_prescan_pair \ + "${WORK_DIR}/prescan_before.prom" \ + "${WORK_DIR}/prescan_central_before.prom" + + run_burst "Prescan burst (cold cache)" + + echo "Waiting for metrics to settle..." + sleep 10 + + echo "Scraping post-burst AC + Central metrics..." + scrape_prescan_pair \ + "${WORK_DIR}/prescan_after.prom" \ + "${WORK_DIR}/prescan_central_after.prom" + + report_prescan_slow_path \ + "${WORK_DIR}/prescan_before.prom" "${WORK_DIR}/prescan_after.prom" \ + "${WORK_DIR}/prescan_central_before.prom" "${WORK_DIR}/prescan_central_after.prom" \ + "$PRESCAN_OK" "$PRESCAN_FAIL" fi diff --git a/tools/admission-control/lib.sh b/tools/admission-control/lib.sh index d0cdb10e24d7d..5b1d138207489 100644 --- a/tools/admission-control/lib.sh +++ b/tools/admission-control/lib.sh @@ -155,6 +155,18 @@ scrape_component() { # Metric math # --------------------------------------------------------------------------- +# Print n in fixed-point form. awk's default OFMT (%.6g) emits 1.23e+06 for +# values >= 1e6, and bc cannot parse the '+' ("Parse error: bad character '+'"). +_awk_fmt=' +function fmt(n) { + x = sprintf("%.6f", n+0) + sub(/0+$/, "", x) + sub(/\.$/, "", x) + if (x == "" || x == "-" || x == "-0") x = "0" + return x +} +' + # Sum a Prometheus counter/gauge across all lines matching metric + optional labels. extract() { local file="$1" metric="$2" labels="${3:-}" @@ -164,31 +176,29 @@ extract() { fi if [[ -n "$labels" ]]; then { grep "^${metric}[{ ]" "$file" 2>/dev/null | grep "$labels" || true; } \ - | awk '{s+=$NF} END{print s+0}' + | awk "${_awk_fmt}"'{s+=$NF} END{print fmt(s)}' else { grep "^${metric}[{ ]" "$file" 2>/dev/null || true; } \ - | awk '{s+=$NF} END{print s+0}' + | awk "${_awk_fmt}"'{s+=$NF} END{print fmt(s)}' fi } -delta() { echo "$2 - $1" | bc; } +delta() { + awk -v a="$1" -v b="$2" "${_awk_fmt}"'BEGIN{print fmt(b-a)}' +} avg_or_na() { - local sum_d="$1" count_d="$2" - if (( $(echo "$count_d > 0" | bc -l) )); then - echo "scale=4; $sum_d / $count_d" | bc - else - echo "N/A" - fi + awk -v s="$1" -v c="$2" 'BEGIN{ + if (c+0 > 0) printf "%.4f\n", s/c + else print "N/A" + }' } pct_or_na() { - local numerator="$1" denominator="$2" - if (( $(echo "$denominator > 0" | bc -l) )); then - echo "scale=1; $numerator * 100 / $denominator" | bc - else - echo "N/A" - fi + awk -v n="$1" -v d="$2" 'BEGIN{ + if (d+0 > 0) printf "%.1f\n", n*100/d + else print "N/A" + }' } row() { printf " %-50s %s\n" "$1" "$2"; } @@ -244,12 +254,9 @@ YAML } > "$outfile" } -generate_slow_path_manifests() { - UNIQUE_COUNT=$(( BURST_SIZE * UNIQUE_PCT / 100 )) - [[ "$UNIQUE_COUNT" -lt 1 ]] && UNIQUE_COUNT=1 - - load_image_pool "$UNIQUE_COUNT" - +# write_slow_path_manifests writes BURST_SIZE deployments from SCANNABLE_POOL. +# Requires: BURST_SIZE, UNIQUE_COUNT, SCANNABLE_POOL, WORK_DIR, NAMESPACE +write_slow_path_manifests() { for (( i = 0; i < BURST_SIZE; i++ )); do generate_slow_path_deployment "burst-${i}" \ "${WORK_DIR}/deploy-${i}.yaml" \ @@ -258,6 +265,14 @@ generate_slow_path_manifests() { echo " Generated ${BURST_SIZE} manifests (${UNIQUE_COUNT} unique images)." } +generate_slow_path_manifests() { + UNIQUE_COUNT=$(( BURST_SIZE * UNIQUE_PCT / 100 )) + [[ "$UNIQUE_COUNT" -lt 1 ]] && UNIQUE_COUNT=1 + + load_image_pool "$UNIQUE_COUNT" + write_slow_path_manifests +} + # --------------------------------------------------------------------------- # Reassess trigger + wait # --------------------------------------------------------------------------- @@ -324,6 +339,87 @@ trigger_and_wait_for_reprocessor() { wait "$log_pid" 2>/dev/null || true } +# --------------------------------------------------------------------------- +# Pre-scan helpers (warm Central DB via REST API) +# --------------------------------------------------------------------------- + +# prescan_image +# Triggers a ScanImage request to Central to populate scan data in the DB. +# Requires: ROX_CENTRAL_ADDRESS, ROX_ADMIN_USER, ROX_PASSWORD +# Prints the HTTP status code on stdout. Returns 0 only for HTTP 200. +prescan_image() { + local image="$1" + local http_code + http_code=$(curl -sk -o /dev/null -w '%{http_code}' \ + -u "${ROX_ADMIN_USER}:${ROX_PASSWORD}" \ + --max-time 120 \ + -X POST "https://${ROX_CENTRAL_ADDRESS}/v1/images/scan" \ + -H "Content-Type: application/json" \ + -d "{\"image_name\": \"${image}\", \"force\": true}") + echo "$http_code" + [[ "$http_code" == "200" ]] +} + +# prescan_pool +# Pre-scans SCANNABLE_POOL[0..UNIQUE_COUNT-1] via Central's ScanImage API. +# Requires: ROX_CENTRAL_ADDRESS, ROX_ADMIN_USER, ROX_PASSWORD, +# SCANNABLE_POOL, UNIQUE_COUNT +prescan_pool() { + local parallel="${1:-5}" + local completed=0 failed=0 + local pids=() + + for (( i = 0; i < UNIQUE_COUNT; i++ )); do + local img="${SCANNABLE_POOL[$i]}" + ( + if code=$(prescan_image "$img"); then + echo "ok ${img}" + else + echo "fail ${img} http=${code}" + fi + ) >> "${WORK_DIR}/prescan.log" & + pids+=($!) + + if [[ "${#pids[@]}" -ge "$parallel" ]]; then + wait "${pids[@]}" 2>/dev/null || true + pids=() + fi + done + if [[ "${#pids[@]}" -gt 0 ]]; then + wait "${pids[@]}" 2>/dev/null || true + fi + + completed=$(grep -c "^ok " "${WORK_DIR}/prescan.log" 2>/dev/null || true) + failed=$(grep -c "^fail " "${WORK_DIR}/prescan.log" 2>/dev/null || true) + echo " Pre-scan complete: ${completed} succeeded, ${failed} failed (of ${UNIQUE_COUNT})." + + if [[ "$failed" -gt 0 ]]; then + echo " Failed images:" >&2 + grep "^fail " "${WORK_DIR}/prescan.log" | while read -r _ rest; do + echo " ${rest}" >&2 + done + fi +} + +# filter_pool_to_prescan_success keeps only images that returned HTTP 200. +# Sets SCANNABLE_POOL, UNIQUE_COUNT, BURST_SIZE, and POOL_SIZE to that set so +# the burst never includes tags that were not stored in Central's DB. +filter_pool_to_prescan_success() { + local ok_file="${WORK_DIR}/prescan.ok" + grep "^ok " "${WORK_DIR}/prescan.log" 2>/dev/null | awk '{print $2}' > "$ok_file" || true + mapfile -t SCANNABLE_POOL < "$ok_file" + UNIQUE_COUNT=${#SCANNABLE_POOL[@]} + if [[ "$UNIQUE_COUNT" -eq 0 ]]; then + echo "ERROR: every prescan failed. Check ROX_PASSWORD, ROX_CENTRAL_ADDRESS, and Scanner health." >&2 + echo " Sample failures:" >&2 + grep "^fail " "${WORK_DIR}/prescan.log" | head -5 >&2 || true + exit 1 + fi + BURST_SIZE=$UNIQUE_COUNT + POOL_SIZE=$UNIQUE_COUNT + echo " Bursting ${BURST_SIZE} unique pre-scanned images (failed prescans dropped)." +} + # --------------------------------------------------------------------------- # Burst runner # --------------------------------------------------------------------------- From 4b14b8d34d03162aa81bca7b3317e92a39f67f08 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 21 Aug 2026 21:38:13 +0200 Subject: [PATCH 042/638] fix(telemetry): query V2 stores when enhanced VM model is on (#22310) Co-authored-by: Cursor --- central/main.go | 6 + central/virtualmachine/datastore/telemetry.go | 78 +++++- .../datastore/telemetry_test.go | 247 ++++++++++++++++-- 3 files changed, 307 insertions(+), 24 deletions(-) diff --git a/central/main.go b/central/main.go index ae7dcf43d3544..90fa6f6a8d0ad 100755 --- a/central/main.go +++ b/central/main.go @@ -178,7 +178,9 @@ import ( "github.com/stackrox/rox/central/version" vStore "github.com/stackrox/rox/central/version/store" virtualMachineDS "github.com/stackrox/rox/central/virtualmachine/datastore" + virtualMachineScanV2DS "github.com/stackrox/rox/central/virtualmachine/scan/v2/datastore" virtualmachineService "github.com/stackrox/rox/central/virtualmachine/service" + virtualMachineV2DS "github.com/stackrox/rox/central/virtualmachine/v2/datastore" virtualmachineV2Service "github.com/stackrox/rox/central/virtualmachine/v2/service" vulnMgmtService "github.com/stackrox/rox/central/vulnmgmt/service" vulnRequestManager "github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/manager/requestmgr" @@ -716,6 +718,10 @@ func addCentralIdentityGatherers(c *phonehomeClient.CentralClient) { add(roleDataStore.Gather) add(signatureIntegrationDS.Gather) add(virtualMachineDS.Gather(virtualMachineDS.Singleton())) + add(virtualMachineDS.GatherV2( + virtualMachineV2DS.Singleton(), + virtualMachineScanV2DS.Singleton(), + )) } func registerDelayedIntegrations(integrationsInput []iiStore.DelayedIntegration) { diff --git a/central/virtualmachine/datastore/telemetry.go b/central/virtualmachine/datastore/telemetry.go index 04bc665791488..777124c8f6e53 100644 --- a/central/virtualmachine/datastore/telemetry.go +++ b/central/virtualmachine/datastore/telemetry.go @@ -4,14 +4,17 @@ import ( "context" "time" + v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/sac/resources" + "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/telemetry/phonehome" + "github.com/stackrox/rox/pkg/timestamp" ) const ( @@ -35,8 +38,8 @@ var ( // 2. Total number of virtual machines // 3. Number of VMs with active agents (received IndexReport within last 24 hours) // -// When the ROX_VIRTUAL_MACHINES feature flag is disabled, this function returns -// an empty map without performing any database queries, ensuring no performance impact. +// When ROX_VIRTUAL_MACHINES is off, or the enhanced data model is on (GatherV2 +// owns these metric names), Gather returns an empty map and does not query storage. func Gather(ds DataStore) phonehome.GatherFunc { return gatherWithTime(ds, time.Now) } @@ -44,8 +47,7 @@ func Gather(ds DataStore) phonehome.GatherFunc { // gatherWithTime allows injecting a time function for deterministic testing. func gatherWithTime(ds DataStore, nowFunc func() time.Time) phonehome.GatherFunc { return func(ctx context.Context) (map[string]any, error) { - // Early return if virtual machines feature is disabled - zero performance impact - if !features.VirtualMachines.Enabled() { + if !features.VirtualMachines.Enabled() || features.VirtualMachinesEnhancedDataModel.Enabled() { return map[string]any{}, nil } @@ -101,3 +103,71 @@ func gatherWithTime(ds DataStore, nowFunc func() time.Time) phonehome.GatherFunc return props, nil } } + +// vmV2Walker is the subset of the VM V2 datastore this gatherer needs. +type vmV2Walker interface { + Walk(ctx context.Context, fn func(vm *storage.VirtualMachineV2) error) error +} + +// scanV2Counter is the subset of the scan V2 datastore this gatherer needs. +type scanV2Counter interface { + Count(ctx context.Context, q *v1.Query) (int, error) +} + +// GatherV2 returns a gatherer that queries the V2 VM and scan datastores. +func GatherV2(vmV2DS vmV2Walker, scanV2DS scanV2Counter) phonehome.GatherFunc { + return gatherV2WithTime(vmV2DS, scanV2DS, time.Now) +} + +func gatherV2WithTime(vmV2DS vmV2Walker, scanV2DS scanV2Counter, nowFunc func() time.Time) phonehome.GatherFunc { + return func(ctx context.Context) (map[string]any, error) { + if !features.VirtualMachines.Enabled() || !features.VirtualMachinesEnhancedDataModel.Enabled() { + return map[string]any{}, nil + } + + ctx = sac.WithGlobalAccessScopeChecker(ctx, + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), + sac.ResourceScopeKeys(resources.VirtualMachine), + ), + ) + + clusterIDsWithRunningVMs := set.NewStringSet() + totalVMs := 0 + + err := vmV2DS.Walk(ctx, func(vm *storage.VirtualMachineV2) error { + totalVMs++ + if vm.GetState() == storage.VirtualMachineV2_RUNNING { + clusterID := vm.GetClusterId() + if clusterID == "" { + log.Debugf("Virtual machine V2 %s has empty cluster_id", vm.GetId()) + } else { + clusterIDsWithRunningVMs.Add(clusterID) + } + } + return nil + }) + if err != nil { + return nil, err + } + + now := nowFunc() + cutoff := now.Add(-activeVMAgentMaxAgeLimitTelemetry) + q := search.NewQueryBuilder(). + AddTimeRangeField(search.VirtualMachineScanTime, cutoff, timestamp.InfiniteFuture.GoTime()). + ProtoQuery() + + vmsWithActiveAgents, err := scanV2DS.Count(ctx, q) + if err != nil { + return nil, err + } + + props := map[string]any{ + metricClustersWithVMs: clusterIDsWithRunningVMs.Cardinality(), + metricTotalVMs: totalVMs, + metricVMsWithActiveAgents: vmsWithActiveAgents, + } + log.Debugf("Virtual machines V2 telemetry update: %v", props) + return props, nil + } +} diff --git a/central/virtualmachine/datastore/telemetry_test.go b/central/virtualmachine/datastore/telemetry_test.go index f730df2af01e7..5c9fa9ce10b27 100644 --- a/central/virtualmachine/datastore/telemetry_test.go +++ b/central/virtualmachine/datastore/telemetry_test.go @@ -2,6 +2,9 @@ package datastore import ( "context" + "errors" + "strconv" + "strings" "testing" "time" @@ -10,6 +13,8 @@ import ( "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/timestamp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -181,29 +186,42 @@ func TestVirtualMachineTelemetry(t *testing.T) { } } -func TestVirtualMachineTelemetryWithFeatureFlagDisabled(t *testing.T) { - // Disable the feature flag using t.Setenv for automatic cleanup - t.Setenv(features.VirtualMachines.EnvVar(), "false") - - ctx := sac.WithAllAccess(context.Background()) - - // Create a mock datastore with VMs (which should NOT be queried) - ds := &mockDataStore{ - vms: []*storage.VirtualMachine{ - {Id: "vm1", ClusterId: "cluster1", Name: "test-vm", State: storage.VirtualMachine_RUNNING, Scan: createScanWithAge(1 * time.Hour)}, - {Id: "vm2", ClusterId: "cluster2", Name: "test-vm2", State: storage.VirtualMachine_RUNNING, Scan: createScanWithAge(1 * time.Hour)}, +func TestVirtualMachineTelemetrySkipGather(t *testing.T) { + tests := map[string]struct { + virtualMachinesEnabled bool + enhancedDataModel bool + }{ + "should return empty map when virtual machines flag is disabled": { + virtualMachinesEnabled: false, + enhancedDataModel: false, + }, + "should return empty map when enhanced data model is enabled": { + virtualMachinesEnabled: true, + enhancedDataModel: true, }, } - gatherer := gatherWithTime(ds, arbitraryNowFunc) - props, err := gatherer(ctx) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), strconv.FormatBool(tc.virtualMachinesEnabled)) + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), strconv.FormatBool(tc.enhancedDataModel)) - require.NoError(t, err) - require.NotNil(t, props) + ctx := sac.WithAllAccess(context.Background()) + ds := &mockDataStore{ + vms: []*storage.VirtualMachine{ + {Id: "vm1", ClusterId: "cluster1", Name: "test-vm", State: storage.VirtualMachine_RUNNING, Scan: createScanWithAge(1 * time.Hour)}, + }, + } - // When feature flag is disabled, should return empty map - // No database query should have been performed - assert.Empty(t, props, "Should return empty map when feature flag is disabled") + gatherer := gatherWithTime(ds, arbitraryNowFunc) + props, err := gatherer(ctx) + + require.NoError(t, err) + require.NotNil(t, props) + assert.Empty(t, props) + assert.False(t, ds.walked) + }) + } } // createScanWithAge creates a scan with a timestamp at the specified duration before arbitraryNow. @@ -216,8 +234,9 @@ func createScanWithAge(age time.Duration) *storage.VirtualMachineScan { } type mockDataStore struct { - vms []*storage.VirtualMachine - err error + vms []*storage.VirtualMachine + err error + walked bool } func (m *mockDataStore) CountVirtualMachines(ctx context.Context, query *v1.Query) (int, error) { @@ -252,6 +271,7 @@ func (m *mockDataStore) SearchRawVirtualMachines(ctx context.Context, query *v1. } func (m *mockDataStore) Walk(ctx context.Context, fn func(vm *storage.VirtualMachine) error) error { + m.walked = true if m.err != nil { return m.err } @@ -262,3 +282,190 @@ func (m *mockDataStore) Walk(ctx context.Context, fn func(vm *storage.VirtualMac } return nil } + +// --- V2 mock types --- + +type mockVMV2DataStore struct { + vms []*storage.VirtualMachineV2 + err error +} + +func (m *mockVMV2DataStore) Walk(_ context.Context, fn func(vm *storage.VirtualMachineV2) error) error { + if m.err != nil { + return m.err + } + for _, vm := range m.vms { + if err := fn(vm); err != nil { + return err + } + } + return nil +} + +type mockScanV2DataStore struct { + count int + err error + lastQuery *v1.Query +} + +func (m *mockScanV2DataStore) Count(_ context.Context, q *v1.Query) (int, error) { + m.lastQuery = q + return m.count, m.err +} + +func TestVirtualMachineTelemetryV2(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), "true") + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), "true") + + tests := map[string]struct { + vms []*storage.VirtualMachineV2 + scanCount int + expectedClustersWithRunningVMs int + expectedTotalVMs int + expectedVMsWithActiveAgents int + }{ + "should return zero for all metrics when no V2 VMs exist": { + vms: nil, + scanCount: 0, + expectedClustersWithRunningVMs: 0, + expectedTotalVMs: 0, + expectedVMsWithActiveAgents: 0, + }, + "should count single cluster with one running V2 VM and one recent scan": { + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "test-vm", State: storage.VirtualMachineV2_RUNNING}, + }, + scanCount: 1, + expectedClustersWithRunningVMs: 1, + expectedTotalVMs: 1, + expectedVMsWithActiveAgents: 1, + }, + "should count total V2 VMs including stopped ones": { + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "vm-1", State: storage.VirtualMachineV2_RUNNING}, + {Id: "vm2", ClusterId: "cluster1", Name: "vm-2", State: storage.VirtualMachineV2_STOPPED}, + {Id: "vm3", ClusterId: "cluster2", Name: "vm-3", State: storage.VirtualMachineV2_UNKNOWN}, + }, + scanCount: 0, + expectedClustersWithRunningVMs: 1, + expectedTotalVMs: 3, + expectedVMsWithActiveAgents: 0, + }, + "should count multiple distinct clusters with running V2 VMs": { + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "vm-1", State: storage.VirtualMachineV2_RUNNING}, + {Id: "vm2", ClusterId: "cluster2", Name: "vm-2", State: storage.VirtualMachineV2_RUNNING}, + {Id: "vm3", ClusterId: "cluster3", Name: "vm-3", State: storage.VirtualMachineV2_RUNNING}, + }, + scanCount: 2, + expectedClustersWithRunningVMs: 3, + expectedTotalVMs: 3, + expectedVMsWithActiveAgents: 2, + }, + "should exclude V2 VMs with empty cluster id from cluster count": { + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "vm-1", State: storage.VirtualMachineV2_RUNNING}, + {Id: "vm2", ClusterId: "", Name: "vm-orphan", State: storage.VirtualMachineV2_RUNNING}, + }, + scanCount: 1, + expectedClustersWithRunningVMs: 1, + expectedTotalVMs: 2, + expectedVMsWithActiveAgents: 1, + }, + "should handle V2 VM with no scan row yet (never scanned)": { + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "vm-1", State: storage.VirtualMachineV2_RUNNING}, + {Id: "vm2", ClusterId: "cluster1", Name: "vm-2", State: storage.VirtualMachineV2_RUNNING}, + }, + scanCount: 0, + expectedClustersWithRunningVMs: 1, + expectedTotalVMs: 2, + expectedVMsWithActiveAgents: 0, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := sac.WithAllAccess(context.Background()) + + vmV2DS := &mockVMV2DataStore{vms: tc.vms} + scanV2DS := &mockScanV2DataStore{count: tc.scanCount} + + gatherer := gatherV2WithTime(vmV2DS, scanV2DS, arbitraryNowFunc) + props, err := gatherer(ctx) + + require.NoError(t, err) + require.NotNil(t, props) + assert.Equal(t, tc.expectedClustersWithRunningVMs, props[metricClustersWithVMs]) + assert.Equal(t, tc.expectedTotalVMs, props[metricTotalVMs]) + assert.Equal(t, tc.expectedVMsWithActiveAgents, props[metricVMsWithActiveAgents]) + assertActiveAgentScanQuery(t, scanV2DS.lastQuery) + }) + } +} + +func TestVirtualMachineTelemetryV2Errors(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), "true") + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), "true") + + walkErr := errors.New("walk failed") + countErr := errors.New("count failed") + + tests := map[string]struct { + walkErr error + countErr error + wantErr error + }{ + "should return Walk error and nil props": { + walkErr: walkErr, + wantErr: walkErr, + }, + "should return Count error and nil props": { + countErr: countErr, + wantErr: countErr, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := sac.WithAllAccess(context.Background()) + vmV2DS := &mockVMV2DataStore{ + vms: []*storage.VirtualMachineV2{ + {Id: "vm1", ClusterId: "cluster1", Name: "vm-1", State: storage.VirtualMachineV2_RUNNING}, + }, + err: tc.walkErr, + } + scanV2DS := &mockScanV2DataStore{count: 1, err: tc.countErr} + + props, err := gatherV2WithTime(vmV2DS, scanV2DS, arbitraryNowFunc)(ctx) + + require.ErrorIs(t, err, tc.wantErr) + assert.Nil(t, props) + if tc.walkErr != nil { + assert.Nil(t, scanV2DS.lastQuery) + return + } + assertActiveAgentScanQuery(t, scanV2DS.lastQuery) + }) + } +} + +func assertActiveAgentScanQuery(t *testing.T, q *v1.Query) { + t.Helper() + require.NotNil(t, q) + mfq := q.GetBaseQuery().GetMatchFieldQuery() + require.NotNil(t, mfq) + assert.Equal(t, search.VirtualMachineScanTime.String(), mfq.GetField()) + + value, ok := strings.CutPrefix(mfq.GetValue(), search.TimeRangePrefix) + require.True(t, ok) + fromStr, toStr, ok := strings.Cut(value, "-") + require.True(t, ok) + from, err := strconv.ParseInt(fromStr, 10, 64) + require.NoError(t, err) + to, err := strconv.ParseInt(toStr, 10, 64) + require.NoError(t, err) + + assert.Equal(t, arbitraryNow.Add(-activeVMAgentMaxAgeLimitTelemetry).UnixMilli(), from) + assert.Equal(t, timestamp.InfiniteFuture.GoTime().UnixMilli(), to) +} From 2e1ca7e1d1df3b8ab9ae304489137dac06d7a34e Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:50:16 -0500 Subject: [PATCH 043/638] ROX-35509: Create and Populate Vuln Origin (#22335) Co-authored-by: StackRox PR Fixxxer --- .../cve/converter/utils/convert_utils_v2.go | 2 + .../converter/utils/convert_utils_v2_test.go | 2 + central/graphql/resolvers/generated.go | 19 + generated/api/v1/image_service.swagger.json | 22 +- generated/api/v1/node_service.swagger.json | 22 +- .../api/v1/vuln_mgmt_service.swagger.json | 22 +- generated/storage/cve.pb.go | 388 +++++++++++------- generated/storage/cve_vtproto.pb.go | 52 +++ generated/storage/vulnerability.pb.go | 44 +- generated/storage/vulnerability_vtproto.pb.go | 52 +++ pkg/scanners/scannerv4/convert.go | 39 ++ pkg/scanners/scannerv4/convert_test.go | 35 ++ proto/storage/cve.proto | 15 + proto/storage/proto.lock | 54 +++ proto/storage/vulnerability.proto | 4 +- 15 files changed, 605 insertions(+), 167 deletions(-) diff --git a/central/cve/converter/utils/convert_utils_v2.go b/central/cve/converter/utils/convert_utils_v2.go index 432f6a8131c25..9a2b918bf11df 100644 --- a/central/cve/converter/utils/convert_utils_v2.go +++ b/central/cve/converter/utils/convert_utils_v2.go @@ -36,6 +36,7 @@ func ImageCVEV2ToEmbeddedVulnerability(vuln *storage.ImageCVEV2) *storage.Embedd VulnerabilityTypes: []storage.EmbeddedVulnerability_VulnerabilityType{storage.EmbeddedVulnerability_IMAGE_VULNERABILITY}, State: vuln.GetState(), Datasource: vuln.GetDatasource(), + Origin: vuln.GetOrigin(), } if vuln.GetIsFixable() { @@ -111,6 +112,7 @@ func EmbeddedVulnerabilityToImageCVEV2(imageID string, componentID string, index ImpactScore: impactScore, Advisory: from.GetAdvisory(), Datasource: from.GetDatasource(), + Origin: from.GetOrigin(), } if !features.FlattenImageData.Enabled() { ret.ImageId = imageID diff --git a/central/cve/converter/utils/convert_utils_v2_test.go b/central/cve/converter/utils/convert_utils_v2_test.go index ef2188bdb06fc..d4cdc4c5b4348 100644 --- a/central/cve/converter/utils/convert_utils_v2_test.go +++ b/central/cve/converter/utils/convert_utils_v2_test.go @@ -101,6 +101,7 @@ var ( }, CisaKev: true, Datasource: "test-ds", + Origin: storage.VulnOrigin_VULN_ORIGIN_RED_HAT, }, { Cve: "cve2", @@ -244,6 +245,7 @@ func getTestCVEs(t *testing.T) []*storage.ImageCVEV2 { HasFixedBy: nil, ComponentId: getTestComponentID(0), Datasource: "test-ds", + Origin: storage.VulnOrigin_VULN_ORIGIN_RED_HAT, } cve2 := &storage.ImageCVEV2{ diff --git a/central/graphql/resolvers/generated.go b/central/graphql/resolvers/generated.go index e567571423f44..ad8f17ae48311 100644 --- a/central/graphql/resolvers/generated.go +++ b/central/graphql/resolvers/generated.go @@ -1575,6 +1575,7 @@ func registerGeneratedTypes(builder generator.SchemaBuilder) { "type: String!", })) generator.RegisterProtoEnum(builder, reflect.TypeOf(storage.Volume_MountPropagation(0))) + generator.RegisterProtoEnum(builder, reflect.TypeOf(storage.VulnOrigin(0))) utils.Must(builder.AddInput("VulnReqGlobalScope", []string{ "images: VulnReqImageScope", })) @@ -16916,6 +16917,24 @@ func toVolume_MountPropagations(values *[]string) []storage.Volume_MountPropagat return output } +func toVulnOrigin(value *string) storage.VulnOrigin { + if value != nil { + return storage.VulnOrigin(storage.VulnOrigin_value[*value]) + } + return storage.VulnOrigin(0) +} + +func toVulnOrigins(values *[]string) []storage.VulnOrigin { + if values == nil { + return nil + } + output := make([]storage.VulnOrigin, len(*values)) + for i, v := range *values { + output[i] = toVulnOrigin(&v) + } + return output +} + type vulnerabilityRequest_CVEsResolver struct { ctx context.Context root *Resolver diff --git a/generated/api/v1/image_service.swagger.json b/generated/api/v1/image_service.swagger.json index 57c003b404037..51036b8b504f4 100644 --- a/generated/api/v1/image_service.swagger.json +++ b/generated/api/v1/image_service.swagger.json @@ -1040,9 +1040,13 @@ }, "cisaKev": { "type": "boolean" + }, + "origin": { + "$ref": "#/definitions/storageVulnOrigin", + "description": "origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV)." } }, - "title": "Next Tag: 29" + "title": "Next Tag: 30" }, "storageExploit": { "type": "object", @@ -1483,6 +1487,22 @@ } } }, + "storageVulnOrigin": { + "type": "string", + "enum": [ + "VULN_ORIGIN_OTHER", + "VULN_ORIGIN_OSV", + "VULN_ORIGIN_ALPINE", + "VULN_ORIGIN_AMAZON", + "VULN_ORIGIN_DEBIAN", + "VULN_ORIGIN_ORACLE", + "VULN_ORIGIN_PHOTON", + "VULN_ORIGIN_RED_HAT", + "VULN_ORIGIN_SUSE", + "VULN_ORIGIN_UBUNTU" + ], + "default": "VULN_ORIGIN_OTHER" + }, "storageVulnerabilitySeverity": { "type": "string", "enum": [ diff --git a/generated/api/v1/node_service.swagger.json b/generated/api/v1/node_service.swagger.json index 4f5fdaed4985e..a89070cc04e43 100644 --- a/generated/api/v1/node_service.swagger.json +++ b/generated/api/v1/node_service.swagger.json @@ -670,9 +670,13 @@ }, "cisaKev": { "type": "boolean" + }, + "origin": { + "$ref": "#/definitions/storageVulnOrigin", + "description": "origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV)." } }, - "title": "Next Tag: 29" + "title": "Next Tag: 30" }, "storageEmbeddedVulnerabilityScoreVersion": { "type": "string", @@ -941,6 +945,22 @@ ], "default": "UNKNOWN_TAINT_EFFECT" }, + "storageVulnOrigin": { + "type": "string", + "enum": [ + "VULN_ORIGIN_OTHER", + "VULN_ORIGIN_OSV", + "VULN_ORIGIN_ALPINE", + "VULN_ORIGIN_AMAZON", + "VULN_ORIGIN_DEBIAN", + "VULN_ORIGIN_ORACLE", + "VULN_ORIGIN_PHOTON", + "VULN_ORIGIN_RED_HAT", + "VULN_ORIGIN_SUSE", + "VULN_ORIGIN_UBUNTU" + ], + "default": "VULN_ORIGIN_OTHER" + }, "storageVulnerabilitySeverity": { "type": "string", "enum": [ diff --git a/generated/api/v1/vuln_mgmt_service.swagger.json b/generated/api/v1/vuln_mgmt_service.swagger.json index d54d36b3fd31a..f35a4d8d751fa 100644 --- a/generated/api/v1/vuln_mgmt_service.swagger.json +++ b/generated/api/v1/vuln_mgmt_service.swagger.json @@ -1025,9 +1025,13 @@ }, "cisaKev": { "type": "boolean" + }, + "origin": { + "$ref": "#/definitions/storageVulnOrigin", + "description": "origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV)." } }, - "title": "Next Tag: 29" + "title": "Next Tag: 30" }, "storageExploit": { "type": "object", @@ -1636,6 +1640,22 @@ } } }, + "storageVulnOrigin": { + "type": "string", + "enum": [ + "VULN_ORIGIN_OTHER", + "VULN_ORIGIN_OSV", + "VULN_ORIGIN_ALPINE", + "VULN_ORIGIN_AMAZON", + "VULN_ORIGIN_DEBIAN", + "VULN_ORIGIN_ORACLE", + "VULN_ORIGIN_PHOTON", + "VULN_ORIGIN_RED_HAT", + "VULN_ORIGIN_SUSE", + "VULN_ORIGIN_UBUNTU" + ], + "default": "VULN_ORIGIN_OTHER" + }, "storageVulnerabilitySeverity": { "type": "string", "enum": [ diff --git a/generated/storage/cve.pb.go b/generated/storage/cve.pb.go index 5c6d7a8540e32..189e39561f71e 100644 --- a/generated/storage/cve.pb.go +++ b/generated/storage/cve.pb.go @@ -179,6 +179,76 @@ func (Source) EnumDescriptor() ([]byte, []int) { return file_storage_cve_proto_rawDescGZIP(), []int{2} } +type VulnOrigin int32 + +const ( + VulnOrigin_VULN_ORIGIN_OTHER VulnOrigin = 0 + VulnOrigin_VULN_ORIGIN_OSV VulnOrigin = 1 + VulnOrigin_VULN_ORIGIN_ALPINE VulnOrigin = 2 + VulnOrigin_VULN_ORIGIN_AMAZON VulnOrigin = 3 + VulnOrigin_VULN_ORIGIN_DEBIAN VulnOrigin = 4 + VulnOrigin_VULN_ORIGIN_ORACLE VulnOrigin = 5 + VulnOrigin_VULN_ORIGIN_PHOTON VulnOrigin = 6 + VulnOrigin_VULN_ORIGIN_RED_HAT VulnOrigin = 7 + VulnOrigin_VULN_ORIGIN_SUSE VulnOrigin = 8 + VulnOrigin_VULN_ORIGIN_UBUNTU VulnOrigin = 9 +) + +// Enum value maps for VulnOrigin. +var ( + VulnOrigin_name = map[int32]string{ + 0: "VULN_ORIGIN_OTHER", + 1: "VULN_ORIGIN_OSV", + 2: "VULN_ORIGIN_ALPINE", + 3: "VULN_ORIGIN_AMAZON", + 4: "VULN_ORIGIN_DEBIAN", + 5: "VULN_ORIGIN_ORACLE", + 6: "VULN_ORIGIN_PHOTON", + 7: "VULN_ORIGIN_RED_HAT", + 8: "VULN_ORIGIN_SUSE", + 9: "VULN_ORIGIN_UBUNTU", + } + VulnOrigin_value = map[string]int32{ + "VULN_ORIGIN_OTHER": 0, + "VULN_ORIGIN_OSV": 1, + "VULN_ORIGIN_ALPINE": 2, + "VULN_ORIGIN_AMAZON": 3, + "VULN_ORIGIN_DEBIAN": 4, + "VULN_ORIGIN_ORACLE": 5, + "VULN_ORIGIN_PHOTON": 6, + "VULN_ORIGIN_RED_HAT": 7, + "VULN_ORIGIN_SUSE": 8, + "VULN_ORIGIN_UBUNTU": 9, + } +) + +func (x VulnOrigin) Enum() *VulnOrigin { + p := new(VulnOrigin) + *p = x + return p +} + +func (x VulnOrigin) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (VulnOrigin) Descriptor() protoreflect.EnumDescriptor { + return file_storage_cve_proto_enumTypes[3].Descriptor() +} + +func (VulnOrigin) Type() protoreflect.EnumType { + return &file_storage_cve_proto_enumTypes[3] +} + +func (x VulnOrigin) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use VulnOrigin.Descriptor instead. +func (VulnOrigin) EnumDescriptor() ([]byte, []int) { + return file_storage_cve_proto_rawDescGZIP(), []int{3} +} + // Added this score version for nvd cvss score. moving out and re using score version from cveinfo will cause backward compatibility issue type CvssScoreVersion int32 @@ -213,11 +283,11 @@ func (x CvssScoreVersion) String() string { } func (CvssScoreVersion) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[3].Descriptor() + return file_storage_cve_proto_enumTypes[4].Descriptor() } func (CvssScoreVersion) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[3] + return &file_storage_cve_proto_enumTypes[4] } func (x CvssScoreVersion) Number() protoreflect.EnumNumber { @@ -226,7 +296,7 @@ func (x CvssScoreVersion) Number() protoreflect.EnumNumber { // Deprecated: Use CvssScoreVersion.Descriptor instead. func (CvssScoreVersion) EnumDescriptor() ([]byte, []int) { - return file_storage_cve_proto_rawDescGZIP(), []int{3} + return file_storage_cve_proto_rawDescGZIP(), []int{4} } type CVE_CVEType int32 @@ -271,11 +341,11 @@ func (x CVE_CVEType) String() string { } func (CVE_CVEType) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[4].Descriptor() + return file_storage_cve_proto_enumTypes[5].Descriptor() } func (CVE_CVEType) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[4] + return &file_storage_cve_proto_enumTypes[5] } func (x CVE_CVEType) Number() protoreflect.EnumNumber { @@ -320,11 +390,11 @@ func (x CVE_ScoreVersion) String() string { } func (CVE_ScoreVersion) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[5].Descriptor() + return file_storage_cve_proto_enumTypes[6].Descriptor() } func (CVE_ScoreVersion) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[5] + return &file_storage_cve_proto_enumTypes[6] } func (x CVE_ScoreVersion) Number() protoreflect.EnumNumber { @@ -370,11 +440,11 @@ func (x CVEInfo_ScoreVersion) String() string { } func (CVEInfo_ScoreVersion) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[6].Descriptor() + return file_storage_cve_proto_enumTypes[7].Descriptor() } func (CVEInfo_ScoreVersion) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[6] + return &file_storage_cve_proto_enumTypes[7] } func (x CVEInfo_ScoreVersion) Number() protoreflect.EnumNumber { @@ -419,11 +489,11 @@ func (x CVSSV2_Impact) String() string { } func (CVSSV2_Impact) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[7].Descriptor() + return file_storage_cve_proto_enumTypes[8].Descriptor() } func (CVSSV2_Impact) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[7] + return &file_storage_cve_proto_enumTypes[8] } func (x CVSSV2_Impact) Number() protoreflect.EnumNumber { @@ -468,11 +538,11 @@ func (x CVSSV2_AttackVector) String() string { } func (CVSSV2_AttackVector) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[8].Descriptor() + return file_storage_cve_proto_enumTypes[9].Descriptor() } func (CVSSV2_AttackVector) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[8] + return &file_storage_cve_proto_enumTypes[9] } func (x CVSSV2_AttackVector) Number() protoreflect.EnumNumber { @@ -517,11 +587,11 @@ func (x CVSSV2_AccessComplexity) String() string { } func (CVSSV2_AccessComplexity) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[9].Descriptor() + return file_storage_cve_proto_enumTypes[10].Descriptor() } func (CVSSV2_AccessComplexity) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[9] + return &file_storage_cve_proto_enumTypes[10] } func (x CVSSV2_AccessComplexity) Number() protoreflect.EnumNumber { @@ -566,11 +636,11 @@ func (x CVSSV2_Authentication) String() string { } func (CVSSV2_Authentication) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[10].Descriptor() + return file_storage_cve_proto_enumTypes[11].Descriptor() } func (CVSSV2_Authentication) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[10] + return &file_storage_cve_proto_enumTypes[11] } func (x CVSSV2_Authentication) Number() protoreflect.EnumNumber { @@ -618,11 +688,11 @@ func (x CVSSV2_Severity) String() string { } func (CVSSV2_Severity) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[11].Descriptor() + return file_storage_cve_proto_enumTypes[12].Descriptor() } func (CVSSV2_Severity) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[11] + return &file_storage_cve_proto_enumTypes[12] } func (x CVSSV2_Severity) Number() protoreflect.EnumNumber { @@ -667,11 +737,11 @@ func (x CVSSV3_Impact) String() string { } func (CVSSV3_Impact) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[12].Descriptor() + return file_storage_cve_proto_enumTypes[13].Descriptor() } func (CVSSV3_Impact) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[12] + return &file_storage_cve_proto_enumTypes[13] } func (x CVSSV3_Impact) Number() protoreflect.EnumNumber { @@ -719,11 +789,11 @@ func (x CVSSV3_AttackVector) String() string { } func (CVSSV3_AttackVector) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[13].Descriptor() + return file_storage_cve_proto_enumTypes[14].Descriptor() } func (CVSSV3_AttackVector) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[13] + return &file_storage_cve_proto_enumTypes[14] } func (x CVSSV3_AttackVector) Number() protoreflect.EnumNumber { @@ -765,11 +835,11 @@ func (x CVSSV3_Complexity) String() string { } func (CVSSV3_Complexity) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[14].Descriptor() + return file_storage_cve_proto_enumTypes[15].Descriptor() } func (CVSSV3_Complexity) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[14] + return &file_storage_cve_proto_enumTypes[15] } func (x CVSSV3_Complexity) Number() protoreflect.EnumNumber { @@ -814,11 +884,11 @@ func (x CVSSV3_Privileges) String() string { } func (CVSSV3_Privileges) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[15].Descriptor() + return file_storage_cve_proto_enumTypes[16].Descriptor() } func (CVSSV3_Privileges) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[15] + return &file_storage_cve_proto_enumTypes[16] } func (x CVSSV3_Privileges) Number() protoreflect.EnumNumber { @@ -860,11 +930,11 @@ func (x CVSSV3_UserInteraction) String() string { } func (CVSSV3_UserInteraction) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[16].Descriptor() + return file_storage_cve_proto_enumTypes[17].Descriptor() } func (CVSSV3_UserInteraction) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[16] + return &file_storage_cve_proto_enumTypes[17] } func (x CVSSV3_UserInteraction) Number() protoreflect.EnumNumber { @@ -906,11 +976,11 @@ func (x CVSSV3_Scope) String() string { } func (CVSSV3_Scope) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[17].Descriptor() + return file_storage_cve_proto_enumTypes[18].Descriptor() } func (CVSSV3_Scope) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[17] + return &file_storage_cve_proto_enumTypes[18] } func (x CVSSV3_Scope) Number() protoreflect.EnumNumber { @@ -964,11 +1034,11 @@ func (x CVSSV3_Severity) String() string { } func (CVSSV3_Severity) Descriptor() protoreflect.EnumDescriptor { - return file_storage_cve_proto_enumTypes[18].Descriptor() + return file_storage_cve_proto_enumTypes[19].Descriptor() } func (CVSSV3_Severity) Type() protoreflect.EnumType { - return &file_storage_cve_proto_enumTypes[18] + return &file_storage_cve_proto_enumTypes[19] } func (x CVSSV3_Severity) Number() protoreflect.EnumNumber { @@ -1684,8 +1754,10 @@ type ImageCVEV2 struct { // Timestamp when the fix for this CVE was made available according to the sources. FixAvailableTimestamp *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=fix_available_timestamp,json=fixAvailableTimestamp,proto3" json:"fix_available_timestamp,omitempty" search:"CVE Fix Available Timestamp,hidden"` // @gotags: search:"CVE Fix Available Timestamp,hidden" Datasource string `protobuf:"bytes,17,opt,name=datasource,proto3" json:"datasource,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). + Origin VulnOrigin `protobuf:"varint,18,opt,name=origin,proto3,enum=storage.VulnOrigin" json:"origin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ImageCVEV2) Reset() { @@ -1847,6 +1919,13 @@ func (x *ImageCVEV2) GetDatasource() string { return "" } +func (x *ImageCVEV2) GetOrigin() VulnOrigin { + if x != nil { + return x.Origin + } + return VulnOrigin_VULN_ORIGIN_OTHER +} + type isImageCVEV2_HasFixedBy interface { isImageCVEV2_HasFixedBy() } @@ -2807,7 +2886,7 @@ const file_storage_cve_proto_rawDesc = "" + "\anvdcvss\x18\n" + " \x01(\x02R\anvdcvss\x125\n" + "\fcvss_metrics\x18\v \x03(\v2\x12.storage.CVSSScoreR\vcvssMetrics\x12E\n" + - "\x11nvd_score_version\x18\f \x01(\x0e2\x19.storage.CvssScoreVersionR\x0fnvdScoreVersion:\x02\x18\x01\"\xfc\x05\n" + + "\x11nvd_score_version\x18\f \x01(\x0e2\x19.storage.CvssScoreVersionR\x0fnvdScoreVersion:\x02\x18\x01\"\xa9\x06\n" + "\n" + "ImageCVEV2\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + @@ -2830,7 +2909,8 @@ const file_storage_cve_proto_rawDesc = "" + "\x17fix_available_timestamp\x18\x10 \x01(\v2\x1a.google.protobuf.TimestampR\x15fixAvailableTimestamp\x12\x1e\n" + "\n" + "datasource\x18\x11 \x01(\tR\n" + - "datasourceB\x0e\n" + + "datasource\x12+\n" + + "\x06origin\x18\x12 \x01(\x0e2\x13.storage.VulnOriginR\x06originB\x0e\n" + "\fhas_fixed_by\"\xe4\x03\n" + "\aNodeCVE\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x124\n" + @@ -2968,7 +3048,19 @@ const file_storage_cve_proto_rawDesc = "" + "\n" + "SOURCE_OSV\x10\x02\x12\x0e\n" + "\n" + - "SOURCE_NVD\x10\x03*7\n" + + "SOURCE_NVD\x10\x03*\xf7\x01\n" + + "\n" + + "VulnOrigin\x12\x15\n" + + "\x11VULN_ORIGIN_OTHER\x10\x00\x12\x13\n" + + "\x0fVULN_ORIGIN_OSV\x10\x01\x12\x16\n" + + "\x12VULN_ORIGIN_ALPINE\x10\x02\x12\x16\n" + + "\x12VULN_ORIGIN_AMAZON\x10\x03\x12\x16\n" + + "\x12VULN_ORIGIN_DEBIAN\x10\x04\x12\x16\n" + + "\x12VULN_ORIGIN_ORACLE\x10\x05\x12\x16\n" + + "\x12VULN_ORIGIN_PHOTON\x10\x06\x12\x17\n" + + "\x13VULN_ORIGIN_RED_HAT\x10\a\x12\x14\n" + + "\x10VULN_ORIGIN_SUSE\x10\b\x12\x16\n" + + "\x12VULN_ORIGIN_UBUNTU\x10\t*7\n" + "\x10CvssScoreVersion\x12\x13\n" + "\x0fUNKNOWN_VERSION\x10\x00\x12\x06\n" + "\x02V2\x10\x01\x12\x06\n" + @@ -2987,125 +3079,127 @@ func file_storage_cve_proto_rawDescGZIP() []byte { return file_storage_cve_proto_rawDescData } -var file_storage_cve_proto_enumTypes = make([]protoimpl.EnumInfo, 19) +var file_storage_cve_proto_enumTypes = make([]protoimpl.EnumInfo, 20) var file_storage_cve_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_storage_cve_proto_goTypes = []any{ (VulnerabilityState)(0), // 0: storage.VulnerabilityState (VulnerabilitySeverity)(0), // 1: storage.VulnerabilitySeverity (Source)(0), // 2: storage.Source - (CvssScoreVersion)(0), // 3: storage.CvssScoreVersion - (CVE_CVEType)(0), // 4: storage.CVE.CVEType - (CVE_ScoreVersion)(0), // 5: storage.CVE.ScoreVersion - (CVEInfo_ScoreVersion)(0), // 6: storage.CVEInfo.ScoreVersion - (CVSSV2_Impact)(0), // 7: storage.CVSSV2.Impact - (CVSSV2_AttackVector)(0), // 8: storage.CVSSV2.AttackVector - (CVSSV2_AccessComplexity)(0), // 9: storage.CVSSV2.AccessComplexity - (CVSSV2_Authentication)(0), // 10: storage.CVSSV2.Authentication - (CVSSV2_Severity)(0), // 11: storage.CVSSV2.Severity - (CVSSV3_Impact)(0), // 12: storage.CVSSV3.Impact - (CVSSV3_AttackVector)(0), // 13: storage.CVSSV3.AttackVector - (CVSSV3_Complexity)(0), // 14: storage.CVSSV3.Complexity - (CVSSV3_Privileges)(0), // 15: storage.CVSSV3.Privileges - (CVSSV3_UserInteraction)(0), // 16: storage.CVSSV3.UserInteraction - (CVSSV3_Scope)(0), // 17: storage.CVSSV3.Scope - (CVSSV3_Severity)(0), // 18: storage.CVSSV3.Severity - (*EPSS)(nil), // 19: storage.EPSS - (*Exploit)(nil), // 20: storage.Exploit - (*CVE)(nil), // 21: storage.CVE - (*CVEInfo)(nil), // 22: storage.CVEInfo - (*Advisory)(nil), // 23: storage.Advisory - (*ImageCVE)(nil), // 24: storage.ImageCVE - (*ImageCVEV2)(nil), // 25: storage.ImageCVEV2 - (*NodeCVE)(nil), // 26: storage.NodeCVE - (*ClusterCVE)(nil), // 27: storage.ClusterCVE - (*CVSSScore)(nil), // 28: storage.CVSSScore - (*CVSSV2)(nil), // 29: storage.CVSSV2 - (*CVSSV3)(nil), // 30: storage.CVSSV3 - (*ImageCVEInfo)(nil), // 31: storage.ImageCVEInfo - (*CVE_DistroSpecific)(nil), // 32: storage.CVE.DistroSpecific - (*CVE_Reference)(nil), // 33: storage.CVE.Reference - nil, // 34: storage.CVE.DistroSpecificsEntry - (*CVEInfo_Reference)(nil), // 35: storage.CVEInfo.Reference - (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp + (VulnOrigin)(0), // 3: storage.VulnOrigin + (CvssScoreVersion)(0), // 4: storage.CvssScoreVersion + (CVE_CVEType)(0), // 5: storage.CVE.CVEType + (CVE_ScoreVersion)(0), // 6: storage.CVE.ScoreVersion + (CVEInfo_ScoreVersion)(0), // 7: storage.CVEInfo.ScoreVersion + (CVSSV2_Impact)(0), // 8: storage.CVSSV2.Impact + (CVSSV2_AttackVector)(0), // 9: storage.CVSSV2.AttackVector + (CVSSV2_AccessComplexity)(0), // 10: storage.CVSSV2.AccessComplexity + (CVSSV2_Authentication)(0), // 11: storage.CVSSV2.Authentication + (CVSSV2_Severity)(0), // 12: storage.CVSSV2.Severity + (CVSSV3_Impact)(0), // 13: storage.CVSSV3.Impact + (CVSSV3_AttackVector)(0), // 14: storage.CVSSV3.AttackVector + (CVSSV3_Complexity)(0), // 15: storage.CVSSV3.Complexity + (CVSSV3_Privileges)(0), // 16: storage.CVSSV3.Privileges + (CVSSV3_UserInteraction)(0), // 17: storage.CVSSV3.UserInteraction + (CVSSV3_Scope)(0), // 18: storage.CVSSV3.Scope + (CVSSV3_Severity)(0), // 19: storage.CVSSV3.Severity + (*EPSS)(nil), // 20: storage.EPSS + (*Exploit)(nil), // 21: storage.Exploit + (*CVE)(nil), // 22: storage.CVE + (*CVEInfo)(nil), // 23: storage.CVEInfo + (*Advisory)(nil), // 24: storage.Advisory + (*ImageCVE)(nil), // 25: storage.ImageCVE + (*ImageCVEV2)(nil), // 26: storage.ImageCVEV2 + (*NodeCVE)(nil), // 27: storage.NodeCVE + (*ClusterCVE)(nil), // 28: storage.ClusterCVE + (*CVSSScore)(nil), // 29: storage.CVSSScore + (*CVSSV2)(nil), // 30: storage.CVSSV2 + (*CVSSV3)(nil), // 31: storage.CVSSV3 + (*ImageCVEInfo)(nil), // 32: storage.ImageCVEInfo + (*CVE_DistroSpecific)(nil), // 33: storage.CVE.DistroSpecific + (*CVE_Reference)(nil), // 34: storage.CVE.Reference + nil, // 35: storage.CVE.DistroSpecificsEntry + (*CVEInfo_Reference)(nil), // 36: storage.CVEInfo.Reference + (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp } var file_storage_cve_proto_depIdxs = []int32{ - 4, // 0: storage.CVE.type:type_name -> storage.CVE.CVEType - 4, // 1: storage.CVE.types:type_name -> storage.CVE.CVEType - 36, // 2: storage.CVE.published_on:type_name -> google.protobuf.Timestamp - 36, // 3: storage.CVE.created_at:type_name -> google.protobuf.Timestamp - 36, // 4: storage.CVE.last_modified:type_name -> google.protobuf.Timestamp - 33, // 5: storage.CVE.references:type_name -> storage.CVE.Reference - 5, // 6: storage.CVE.score_version:type_name -> storage.CVE.ScoreVersion - 29, // 7: storage.CVE.cvss_v2:type_name -> storage.CVSSV2 - 30, // 8: storage.CVE.cvss_v3:type_name -> storage.CVSSV3 - 36, // 9: storage.CVE.suppress_activation:type_name -> google.protobuf.Timestamp - 36, // 10: storage.CVE.suppress_expiry:type_name -> google.protobuf.Timestamp - 34, // 11: storage.CVE.distro_specifics:type_name -> storage.CVE.DistroSpecificsEntry + 5, // 0: storage.CVE.type:type_name -> storage.CVE.CVEType + 5, // 1: storage.CVE.types:type_name -> storage.CVE.CVEType + 37, // 2: storage.CVE.published_on:type_name -> google.protobuf.Timestamp + 37, // 3: storage.CVE.created_at:type_name -> google.protobuf.Timestamp + 37, // 4: storage.CVE.last_modified:type_name -> google.protobuf.Timestamp + 34, // 5: storage.CVE.references:type_name -> storage.CVE.Reference + 6, // 6: storage.CVE.score_version:type_name -> storage.CVE.ScoreVersion + 30, // 7: storage.CVE.cvss_v2:type_name -> storage.CVSSV2 + 31, // 8: storage.CVE.cvss_v3:type_name -> storage.CVSSV3 + 37, // 9: storage.CVE.suppress_activation:type_name -> google.protobuf.Timestamp + 37, // 10: storage.CVE.suppress_expiry:type_name -> google.protobuf.Timestamp + 35, // 11: storage.CVE.distro_specifics:type_name -> storage.CVE.DistroSpecificsEntry 1, // 12: storage.CVE.severity:type_name -> storage.VulnerabilitySeverity - 36, // 13: storage.CVEInfo.published_on:type_name -> google.protobuf.Timestamp - 36, // 14: storage.CVEInfo.created_at:type_name -> google.protobuf.Timestamp - 36, // 15: storage.CVEInfo.last_modified:type_name -> google.protobuf.Timestamp - 6, // 16: storage.CVEInfo.score_version:type_name -> storage.CVEInfo.ScoreVersion - 29, // 17: storage.CVEInfo.cvss_v2:type_name -> storage.CVSSV2 - 30, // 18: storage.CVEInfo.cvss_v3:type_name -> storage.CVSSV3 - 35, // 19: storage.CVEInfo.references:type_name -> storage.CVEInfo.Reference - 28, // 20: storage.CVEInfo.cvss_metrics:type_name -> storage.CVSSScore - 19, // 21: storage.CVEInfo.epss:type_name -> storage.EPSS - 20, // 22: storage.CVEInfo.exploit:type_name -> storage.Exploit - 22, // 23: storage.ImageCVE.cve_base_info:type_name -> storage.CVEInfo + 37, // 13: storage.CVEInfo.published_on:type_name -> google.protobuf.Timestamp + 37, // 14: storage.CVEInfo.created_at:type_name -> google.protobuf.Timestamp + 37, // 15: storage.CVEInfo.last_modified:type_name -> google.protobuf.Timestamp + 7, // 16: storage.CVEInfo.score_version:type_name -> storage.CVEInfo.ScoreVersion + 30, // 17: storage.CVEInfo.cvss_v2:type_name -> storage.CVSSV2 + 31, // 18: storage.CVEInfo.cvss_v3:type_name -> storage.CVSSV3 + 36, // 19: storage.CVEInfo.references:type_name -> storage.CVEInfo.Reference + 29, // 20: storage.CVEInfo.cvss_metrics:type_name -> storage.CVSSScore + 20, // 21: storage.CVEInfo.epss:type_name -> storage.EPSS + 21, // 22: storage.CVEInfo.exploit:type_name -> storage.Exploit + 23, // 23: storage.ImageCVE.cve_base_info:type_name -> storage.CVEInfo 1, // 24: storage.ImageCVE.severity:type_name -> storage.VulnerabilitySeverity - 36, // 25: storage.ImageCVE.snooze_start:type_name -> google.protobuf.Timestamp - 36, // 26: storage.ImageCVE.snooze_expiry:type_name -> google.protobuf.Timestamp - 28, // 27: storage.ImageCVE.cvss_metrics:type_name -> storage.CVSSScore - 3, // 28: storage.ImageCVE.nvd_score_version:type_name -> storage.CvssScoreVersion - 22, // 29: storage.ImageCVEV2.cve_base_info:type_name -> storage.CVEInfo + 37, // 25: storage.ImageCVE.snooze_start:type_name -> google.protobuf.Timestamp + 37, // 26: storage.ImageCVE.snooze_expiry:type_name -> google.protobuf.Timestamp + 29, // 27: storage.ImageCVE.cvss_metrics:type_name -> storage.CVSSScore + 4, // 28: storage.ImageCVE.nvd_score_version:type_name -> storage.CvssScoreVersion + 23, // 29: storage.ImageCVEV2.cve_base_info:type_name -> storage.CVEInfo 1, // 30: storage.ImageCVEV2.severity:type_name -> storage.VulnerabilitySeverity - 3, // 31: storage.ImageCVEV2.nvd_score_version:type_name -> storage.CvssScoreVersion - 36, // 32: storage.ImageCVEV2.first_image_occurrence:type_name -> google.protobuf.Timestamp + 4, // 31: storage.ImageCVEV2.nvd_score_version:type_name -> storage.CvssScoreVersion + 37, // 32: storage.ImageCVEV2.first_image_occurrence:type_name -> google.protobuf.Timestamp 0, // 33: storage.ImageCVEV2.state:type_name -> storage.VulnerabilityState - 23, // 34: storage.ImageCVEV2.advisory:type_name -> storage.Advisory - 36, // 35: storage.ImageCVEV2.fix_available_timestamp:type_name -> google.protobuf.Timestamp - 22, // 36: storage.NodeCVE.cve_base_info:type_name -> storage.CVEInfo - 1, // 37: storage.NodeCVE.severity:type_name -> storage.VulnerabilitySeverity - 36, // 38: storage.NodeCVE.snooze_start:type_name -> google.protobuf.Timestamp - 36, // 39: storage.NodeCVE.snooze_expiry:type_name -> google.protobuf.Timestamp - 36, // 40: storage.NodeCVE.orphaned_time:type_name -> google.protobuf.Timestamp - 22, // 41: storage.ClusterCVE.cve_base_info:type_name -> storage.CVEInfo - 1, // 42: storage.ClusterCVE.severity:type_name -> storage.VulnerabilitySeverity - 36, // 43: storage.ClusterCVE.snooze_start:type_name -> google.protobuf.Timestamp - 36, // 44: storage.ClusterCVE.snooze_expiry:type_name -> google.protobuf.Timestamp - 4, // 45: storage.ClusterCVE.type:type_name -> storage.CVE.CVEType - 2, // 46: storage.CVSSScore.source:type_name -> storage.Source - 29, // 47: storage.CVSSScore.cvssv2:type_name -> storage.CVSSV2 - 30, // 48: storage.CVSSScore.cvssv3:type_name -> storage.CVSSV3 - 8, // 49: storage.CVSSV2.attack_vector:type_name -> storage.CVSSV2.AttackVector - 9, // 50: storage.CVSSV2.access_complexity:type_name -> storage.CVSSV2.AccessComplexity - 10, // 51: storage.CVSSV2.authentication:type_name -> storage.CVSSV2.Authentication - 7, // 52: storage.CVSSV2.confidentiality:type_name -> storage.CVSSV2.Impact - 7, // 53: storage.CVSSV2.integrity:type_name -> storage.CVSSV2.Impact - 7, // 54: storage.CVSSV2.availability:type_name -> storage.CVSSV2.Impact - 11, // 55: storage.CVSSV2.severity:type_name -> storage.CVSSV2.Severity - 13, // 56: storage.CVSSV3.attack_vector:type_name -> storage.CVSSV3.AttackVector - 14, // 57: storage.CVSSV3.attack_complexity:type_name -> storage.CVSSV3.Complexity - 15, // 58: storage.CVSSV3.privileges_required:type_name -> storage.CVSSV3.Privileges - 16, // 59: storage.CVSSV3.user_interaction:type_name -> storage.CVSSV3.UserInteraction - 17, // 60: storage.CVSSV3.scope:type_name -> storage.CVSSV3.Scope - 12, // 61: storage.CVSSV3.confidentiality:type_name -> storage.CVSSV3.Impact - 12, // 62: storage.CVSSV3.integrity:type_name -> storage.CVSSV3.Impact - 12, // 63: storage.CVSSV3.availability:type_name -> storage.CVSSV3.Impact - 18, // 64: storage.CVSSV3.severity:type_name -> storage.CVSSV3.Severity - 36, // 65: storage.ImageCVEInfo.fix_available_timestamp:type_name -> google.protobuf.Timestamp - 36, // 66: storage.ImageCVEInfo.first_system_occurrence:type_name -> google.protobuf.Timestamp - 1, // 67: storage.CVE.DistroSpecific.severity:type_name -> storage.VulnerabilitySeverity - 5, // 68: storage.CVE.DistroSpecific.score_version:type_name -> storage.CVE.ScoreVersion - 29, // 69: storage.CVE.DistroSpecific.cvss_v2:type_name -> storage.CVSSV2 - 30, // 70: storage.CVE.DistroSpecific.cvss_v3:type_name -> storage.CVSSV3 - 32, // 71: storage.CVE.DistroSpecificsEntry.value:type_name -> storage.CVE.DistroSpecific - 72, // [72:72] is the sub-list for method output_type - 72, // [72:72] is the sub-list for method input_type - 72, // [72:72] is the sub-list for extension type_name - 72, // [72:72] is the sub-list for extension extendee - 0, // [0:72] is the sub-list for field type_name + 24, // 34: storage.ImageCVEV2.advisory:type_name -> storage.Advisory + 37, // 35: storage.ImageCVEV2.fix_available_timestamp:type_name -> google.protobuf.Timestamp + 3, // 36: storage.ImageCVEV2.origin:type_name -> storage.VulnOrigin + 23, // 37: storage.NodeCVE.cve_base_info:type_name -> storage.CVEInfo + 1, // 38: storage.NodeCVE.severity:type_name -> storage.VulnerabilitySeverity + 37, // 39: storage.NodeCVE.snooze_start:type_name -> google.protobuf.Timestamp + 37, // 40: storage.NodeCVE.snooze_expiry:type_name -> google.protobuf.Timestamp + 37, // 41: storage.NodeCVE.orphaned_time:type_name -> google.protobuf.Timestamp + 23, // 42: storage.ClusterCVE.cve_base_info:type_name -> storage.CVEInfo + 1, // 43: storage.ClusterCVE.severity:type_name -> storage.VulnerabilitySeverity + 37, // 44: storage.ClusterCVE.snooze_start:type_name -> google.protobuf.Timestamp + 37, // 45: storage.ClusterCVE.snooze_expiry:type_name -> google.protobuf.Timestamp + 5, // 46: storage.ClusterCVE.type:type_name -> storage.CVE.CVEType + 2, // 47: storage.CVSSScore.source:type_name -> storage.Source + 30, // 48: storage.CVSSScore.cvssv2:type_name -> storage.CVSSV2 + 31, // 49: storage.CVSSScore.cvssv3:type_name -> storage.CVSSV3 + 9, // 50: storage.CVSSV2.attack_vector:type_name -> storage.CVSSV2.AttackVector + 10, // 51: storage.CVSSV2.access_complexity:type_name -> storage.CVSSV2.AccessComplexity + 11, // 52: storage.CVSSV2.authentication:type_name -> storage.CVSSV2.Authentication + 8, // 53: storage.CVSSV2.confidentiality:type_name -> storage.CVSSV2.Impact + 8, // 54: storage.CVSSV2.integrity:type_name -> storage.CVSSV2.Impact + 8, // 55: storage.CVSSV2.availability:type_name -> storage.CVSSV2.Impact + 12, // 56: storage.CVSSV2.severity:type_name -> storage.CVSSV2.Severity + 14, // 57: storage.CVSSV3.attack_vector:type_name -> storage.CVSSV3.AttackVector + 15, // 58: storage.CVSSV3.attack_complexity:type_name -> storage.CVSSV3.Complexity + 16, // 59: storage.CVSSV3.privileges_required:type_name -> storage.CVSSV3.Privileges + 17, // 60: storage.CVSSV3.user_interaction:type_name -> storage.CVSSV3.UserInteraction + 18, // 61: storage.CVSSV3.scope:type_name -> storage.CVSSV3.Scope + 13, // 62: storage.CVSSV3.confidentiality:type_name -> storage.CVSSV3.Impact + 13, // 63: storage.CVSSV3.integrity:type_name -> storage.CVSSV3.Impact + 13, // 64: storage.CVSSV3.availability:type_name -> storage.CVSSV3.Impact + 19, // 65: storage.CVSSV3.severity:type_name -> storage.CVSSV3.Severity + 37, // 66: storage.ImageCVEInfo.fix_available_timestamp:type_name -> google.protobuf.Timestamp + 37, // 67: storage.ImageCVEInfo.first_system_occurrence:type_name -> google.protobuf.Timestamp + 1, // 68: storage.CVE.DistroSpecific.severity:type_name -> storage.VulnerabilitySeverity + 6, // 69: storage.CVE.DistroSpecific.score_version:type_name -> storage.CVE.ScoreVersion + 30, // 70: storage.CVE.DistroSpecific.cvss_v2:type_name -> storage.CVSSV2 + 31, // 71: storage.CVE.DistroSpecific.cvss_v3:type_name -> storage.CVSSV3 + 33, // 72: storage.CVE.DistroSpecificsEntry.value:type_name -> storage.CVE.DistroSpecific + 73, // [73:73] is the sub-list for method output_type + 73, // [73:73] is the sub-list for method input_type + 73, // [73:73] is the sub-list for extension type_name + 73, // [73:73] is the sub-list for extension extendee + 0, // [0:73] is the sub-list for field type_name } func init() { file_storage_cve_proto_init() } @@ -3125,7 +3219,7 @@ func file_storage_cve_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_storage_cve_proto_rawDesc), len(file_storage_cve_proto_rawDesc)), - NumEnums: 19, + NumEnums: 20, NumMessages: 17, NumExtensions: 0, NumServices: 0, diff --git a/generated/storage/cve_vtproto.pb.go b/generated/storage/cve_vtproto.pb.go index 6b1495a836eda..8c05e40d486f1 100644 --- a/generated/storage/cve_vtproto.pb.go +++ b/generated/storage/cve_vtproto.pb.go @@ -294,6 +294,7 @@ func (m *ImageCVEV2) CloneVT() *ImageCVEV2 { r.ImageIdV2 = m.ImageIdV2 r.FixAvailableTimestamp = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.FixAvailableTimestamp).CloneVT()) r.Datasource = m.Datasource + r.Origin = m.Origin if m.HasFixedBy != nil { r.HasFixedBy = m.HasFixedBy.(interface { CloneVT() isImageCVEV2_HasFixedBy @@ -976,6 +977,9 @@ func (this *ImageCVEV2) EqualVT(that *ImageCVEV2) bool { if this.Datasource != that.Datasource { return false } + if this.Origin != that.Origin { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -2193,6 +2197,13 @@ func (m *ImageCVEV2) MarshalToSizedBufferVT(dAtA []byte) (int, error) { } i -= size } + if m.Origin != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Origin)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x90 + } if len(m.Datasource) > 0 { i -= len(m.Datasource) copy(dAtA[i:], m.Datasource) @@ -3334,6 +3345,9 @@ func (m *ImageCVEV2) SizeVT() (n int) { if l > 0 { n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.Origin != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.Origin)) + } n += len(m.unknownFields) return n } @@ -6466,6 +6480,25 @@ func (m *ImageCVEV2) UnmarshalVT(dAtA []byte) error { } m.Datasource = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 18: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) + } + m.Origin = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Origin |= VulnOrigin(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -10970,6 +11003,25 @@ func (m *ImageCVEV2) UnmarshalVTUnsafe(dAtA []byte) error { } m.Datasource = stringValue iNdEx = postIndex + case 18: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) + } + m.Origin = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Origin |= VulnOrigin(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/generated/storage/vulnerability.pb.go b/generated/storage/vulnerability.pb.go index 72d93b0238a75..4d5bcd07dc214 100644 --- a/generated/storage/vulnerability.pb.go +++ b/generated/storage/vulnerability.pb.go @@ -127,7 +127,7 @@ func (EmbeddedVulnerability_VulnerabilityType) EnumDescriptor() ([]byte, []int) return file_storage_vulnerability_proto_rawDescGZIP(), []int{0, 1} } -// Next Tag: 29 +// Next Tag: 30 type EmbeddedVulnerability struct { state protoimpl.MessageState `protogen:"open.v1"` Cve string `protobuf:"bytes,1,opt,name=cve,proto3" json:"cve,omitempty" search:"CVE"` // @gotags: search:"CVE" @@ -166,9 +166,11 @@ type EmbeddedVulnerability struct { NvdCvss float32 `protobuf:"fixed32,22,opt,name=nvd_cvss,json=nvdCvss,proto3" json:"nvd_cvss,omitempty" search:"NVD CVSS"` // @gotags: search:"NVD CVSS" Epss *EPSS `protobuf:"bytes,23,opt,name=epss,proto3" json:"epss,omitempty"` // datasource is for internal use only - Datasource string `protobuf:"bytes,26,opt,name=datasource,proto3" json:"-" hash:"ignore"` // @gotags: json:"-" hash:"ignore" - Exploit *Exploit `protobuf:"bytes,27,opt,name=exploit,proto3" json:"exploit,omitempty"` - CisaKev bool `protobuf:"varint,28,opt,name=cisa_kev,json=cisaKev,proto3" json:"cisa_kev,omitempty" policy:"CISA KEV"` // @gotags: policy:"CISA KEV" + Datasource string `protobuf:"bytes,26,opt,name=datasource,proto3" json:"-" hash:"ignore"` // @gotags: json:"-" hash:"ignore" + Exploit *Exploit `protobuf:"bytes,27,opt,name=exploit,proto3" json:"exploit,omitempty"` + CisaKev bool `protobuf:"varint,28,opt,name=cisa_kev,json=cisaKev,proto3" json:"cisa_kev,omitempty" policy:"CISA KEV"` // @gotags: policy:"CISA KEV" + // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). + Origin VulnOrigin `protobuf:"varint,29,opt,name=origin,proto3,enum=storage.VulnOrigin" json:"origin,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -401,6 +403,13 @@ func (x *EmbeddedVulnerability) GetCisaKev() bool { return false } +func (x *EmbeddedVulnerability) GetOrigin() VulnOrigin { + if x != nil { + return x.Origin + } + return VulnOrigin_VULN_ORIGIN_OTHER +} + type isEmbeddedVulnerability_SetFixedBy interface { isEmbeddedVulnerability_SetFixedBy() } @@ -529,7 +538,7 @@ var File_storage_vulnerability_proto protoreflect.FileDescriptor const file_storage_vulnerability_proto_rawDesc = "" + "\n" + - "\x1bstorage/vulnerability.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x11storage/cve.proto\"\xfd\f\n" + + "\x1bstorage/vulnerability.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x11storage/cve.proto\"\xaa\r\n" + "\x15EmbeddedVulnerability\x12\x10\n" + "\x03cve\x18\x01 \x01(\tR\x03cve\x12-\n" + "\badvisory\x18\x18 \x01(\v2\x11.storage.AdvisoryR\badvisory\x12\x12\n" + @@ -562,7 +571,8 @@ const file_storage_vulnerability_proto_rawDesc = "" + "datasource\x18\x1a \x01(\tR\n" + "datasource\x12*\n" + "\aexploit\x18\x1b \x01(\v2\x10.storage.ExploitR\aexploit\x12\x19\n" + - "\bcisa_kev\x18\x1c \x01(\bR\acisaKev\"\x1e\n" + + "\bcisa_kev\x18\x1c \x01(\bR\acisaKev\x12+\n" + + "\x06origin\x18\x1d \x01(\x0e2\x13.storage.VulnOriginR\x06origin\"\x1e\n" + "\fScoreVersion\x12\x06\n" + "\x02V2\x10\x00\x12\x06\n" + "\x02V3\x10\x01\"\xac\x01\n" + @@ -613,7 +623,8 @@ var file_storage_vulnerability_proto_goTypes = []any{ (*CVSSScore)(nil), // 10: storage.CVSSScore (*EPSS)(nil), // 11: storage.EPSS (*Exploit)(nil), // 12: storage.Exploit - (*CVEInfo)(nil), // 13: storage.CVEInfo + (VulnOrigin)(0), // 13: storage.VulnOrigin + (*CVEInfo)(nil), // 14: storage.CVEInfo } var file_storage_vulnerability_proto_depIdxs = []int32{ 4, // 0: storage.EmbeddedVulnerability.advisory:type_name -> storage.Advisory @@ -634,15 +645,16 @@ var file_storage_vulnerability_proto_depIdxs = []int32{ 10, // 15: storage.EmbeddedVulnerability.cvss_metrics:type_name -> storage.CVSSScore 11, // 16: storage.EmbeddedVulnerability.epss:type_name -> storage.EPSS 12, // 17: storage.EmbeddedVulnerability.exploit:type_name -> storage.Exploit - 13, // 18: storage.NodeVulnerability.cve_base_info:type_name -> storage.CVEInfo - 8, // 19: storage.NodeVulnerability.severity:type_name -> storage.VulnerabilitySeverity - 7, // 20: storage.NodeVulnerability.snooze_start:type_name -> google.protobuf.Timestamp - 7, // 21: storage.NodeVulnerability.snooze_expiry:type_name -> google.protobuf.Timestamp - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 13, // 18: storage.EmbeddedVulnerability.origin:type_name -> storage.VulnOrigin + 14, // 19: storage.NodeVulnerability.cve_base_info:type_name -> storage.CVEInfo + 8, // 20: storage.NodeVulnerability.severity:type_name -> storage.VulnerabilitySeverity + 7, // 21: storage.NodeVulnerability.snooze_start:type_name -> google.protobuf.Timestamp + 7, // 22: storage.NodeVulnerability.snooze_expiry:type_name -> google.protobuf.Timestamp + 23, // [23:23] is the sub-list for method output_type + 23, // [23:23] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_storage_vulnerability_proto_init() } diff --git a/generated/storage/vulnerability_vtproto.pb.go b/generated/storage/vulnerability_vtproto.pb.go index 6b79fd4de7ec4..32deb7c430fda 100644 --- a/generated/storage/vulnerability_vtproto.pb.go +++ b/generated/storage/vulnerability_vtproto.pb.go @@ -53,6 +53,7 @@ func (m *EmbeddedVulnerability) CloneVT() *EmbeddedVulnerability { r.Datasource = m.Datasource r.Exploit = m.Exploit.CloneVT() r.CisaKev = m.CisaKev + r.Origin = m.Origin if m.SetFixedBy != nil { r.SetFixedBy = m.SetFixedBy.(interface { CloneVT() isEmbeddedVulnerability_SetFixedBy @@ -242,6 +243,9 @@ func (this *EmbeddedVulnerability) EqualVT(that *EmbeddedVulnerability) bool { if this.CisaKev != that.CisaKev { return false } + if this.Origin != that.Origin { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -371,6 +375,13 @@ func (m *EmbeddedVulnerability) MarshalToSizedBufferVT(dAtA []byte) (int, error) } i -= size } + if m.Origin != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Origin)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xe8 + } if m.CisaKev { i-- if m.CisaKev { @@ -862,6 +873,9 @@ func (m *EmbeddedVulnerability) SizeVT() (n int) { if m.CisaKev { n += 3 } + if m.Origin != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.Origin)) + } n += len(m.unknownFields) return n } @@ -1782,6 +1796,25 @@ func (m *EmbeddedVulnerability) UnmarshalVT(dAtA []byte) error { } } m.CisaKev = bool(v != 0) + case 29: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) + } + m.Origin = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Origin |= VulnOrigin(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -2927,6 +2960,25 @@ func (m *EmbeddedVulnerability) UnmarshalVTUnsafe(dAtA []byte) error { } } m.CisaKev = bool(v != 0) + case 29: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) + } + m.Origin = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Origin |= VulnOrigin(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pkg/scanners/scannerv4/convert.go b/pkg/scanners/scannerv4/convert.go index 39b5452d25fc2..e511b1a605c66 100644 --- a/pkg/scanners/scannerv4/convert.go +++ b/pkg/scanners/scannerv4/convert.go @@ -295,6 +295,7 @@ func buildEmbeddedVulnerability(ccVuln *v4.VulnerabilityReport_Vulnerability, en CisaKev: cisaKevEnabled(ccVuln), FixAvailableTimestamp: ccVuln.GetFixedDate(), Datasource: vulnDataSource(ccVuln, envOS), + Origin: vulnOrigin(ccVuln.GetUpdater()), } if err := setScoresAndScoreVersions(vuln, ccVuln.GetCvssMetrics()); err != nil { utils.Should(err) @@ -844,6 +845,11 @@ func splitVersionNumbers(v string) []int { // mergeScoringFields overwrites scoring-related fields on dst when src has more // complete or higher-severity scoring data. Priority: more CVSS metrics, higher // severity, higher CVSS base score. +// +// Origin is set here defensively so that if in the future multiple updaters +// report the same CVE, Origin reflects the source that won the scoring +// comparison, keeping the assignment deterministic rather than dependent +// on input order. func mergeScoringFields(dst, src *storage.EmbeddedVulnerability) { c := cmp.Or( cmp.Compare(len(src.GetCvssMetrics()), len(dst.GetCvssMetrics())), @@ -867,4 +873,37 @@ func mergeScoringFields(dst, src *storage.EmbeddedVulnerability) { dst.Epss = src.GetEpss() dst.Exploit = src.GetExploit() dst.CisaKev = src.GetExploit() != nil + dst.Origin = src.GetOrigin() +} + +var updaterOrigins = []struct { + prefix string + origin storage.VulnOrigin +}{ + {"alpine-", storage.VulnOrigin_VULN_ORIGIN_ALPINE}, + {"aws-", storage.VulnOrigin_VULN_ORIGIN_AMAZON}, + {"debian/", storage.VulnOrigin_VULN_ORIGIN_DEBIAN}, + {"oracle-", storage.VulnOrigin_VULN_ORIGIN_ORACLE}, + {"osv/", storage.VulnOrigin_VULN_ORIGIN_OSV}, + {"photon-", storage.VulnOrigin_VULN_ORIGIN_PHOTON}, + {"rhel-", storage.VulnOrigin_VULN_ORIGIN_RED_HAT}, + {"suse-", storage.VulnOrigin_VULN_ORIGIN_SUSE}, + {"ubuntu/", storage.VulnOrigin_VULN_ORIGIN_UBUNTU}, +} + +// vulnOrigin translates a Claircore updater name into VulnOrigin. +// +// ASSUMPTION: updater names follow prefix conventions observed in Claircore. +// These names are exposed via the API but their values are not guaranteed +// to be stable. +// +// TODO(ROX-36340): Replace this prefix-based translation with something +// more stable. +func vulnOrigin(updater string) storage.VulnOrigin { + for _, e := range updaterOrigins { + if strings.HasPrefix(updater, e.prefix) { + return e.origin + } + } + return storage.VulnOrigin_VULN_ORIGIN_OTHER } diff --git a/pkg/scanners/scannerv4/convert_test.go b/pkg/scanners/scannerv4/convert_test.go index 16608d3f3dfc5..9ad37ca82d9b2 100644 --- a/pkg/scanners/scannerv4/convert_test.go +++ b/pkg/scanners/scannerv4/convert_test.go @@ -3567,3 +3567,38 @@ func TestCompareNumericSegments(t *testing.T) { }) } } + +func TestVulnOrigin(t *testing.T) { + testcases := map[string]struct { + updater string + expected storage.VulnOrigin + }{ + "empty": {"", storage.VulnOrigin_VULN_ORIGIN_OTHER}, + "unknown updater": {"something-else", storage.VulnOrigin_VULN_ORIGIN_OTHER}, + "alpine main": {"alpine-main-v3.18-updater", storage.VulnOrigin_VULN_ORIGIN_ALPINE}, + "alpine community": {"alpine-community-v3.21-updater", storage.VulnOrigin_VULN_ORIGIN_ALPINE}, + "alpine edge": {"alpine-main-edge-updater", storage.VulnOrigin_VULN_ORIGIN_ALPINE}, + "aws AL2": {"aws-AL2-updater", storage.VulnOrigin_VULN_ORIGIN_AMAZON}, + "aws AL2023": {"aws-AL2023-updater", storage.VulnOrigin_VULN_ORIGIN_AMAZON}, + "debian": {"debian/updater", storage.VulnOrigin_VULN_ORIGIN_DEBIAN}, + "oracle": {"oracle-2024-updater", storage.VulnOrigin_VULN_ORIGIN_ORACLE}, + "osv go": {"osv/go", storage.VulnOrigin_VULN_ORIGIN_OSV}, + "osv maven": {"osv/maven", storage.VulnOrigin_VULN_ORIGIN_OSV}, + "osv npm": {"osv/npm", storage.VulnOrigin_VULN_ORIGIN_OSV}, + "photon": {"photon-updater-photon3", storage.VulnOrigin_VULN_ORIGIN_PHOTON}, + "rhel vex": {"rhel-vex", storage.VulnOrigin_VULN_ORIGIN_RED_HAT}, + "suse leap": {"suse-updater-opensuse.leap.15.6", storage.VulnOrigin_VULN_ORIGIN_SUSE}, + "suse enterprise": {"suse-updater-suse.linux.enterprise.server.15", storage.VulnOrigin_VULN_ORIGIN_SUSE}, + "ubuntu focal": {"ubuntu/updater/focal", storage.VulnOrigin_VULN_ORIGIN_UBUNTU}, + "ubuntu noble": {"ubuntu/updater/noble", storage.VulnOrigin_VULN_ORIGIN_UBUNTU}, + "prefix only alpine": {"alpine-", storage.VulnOrigin_VULN_ORIGIN_ALPINE}, + "near miss alpine no dash": {"alpine", storage.VulnOrigin_VULN_ORIGIN_OTHER}, + "near miss debian no slash": {"debian-updater", storage.VulnOrigin_VULN_ORIGIN_OTHER}, + } + for name, tt := range testcases { + t.Run(name, func(t *testing.T) { + got := vulnOrigin(tt.updater) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/proto/storage/cve.proto b/proto/storage/cve.proto index 5a14f5a1eb3bf..178ee1c04768e 100644 --- a/proto/storage/cve.proto +++ b/proto/storage/cve.proto @@ -29,6 +29,19 @@ enum Source { SOURCE_NVD = 3; } +enum VulnOrigin { + VULN_ORIGIN_OTHER = 0; + VULN_ORIGIN_OSV = 1; + VULN_ORIGIN_ALPINE = 2; + VULN_ORIGIN_AMAZON = 3; + VULN_ORIGIN_DEBIAN = 4; + VULN_ORIGIN_ORACLE = 5; + VULN_ORIGIN_PHOTON = 6; + VULN_ORIGIN_RED_HAT = 7; + VULN_ORIGIN_SUSE = 8; + VULN_ORIGIN_UBUNTU = 9; +} + //Added this score version for nvd cvss score. moving out and re using score version from cveinfo will cause backward compatibility issue enum CvssScoreVersion { UNKNOWN_VERSION = 0; @@ -211,6 +224,8 @@ message ImageCVEV2 { // Timestamp when the fix for this CVE was made available according to the sources. google.protobuf.Timestamp fix_available_timestamp = 16; // @gotags: search:"CVE Fix Available Timestamp,hidden" string datasource = 17; + // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). + VulnOrigin origin = 18; } message NodeCVE { diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index 99a29894e2557..a335c54587da6 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -6070,6 +6070,50 @@ } ] }, + { + "name": "VulnOrigin", + "enum_fields": [ + { + "name": "VULN_ORIGIN_OTHER" + }, + { + "name": "VULN_ORIGIN_OSV", + "integer": 1 + }, + { + "name": "VULN_ORIGIN_ALPINE", + "integer": 2 + }, + { + "name": "VULN_ORIGIN_AMAZON", + "integer": 3 + }, + { + "name": "VULN_ORIGIN_DEBIAN", + "integer": 4 + }, + { + "name": "VULN_ORIGIN_ORACLE", + "integer": 5 + }, + { + "name": "VULN_ORIGIN_PHOTON", + "integer": 6 + }, + { + "name": "VULN_ORIGIN_RED_HAT", + "integer": 7 + }, + { + "name": "VULN_ORIGIN_SUSE", + "integer": 8 + }, + { + "name": "VULN_ORIGIN_UBUNTU", + "integer": 9 + } + ] + }, { "name": "CvssScoreVersion", "enum_fields": [ @@ -6846,6 +6890,11 @@ "id": 17, "name": "datasource", "type": "string" + }, + { + "id": 18, + "name": "origin", + "type": "VulnOrigin" } ] }, @@ -21048,6 +21097,11 @@ "id": 28, "name": "cisa_kev", "type": "bool" + }, + { + "id": 29, + "name": "origin", + "type": "VulnOrigin" } ], "reserved_ids": [ diff --git a/proto/storage/vulnerability.proto b/proto/storage/vulnerability.proto index 849cf5b2247e7..9f08c6c8bbdb1 100644 --- a/proto/storage/vulnerability.proto +++ b/proto/storage/vulnerability.proto @@ -8,7 +8,7 @@ import "storage/cve.proto"; option go_package = "./storage;storage"; option java_package = "io.stackrox.proto.storage"; -// Next Tag: 29 +// Next Tag: 30 message EmbeddedVulnerability { // ScoreVersion can be deprecated ROX-26066 enum ScoreVersion { @@ -66,6 +66,8 @@ message EmbeddedVulnerability { string datasource = 26; // @gotags: json:"-" hash:"ignore" Exploit exploit = 27; bool cisa_kev = 28; // @gotags: policy:"CISA KEV" + // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). + VulnOrigin origin = 29; } message NodeVulnerability { From bed4a7b242715957740f1b6b61f4e2c8ca146eab Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:23:08 +0200 Subject: [PATCH 044/638] ROX-33123: Run e2e-qa-tests for GKE & OCP in GHA (#21056) --- .github/workflows/e2e-dispatch.yaml | 234 ++++++++--- .../workflows/e2e-qa-tests-gke-konflux.yaml | 38 ++ .github/workflows/e2e-qa-tests-gke.yaml | 33 ++ .github/workflows/e2e-qa-tests-ocp-4-22.yaml | 46 +++ .github/workflows/e2e-qa-tests.yaml | 367 ++++++++++++++++++ qa-tests-backend/scripts/run-part-1.sh | 38 +- .../src/main/groovy/util/Env.groovy | 4 - .../src/test/groovy/AuditScrubbingTest.groovy | 5 +- scripts/ci/create-webhookserver.sh | 2 +- scripts/ci/lib.sh | 91 ++++- scripts/ci/store-artifacts.sh | 2 +- tests/e2e/lib.sh | 29 +- 12 files changed, 753 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/e2e-qa-tests-gke-konflux.yaml create mode 100644 .github/workflows/e2e-qa-tests-gke.yaml create mode 100644 .github/workflows/e2e-qa-tests-ocp-4-22.yaml create mode 100644 .github/workflows/e2e-qa-tests.yaml diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index dffba0084de15..102623bc8400b 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -32,7 +32,8 @@ jobs: needs: [ detect-changes ] runs-on: ubuntu-slim outputs: - dispatch-any: ${{ steps.determine.outputs.dispatch-any }} + wait-for-gha-images: ${{ steps.determine.outputs.wait-for-gha-images }} + wait-for-konflux-images: ${{ steps.determine.outputs.wait-for-konflux-images }} jobs: ${{ steps.determine.outputs.jobs }} steps: - name: Determine whether should dispatch workflows @@ -52,37 +53,66 @@ jobs: import os import subprocess from types import SimpleNamespace + from dataclasses import dataclass + + @dataclass + class Job: + name: str + require_pr_label: bool = False + require_konflux_build: bool = False + + def should_run(self, input): + if input.is_fork == 'true': + return False + + if input.event_name != 'pull_request': + return True + elif self.name in input.pr_labels: + return True + + # We are in a PR context, job is not explicitly listed among the PR labels. + + # TODO(ROX-21073): re-enable Konflux for all PR branches + if self.require_konflux_build and 'konflux-build' not in input.pr_labels: + return False + + if input.ui_only_change == 'true': + # For now, all the workflows that we dispatch should be skipped for PRs that only change UI. + # As we add more E2E test workflows, we may want to make this more flexible. + return False + if self.require_pr_label: + return False + if input.is_draft != 'true': + return True + + return False + input = SimpleNamespace(**{ key.removeprefix('input__'): val for (key, val) in os.environ.items() if key.startswith('input__')}) - for k, v in vars(input).items(): - print(f'{k} = {v}') if input.event_name != 'pull_request': - all_labels = [] + input.pr_labels = [] else: p = subprocess.run( ['gh', 'pr', 'view', input.pr_number, '--repo', input.repo, '--json', 'labels', '--jq', '.labels[].name'], check=True, encoding='utf-8', stdout=subprocess.PIPE, stdin=subprocess.DEVNULL) - all_labels = p.stdout.strip().splitlines() - print(f'all_labels = {", ".join(all_labels)}') + input.pr_labels = p.stdout.strip().splitlines() - def should_run(job): - if input.is_fork == 'true': - return False - if input.event_name != 'pull_request': - return True - if input.ui_only_change == 'true': - # For now, all the workflows that we dispatch should be skipped for PRs that only change UI. - # As we add more E2E test workflows, we may want to make this more flexible. - return False - if input.is_draft != 'true': - return True - if job in all_labels: - return True - return False + for k, v in vars(input).items(): + print(f'{k} = {v}') + + jobs_registry = [ + Job(name='e2e-byodb-tests'), + Job(name='e2e-db-backup-restore-tests'), + Job(name='e2e-gke-upgrade-tests'), + Job(name='e2e-nongroovy-tests'), + Job(name='e2e-qa-tests-gke', require_pr_label=True), + Job(name='e2e-qa-tests-gke-konflux', require_konflux_build=True), + Job(name='e2e-qa-tests-ocp-4-22', require_pr_label=True), + ] with open(os.environ['GITHUB_OUTPUT'], mode='at', encoding='utf-8') as file: def write(job, val): @@ -90,34 +120,32 @@ jobs: print(line) file.write(line + '\n') - jobs = { job: should_run(job) for job in [ - 'e2e-byodb-tests', - 'e2e-db-backup-restore-tests', - 'e2e-gke-upgrade-tests', - 'e2e-nongroovy-tests', - ]} + jobs = {job.name: job.should_run(input) for job in jobs_registry} write('jobs', json.dumps(jobs)) - # dispatch_any==False is a shortcut to prevent executing the further workflow for draft PRs which don't - # qualify for running any actual e2e jobs. - # We must make sure that dispatch_any is True for non-drafts even if no actual jobs are to be dispatched - # (the case of UI-only PRs) because this lets GHA enter the corresponding workflows and skip the test jobs - # inside them "in proper way" that allows to satisfy branch protection rules. - dispatch_any = any(jobs.values()) or input.is_draft != 'true' - write('dispatch-any', str(dispatch_any).lower()) + # Find out when we can complete the workflow early by not even waiting for GHA-built images. + wait_for_gha_images = input.event_name != 'pull_request' or input.is_draft != 'true' or any(jobs.values()) + write('wait-for-gha-images', str(wait_for_gha_images).lower()) + + # Waiting for konflux-built images in PR request context requires the 'konflux-build' PR label. + wait_for_konflux_images = input.event_name != 'pull_request' or ('konflux-build' in input.pr_labels and any(jobs.values())) + write('wait-for-konflux-images', str(wait_for_konflux_images).lower()) - # Waits for images to be built so we don't start testing until they are available. wait-for-images: needs: [ should-dispatch ] - if: needs.should-dispatch.outputs.dispatch-any == 'true' - runs-on: ubuntu-slim + if: needs.should-dispatch.outputs.wait-for-gha-images == 'true' + runs-on: ubuntu-latest + env: + MAIN_IMAGE_TAG_DIR: "." + IMAGE_PREFIX: "" + OPERATOR_IMAGE_PREFIX: "stackrox-" + DEV_TAG_SUFFIX: "" + # MAIN_IMAGE_TAG can be used for overriding when you want to run workflows for particular images. + # MAIN_IMAGE_TAG: 4.12.x-350-gc76d76b0fb outputs: - tag: ${{ steps.build-tag.outputs.tag }} - operator_tag: ${{ steps.build-tag.outputs.operator_tag }} - ## MAIN_IMAGE_TAG can be used for overriding when you want to run workflows for particular images. - # env: - # MAIN_IMAGE_TAG: 4.12.x-350-gc76d76b0fb + tag: ${{ steps.image-references.outputs.tag }} + tag_suffix: ${{ steps.image-references.outputs.tag_suffix }} concurrency: group: wait-for-images-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -129,30 +157,81 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false - uses: ./.github/actions/handle-tagged-build - - name: Compute build tag - id: build-tag - run: | - tag="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" - echo "tag=$tag" | tee -a "$GITHUB_OUTPUT" -a "$GITHUB_ENV" - echo "operator_tag=$(BUILD_TAG="${tag}" make -C operator --quiet --no-print-directory tag)" | tee -a "$GITHUB_OUTPUT" -a "$GITHUB_ENV" + - name: Determine image references + id: image-references + run: &image-references | + IMAGE_TAG="${MAIN_IMAGE_TAG:-"$(make -C "$MAIN_IMAGE_TAG_DIR" --quiet --no-print-directory tag)"}" + echo "IMAGE_TAG=$IMAGE_TAG" | tee -a "$GITHUB_ENV" + echo "tag=$IMAGE_TAG" | tee -a "$GITHUB_OUTPUT" + + OPERATOR_IMAGE_TAG="$(BUILD_TAG="${IMAGE_TAG}" make -C operator --quiet --no-print-directory tag)" + echo "OPERATOR_IMAGE_TAG=${OPERATOR_IMAGE_TAG}" | tee -a "$GITHUB_ENV" + + # Mirrors the logic in https://github.com/stackrox/konflux-tasks/blob/main/tasks/determine-image-tag-task.yaml. + source_branch="${GITHUB_HEAD_REF:-}" + target_branch="${GITHUB_BASE_REF:-${GITHUB_REF}}" + TAG_SUFFIX="" + if [[ "${source_branch}" == *konflux-release-like* ]]; then + echo "Source branch contains 'konflux-release-like', suppressing suffix." + elif ! grep -qE '^((refs/heads/)?release-[0-9a-z]+\.[0-9a-z]+|refs/tags/[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?)$' <<< "${target_branch}"; then + TAG_SUFFIX="${DEV_TAG_SUFFIX}" + fi + echo "TAG_SUFFIX=${TAG_SUFFIX}" | tee -a "$GITHUB_ENV" + echo "tag_suffix=${TAG_SUFFIX}" | tee -a "$GITHUB_OUTPUT" + - name: Wait for images uses: stackrox/actions/release/wait-for-image@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/release/wait-for-image@v1 with: token: ${{ secrets.QUAY_RHACS_ENG_BEARER_TOKEN }} - image: | - rhacs-eng/central-db:${{ env.tag }} - rhacs-eng/collector:${{ env.tag }} - rhacs-eng/fact:${{ env.tag }} - rhacs-eng/main:${{ env.tag }} - rhacs-eng/roxctl:${{ env.tag }} - rhacs-eng/scanner-db-slim:${{ env.tag }} - rhacs-eng/scanner-db:${{ env.tag }} - rhacs-eng/scanner-slim:${{ env.tag }} - rhacs-eng/scanner-v4-db:${{ env.tag }} - rhacs-eng/scanner-v4:${{ env.tag }} - rhacs-eng/scanner:${{ env.tag }} - rhacs-eng/stackrox-operator-bundle:v${{ env.operator_tag }} - rhacs-eng/stackrox-operator:${{ env.operator_tag }} + limit: 1800 # Seconds, corresponding to 30m. + image: &stackrox-required-images | + rhacs-eng/${{ env.IMAGE_PREFIX }}central-db:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}collector:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}fact:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}main:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}roxctl:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-db-slim:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-db:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-slim:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-v4-db:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-v4:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.IMAGE_PREFIX }}scanner:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.OPERATOR_IMAGE_PREFIX }}operator-bundle:v${{ env.OPERATOR_IMAGE_TAG }}${{ env.TAG_SUFFIX }} + rhacs-eng/${{ env.OPERATOR_IMAGE_PREFIX }}operator:${{ env.OPERATOR_IMAGE_TAG }}${{ env.TAG_SUFFIX }} + + wait-for-konflux-images: + needs: [ should-dispatch ] + if: needs.should-dispatch.outputs.wait-for-konflux-images == 'true' + runs-on: ubuntu-latest + env: + MAIN_IMAGE_TAG_DIR: "operator" + IMAGE_PREFIX: "release-" + OPERATOR_IMAGE_PREFIX: "release-" + DEV_TAG_SUFFIX: "-fast" + outputs: + tag: ${{ steps.image-references.outputs.tag }} + tag_suffix: ${{ steps.image-references.outputs.tag_suffix }} + concurrency: + group: wait-for-konflux-images-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + steps: + - name: Checkout repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - uses: ./.github/actions/handle-tagged-build + - name: Determine image references + id: image-references + run: *image-references + + - name: Wait for Konflux images + uses: stackrox/actions/release/wait-for-image@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/release/wait-for-image@v1 + with: + token: ${{ secrets.QUAY_RHACS_ENG_BEARER_TOKEN }} + image: *stackrox-required-images + limit: 14100 # Seconds, corresponding to 3h55m, which is the timeout for building the operator-index (.tekton/operator-index-build.yaml) + 5m buffer. e2e-byodb-tests: needs: [ wait-for-images, should-dispatch ] @@ -197,3 +276,36 @@ jobs: concurrency: group: e2e-nongroovy-tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true + + e2e-qa-tests-gke: + needs: [ wait-for-images, should-dispatch ] + uses: ./.github/workflows/e2e-qa-tests-gke.yaml + with: + tag: ${{ needs.wait-for-images.outputs.tag }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-qa-tests-gke }} + secrets: inherit + concurrency: + group: e2e-qa-tests-gke-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + + e2e-qa-tests-ocp-4-22: + needs: [ wait-for-images, should-dispatch ] + uses: ./.github/workflows/e2e-qa-tests-ocp-4-22.yaml + with: + tag: ${{ needs.wait-for-images.outputs.tag }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-qa-tests-ocp-4-22 }} + secrets: inherit + concurrency: + group: e2e-qa-tests-ocp-4-22-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + + e2e-qa-tests-gke-konflux: + needs: [ wait-for-konflux-images, should-dispatch ] + uses: ./.github/workflows/e2e-qa-tests-gke-konflux.yaml + with: + tag: ${{ needs.wait-for-konflux-images.outputs.tag }}${{ needs.wait-for-konflux-images.outputs.tag_suffix }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-qa-tests-gke-konflux }} + secrets: inherit + concurrency: + group: e2e-qa-tests-gke-konflux-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true diff --git a/.github/workflows/e2e-qa-tests-gke-konflux.yaml b/.github/workflows/e2e-qa-tests-gke-konflux.yaml new file mode 100644 index 0000000000000..cf816fcc4232a --- /dev/null +++ b/.github/workflows/e2e-qa-tests-gke-konflux.yaml @@ -0,0 +1,38 @@ +name: e2e-qa-tests-gke-konflux + +on: + workflow_call: + inputs: + tag: + description: | + Image tag to test. The caller needs to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). + type: string + required: true + should-run: + description: true if should really run the job. false - otherwise, i.e. the job should be skipped. + type: boolean + required: true + workflow_dispatch: + inputs: + tag: + description: | + Image tag to test. You need to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). + type: string + required: true + +jobs: + run: + uses: ./.github/workflows/e2e-qa-tests.yaml + with: + should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} + tag: ${{ inputs.tag }} + ci-job-name: gke-qa-e2e-tests-konflux + cluster-name-prefix: e2e-qa-gke-konflux + cluster-provisioning-method: direct-gke + extra-env-vars: | + ORCHESTRATOR_FLAVOR=k8s + KUBERNETES_PROVIDER=gke + USE_KONFLUX_IMAGES=true + secrets: inherit diff --git a/.github/workflows/e2e-qa-tests-gke.yaml b/.github/workflows/e2e-qa-tests-gke.yaml new file mode 100644 index 0000000000000..b575161117c70 --- /dev/null +++ b/.github/workflows/e2e-qa-tests-gke.yaml @@ -0,0 +1,33 @@ +name: e2e-qa-tests-gke + +on: + workflow_call: + inputs: + tag: + description: Image tag to test. + type: string + required: true + should-run: + description: true if should really run the job. false - otherwise, i.e. the job should be skipped. + type: boolean + required: true + workflow_dispatch: + inputs: + tag: + description: Image tag to test. + type: string + required: true + +jobs: + run: + uses: ./.github/workflows/e2e-qa-tests.yaml + with: + should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} + tag: ${{ inputs.tag }} + ci-job-name: gke-qa-e2e-tests + cluster-name-prefix: e2e-qa-tests-gke + cluster-provisioning-method: direct-gke + extra-env-vars: | + ORCHESTRATOR_FLAVOR=k8s + KUBERNETES_PROVIDER=gke + secrets: inherit diff --git a/.github/workflows/e2e-qa-tests-ocp-4-22.yaml b/.github/workflows/e2e-qa-tests-ocp-4-22.yaml new file mode 100644 index 0000000000000..45d41397f18c4 --- /dev/null +++ b/.github/workflows/e2e-qa-tests-ocp-4-22.yaml @@ -0,0 +1,46 @@ +name: e2e-qa-tests-ocp-4-22 + +on: + workflow_call: + inputs: + tag: + description: Image tag to test. + type: string + required: true + should-run: + description: true if should really run the job. false - otherwise, i.e. the job should be skipped. + type: boolean + required: true + workflow_dispatch: + inputs: + tag: + description: Image tag to test. + type: string + required: true + +jobs: + run: + uses: ./.github/workflows/e2e-qa-tests.yaml + with: + should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} + tag: ${{ inputs.tag }} + ci-job-name: ocp-4-22-qa-e2e-tests + cluster-name-prefix: e2e-qa-tests-ocp422 + cluster-provisioning-method: infra + cluster-wait-timeout-minutes: 50 + infra-flavor: openshift-4 + infra-args: | + openshift-version=ocp/stable-4.22 + master-node-type=e2-standard-4 + master-node-count=3 + worker-node-type=e2-standard-8 + worker-node-count=3 + ssd-storage-class=true + infra-lifespan: 5h + # TODO(ROX-36332): setup workload identities + # SETUP_WORKLOAD_IDENTITIES=true + extra-env-vars: | + ORCHESTRATOR_FLAVOR=openshift + KUBERNETES_PROVIDER=ocp + SFA_AGENT=true + secrets: inherit diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml new file mode 100644 index 0000000000000..bae02574b6ff8 --- /dev/null +++ b/.github/workflows/e2e-qa-tests.yaml @@ -0,0 +1,367 @@ +name: e2e-qa-tests + +on: + workflow_call: + inputs: + should-run: + description: "true if should really run the job. false - otherwise, i.e. the job should be skipped." + required: true + type: boolean + tag: + description: "Image tag to test." + required: true + type: string + ci-job-name: + description: "Unique job name, e.g. e2e-qa-tests-. Used to configure the image prefetcher and name uploaded artifacts. Propagated to tests as CI_JOB_NAME for test dispatching." + required: true + type: string + cluster-name-prefix: + description: "Used as prefix in provisioned cluster names. Maximum 20 characters." + required: true + type: string + cluster-provisioning-method: + description: "Selects cluster provisioning method to use (supported: direct-gke, infra)." + type: string + default: "direct-gke" + extra-env-vars: + description: "Environment variables for this flavor of QA E2E tests, as multi-line KEY=VALUE pairs" + required: false + type: string + infra-flavor: + description: "Infra cluster flavor, e.g. gke-default, ocp-4." + type: string + default: "gke-default" + infra-args: + description: "Infra cluster args as multi-line KEY=VALUE pairs, one per line." + type: string + default: | + nodes=3 + machine-type=e2-standard-8 + infra-lifespan: + description: "Infra cluster lifespan (auto-deleted after expiry)." + type: string + default: "5h" + cluster-wait-timeout-minutes: + description: "Timeout for waiting on cluster provisioning (OCP needs longer)." + type: number + default: 25 + job-timeout-minutes: + description: "Overall job timeout." + type: number + default: 361 + part1-timeout-minutes: + description: "Part 1 test timeout." + type: number + default: 245 + part2-timeout-minutes: + description: "Part 2 test timeout." + type: number + default: 35 + db-backup-restore-timeout-minutes: + description: "DB backup and restore test timeout." + type: number + default: 30 + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + qa-e2e-tests: + if: inputs.should-run + runs-on: ubuntu-latest + env: + MAIN_IMAGE_TAG: ${{ inputs.tag }} + BUILD_TAG: ${{ inputs.tag }} + CI_JOB_NAME: ${{ inputs.ci-job-name }} + ARTIFACT_DIR: ./junit-reports/ + USE_LOCAL_ROXCTL: "true" + ROX_RISK_REPROCESSING_INTERVAL: 15s + ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL: 30s + GCP_SERVICE_ACCOUNT_STACKROX_CI: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} + QA_TEST_DEBUG_LOGS: /tmp/qa-tests-backend-logs + PYTHONUNBUFFERED: "1" + CLUSTER_NAME_PREFIX: "${{ inputs.cluster-name-prefix }}" + CLUSTER_NAME_PREFIX_MAX: "20" + timeout-minutes: ${{ inputs.job-timeout-minutes }} + steps: + - name: Validate workflow input + run: | + case "${{ inputs.cluster-provisioning-method }}" in + direct-gke|infra) ;; + *) echo "::error::Unknown cluster-provisioning-method: '${{ inputs.cluster-provisioning-method }}'. Supported: direct-gke, infra"; exit 1 ;; + esac + + if [[ "${#CLUSTER_NAME_PREFIX}" -gt "$CLUSTER_NAME_PREFIX_MAX" ]]; then + echo "::error::Workflow input cluster-name-prefix must not be larger than $CLUSTER_NAME_PREFIX_MAX characters (is ${#CLUSTER_NAME_PREFIX} characters long)." + exit 1 + fi + + - name: Checkout repo + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # ratchet:actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - uses: ./.github/actions/job-preamble + with: + gcp-account: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} + + - name: Compute cluster name for infra-provisioned cluster + if: inputs.cluster-provisioning-method == 'infra' + # infractl enforces a 28-char limit for cluster names. + run: | + cluster_name_max=28 + + hash_length=$(( cluster_name_max - 1 - CLUSTER_NAME_PREFIX_MAX )) + hash_input="${{ github.run_id }}-${{ github.run_attempt }}" + hash="$(echo "$hash_input" | sha256sum)" + hash_truncated="${hash:0:$hash_length}" + + cluster_name="${CLUSTER_NAME_PREFIX}-${hash_truncated}" + echo "CLUSTER_NAME=${cluster_name}" >> "$GITHUB_ENV" + + - name: Prepare infra args + if: inputs.cluster-provisioning-method == 'infra' + id: prepare-infra-args + env: + INFRA_ARGS: ${{ inputs.infra-args }} + run: | + args=$(echo "$INFRA_ARGS" | grep -v '^$' | paste -sd ',' -) + echo "args=$args" >> "$GITHUB_OUTPUT" + + - name: Trigger cluster creation via infra + if: inputs.cluster-provisioning-method == 'infra' + uses: stackrox/actions/infra/create-cluster@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/create-cluster@v1 + with: + token: ${{ secrets.INFRA_TOKEN }} + flavor: ${{ inputs.infra-flavor }} + name: ${{ env.CLUSTER_NAME }} + lifespan: ${{ inputs.infra-lifespan }} + args: ${{ steps.prepare-infra-args.outputs.args }} + wait: false + + - name: Set up GCloud SDK + uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 + with: + install_components: "gke-gcloud-auth-plugin,beta" + + # In the direct-gke provisioning case, this step also exports CLUSTER_NAME + # into the environments. + - name: Create cluster via direct-gke + if: inputs.cluster-provisioning-method == 'direct-gke' + timeout-minutes: ${{ inputs.cluster-wait-timeout-minutes }} + env: + MACHINE_TYPE: "e2-standard-8" + NUM_NODES: "3" + GKE_SPOT: "true" + run: | + scripts/ci/gke.sh provision_gke_cluster "$CLUSTER_NAME_PREFIX" + echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" + + - name: Docker login to Quay.io + env: + REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + run: docker login -u "${REGISTRY_USERNAME}" --password-stdin quay.io <<<"${REGISTRY_PASSWORD}" + + - name: Set flavor-specific env vars + env: + EXTRA_ENV_VARS: ${{ inputs.extra-env-vars }} + run: | + while IFS= read -r line; do + if [[ -n "$line" ]]; then + echo "$line" >> "$GITHUB_ENV" + fi + done <<< "$EXTRA_ENV_VARS" + + - name: Install roxctl + uses: stackrox/roxctl-installer-action@47fb4f5b275066b8322369e6e33fa010915b0d13 # ratchet:stackrox/roxctl-installer-action@v1 + with: + roxctl-release: latest + + - name: Retrieving roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + + - name: Install oc + if: startsWith(inputs.infra-flavor, 'openshift') + run: | + curl -fsSL --retry 5 --retry-all-errors https://mirror.openshift.com/pub/openshift-v5/clients/ocp/stable/openshift-client-linux.tar.gz \ + | tar xz -C /usr/local/bin oc + oc version --client + + - uses: stackrox/actions/infra/install-infractl@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/install-infractl@v1 + if: inputs.cluster-provisioning-method == 'infra' + + - name: Wait for infra cluster and get kubeconfig + if: inputs.cluster-provisioning-method == 'infra' + env: + INFRA_TOKEN: ${{ secrets.INFRA_TOKEN }} + timeout-minutes: ${{ inputs.cluster-wait-timeout-minutes }} + run: | + infractl wait "${CLUSTER_NAME}" + infractl artifacts "${CLUSTER_NAME}" --download-dir artifacts > /dev/null + echo "KUBECONFIG=${PWD}/artifacts/kubeconfig" >> "$GITHUB_ENV" + + # gcloud container clusters create is synchronous, so the cluster is + # already RUNNING at this point. wait_for_cluster provides additional + # guarantees by polling until all kube-system pods are ready. + - name: Wait for cluster to stabilize + timeout-minutes: 5 + env: + CLUSTER_PROVISIONING_METHOD: ${{ inputs.cluster-provisioning-method }} + run: | + kubectl cluster-info + kubectl get nodes + if [[ "$CLUSTER_PROVISIONING_METHOD" == "direct-gke" ]]; then + scripts/ci/gke.sh wait_for_cluster + fi + + - name: Create artifact directory + run: mkdir -p "$ARTIFACT_DIR" + + - name: Download Go module dependencies + # The qa-tests-backend Makefile copies scanner protos from the Go module + # cache (via go list -m). Unlike Prow's apollo-ci image, GHA runners + # don't have the cache pre-populated. + run: go mod download github.com/stackrox/scanner + + - name: Run pre-test + shell: python + env: + QUAY_RHACS_ENG_BEARER_TOKEN: ${{ secrets.QUAY_RHACS_ENG_BEARER_TOKEN }} + QUAY_RHACS_ENG_RO_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} + QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + run: | + import sys + sys.path.append('.openshift-ci') + from pre_tests import PreSystemTests + PreSystemTests().run() + + - name: Run Part 1 tests + env: &test_env + USE_ROXIE_DEPLOY: true + QUAY_RHACS_ENG_RO_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} + QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} + REDHAT_USERNAME: ${{ secrets.REDHAT_USERNAME }} + REDHAT_PASSWORD: ${{ secrets.REDHAT_PASSWORD }} + GOOGLE_CREDENTIALS_GCR_SCANNER_V2: ${{ secrets.GOOGLE_CREDENTIALS_GCR_SCANNER_V2 }} + GOOGLE_CREDENTIALS_GCR_NO_ACCESS_KEY_V2: ${{ secrets.GOOGLE_CREDENTIALS_GCR_NO_ACCESS_KEY_V2 }} + AZURE_REGISTRY_PASSWORD: ${{ secrets.AZURE_REGISTRY_PASSWORD }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ECR_REGISTRY_NAME: ${{ secrets.AWS_ECR_REGISTRY_NAME }} + AWS_ECR_REGISTRY_REGION: ${{ secrets.AWS_ECR_REGISTRY_REGION }} + AWS_ASSUME_ROLE_ACCESS_KEY_ID: ${{ secrets.AWS_ASSUME_ROLE_ACCESS_KEY_ID }} + AWS_ASSUME_ROLE_SECRET_ACCESS_KEY: ${{ secrets.AWS_ASSUME_ROLE_SECRET_ACCESS_KEY }} + AWS_ASSUME_ROLE_ROLE_ID: ${{ secrets.AWS_ASSUME_ROLE_ROLE_ID }} + AWS_ASSUME_ROLE_EXTERNAL_ID: ${{ secrets.AWS_ASSUME_ROLE_EXTERNAL_ID }} + AWS_ASSUME_ROLE_TEST_CONDITION_ID: ${{ secrets.AWS_ASSUME_ROLE_TEST_CONDITION_ID }} + SLACK_FIXABLE_VULNS_CHANNEL: ${{ secrets.SLACK_FIXABLE_VULNS_CHANNEL }} + SLACK_ALT_WEBHOOK: ${{ secrets.SLACK_ALT_WEBHOOK }} + GOOGLE_ARTIFACT_REGISTRY_SERVICE_ACCOUNT_V2: ${{ secrets.GOOGLE_ARTIFACT_REGISTRY_SERVICE_ACCOUNT_V2 }} + QUAY_RHACS_ENG_BEARER_TOKEN: ${{ secrets.QUAY_RHACS_ENG_BEARER_TOKEN }} + OCM_OFFLINE_TOKEN: ${{ secrets.OCM_OFFLINE_TOKEN }} + CLOUD_SOURCES_TEST_OCM_CLIENT_ID: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_ID }} + CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET }} + SLACK_MAIN_WEBHOOK: ${{ secrets.SLACK_MAIN_WEBHOOK }} + GCP_SERVICE_ACCOUNT_EMAIL_STACKROX_CI_WORKLOAD_IDENTITY: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL_STACKROX_CI_WORKLOAD_IDENTITY }} + GCP_PROJECT_NUMBER_WORKLOAD_IDENTITY: ${{ secrets.GCP_PROJECT_NUMBER_WORKLOAD_IDENTITY }} + timeout-minutes: ${{ inputs.part1-timeout-minutes }} + run: qa-tests-backend/scripts/run-part-1.sh + + - name: Run Part 1 post-test + if: always() && env.KUBECONFIG != '' + shell: python + run: | + import os, sys + sys.path.append('.openshift-ci') + from post_tests import PostClusterTest + post = PostClusterTest( + check_stackrox_logs=True, + artifact_destination_prefix="part-1", + ) + post.run([os.environ['ARTIFACT_DIR']]) + + - name: Run Part 2 tests + id: run-part-2 + env: *test_env + timeout-minutes: ${{ inputs.part2-timeout-minutes }} + run: qa-tests-backend/scripts/run-part-2.sh + + - name: Run Part 2 post-test + if: always() && steps.run-part-2.outcome != 'skipped' && env.KUBECONFIG != '' + shell: python + run: | + import os, sys + sys.path.append('.openshift-ci') + from post_tests import PostClusterTest + post = PostClusterTest( + check_stackrox_logs=True, + artifact_destination_prefix="part-2", + ) + post.run([os.environ['ARTIFACT_DIR']]) + + - name: Run final-post + if: always() + shell: python + run: | + import sys + sys.path.append('.openshift-ci') + from post_tests import FinalPost + FinalPost(store_qa_tests_data=True).run() + + - name: Publish test summary + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: all + + - name: Report junit failures in jira + if: (!cancelled()) + id: junit2jira + uses: ./.github/actions/junit2jira + with: + create-jiras: ${{ github.event_name == 'push' }} + jira-user: ${{ secrets.JIRA_USER }} + jira-token: ${{ secrets.JIRA_TOKEN }} + gcp-account: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} + directory: ${{ env.ARTIFACT_DIR }} + + # CLUSTER_NAME is set before cluster creation, so it may be non-empty + # even when creation ultimately failed. We intentionally attempt teardown + # here anyway, just in case a cluster has been provisioned even though + # a failure during cluster creation has been surfaced. + - name: Delete cluster via direct-gke + if: always() && inputs.cluster-provisioning-method == 'direct-gke' + continue-on-error: true + run: | + if [[ -z "${CLUSTER_NAME:-}" ]]; then + echo "No cluster to teardown" + exit 0 + fi + if ! scripts/ci/gke.sh teardown_gke_cluster false; then + echo "::warning::WARNING: Could not delete the cluster ${CLUSTER_NAME}. See the step logs for details." + exit 6 + fi + + - name: Delete cluster via infra + if: always() && inputs.cluster-provisioning-method == 'infra' + continue-on-error: true + env: + INFRA_TOKEN: ${{ secrets.INFRA_TOKEN }} + run: | + infractl delete "${CLUSTER_NAME}" diff --git a/qa-tests-backend/scripts/run-part-1.sh b/qa-tests-backend/scripts/run-part-1.sh index 35b36700fba2c..ef446d76f608a 100755 --- a/qa-tests-backend/scripts/run-part-1.sh +++ b/qa-tests-backend/scripts/run-part-1.sh @@ -65,24 +65,19 @@ config_part_1() { central: pauseReconciliation: true resourceProfile: ci + portForwarding: false securedCluster: pauseReconciliation: true resourceProfile: ci EOF - if pr_has_label test-konflux-images; then + if [[ "${USE_KONFLUX_IMAGES:-false}" == "true" ]]; then + info "Environment contains USE_KONFLUX_IMAGES=true, will be using Konflux-built images for deploying StackRox" + patch_yaml "$config_file" ".roxie.konfluxImages = true" + elif pr_has_label test-konflux-images; then info "PR label 'test-konflux-images' detected, will be using Konflux-built images for deploying StackRox" patch_yaml "$config_file" ".roxie.konfluxImages = true" fi - if [[ "$(yq eval ".roxie.konfluxImages" "$config_file")" == "true" ]]; then - # Due to https://access.redhat.com/solutions/6540591 we need to patch the global pull secrets - # to be able to pull images after applying image-rewriting rules for downstream images. - # See https://docs.redhat.com/en/documentation/openshift_container_platform/4.12/html/images/ - # managing-images#images-update-global-pull-secret_using-image-pull-secrets. - # - # Can be removed once https://github.com/stackrox/roxie/pull/186 lands. - patch_global_openshift_pull_secret "quay.io/rhacs-eng" "${QUAY_RHACS_ENG_RO_USERNAME}" "${QUAY_RHACS_ENG_RO_PASSWORD}" - fi deploy_stackrox_with_roxie_compat "$config_file" setup_client_TLS_certs "$ROOT/$DEPLOY_DIR/client_TLS_certs" # Note: The traditional deployment path still references PodSecurityPolicies, @@ -105,29 +100,6 @@ EOF image_prefetcher_prebuilt_await } -patch_global_openshift_pull_secret() { - local registry="$1" - local username="$2" - local password="$3" - - info "Patching global OpenShift pull-secret to include credentials for ${registry}" - local tmp_pull_secret; tmp_pull_secret=$(mktemp) - - oc get secret/pull-secret \ - -n openshift-config \ - --template='{{index .data ".dockerconfigjson" | base64decode}}' \ - > "$tmp_pull_secret" - oc registry login \ - --registry="$registry" \ - --auth-basic="${username}:${password}" \ - --to="$tmp_pull_secret" - oc set data secret/pull-secret \ - -n openshift-config \ - --from-file=.dockerconfigjson="$tmp_pull_secret" - - rm -f "$tmp_pull_secret" -} - reuse_config_part_1() { info "Reusing config from a prior part 1 e2e test" diff --git a/qa-tests-backend/src/main/groovy/util/Env.groovy b/qa-tests-backend/src/main/groovy/util/Env.groovy index 76801ffe04106..22bd89dedee97 100644 --- a/qa-tests-backend/src/main/groovy/util/Env.groovy +++ b/qa-tests-backend/src/main/groovy/util/Env.groovy @@ -267,10 +267,6 @@ class Env { return mustGet("GOOGLE_CREDENTIALS_GCR_NO_ACCESS_KEY_V2") } - static String mustGetPagerdutyToken() { - return mustGet("PAGERDUTY_TOKEN") - } - static String mustGetSlackFixableVulnsChannel() { return mustGet("SLACK_FIXABLE_VULNS_CHANNEL") } diff --git a/qa-tests-backend/src/test/groovy/AuditScrubbingTest.groovy b/qa-tests-backend/src/test/groovy/AuditScrubbingTest.groovy index 30a12cba6779c..2d10175670e9c 100644 --- a/qa-tests-backend/src/test/groovy/AuditScrubbingTest.groovy +++ b/qa-tests-backend/src/test/groovy/AuditScrubbingTest.groovy @@ -58,12 +58,14 @@ class AuditScrubbingTest extends BaseSpecification { when: "A POST request is made to the ExchangeToken API" + def encodedUser = URLEncoder.encode(username, 'UTF-8') + def encodedPass = URLEncoder.encode(password, 'UTF-8') RestAssured.given() .relaxedHTTPSValidation() .body( JsonFormat.printer().print( AuthproviderService.ExchangeTokenRequest.newBuilder() - .setExternalToken("username=${username}&password=${password}") + .setExternalToken("username=${encodedUser}&password=${encodedPass}") .setState("${BASIC_AUTH_PROVIDER_ID}:${attemptId}") .setType("basic"))) .header("Referer", baseURL) @@ -83,6 +85,7 @@ class AuditScrubbingTest extends BaseSpecification { and: "Verify that audit log string representation does not contain the password" assert !auditLogEntry.toString().contains(password) + assert !auditLogEntry.toString().contains(encodedPass) where: "Data inputs are" diff --git a/scripts/ci/create-webhookserver.sh b/scripts/ci/create-webhookserver.sh index c08d0191fc887..b6d83f925a956 100755 --- a/scripts/ci/create-webhookserver.sh +++ b/scripts/ci/create-webhookserver.sh @@ -34,7 +34,7 @@ create_webhook_server_port_forward() { kubectl rollout status deployment webhookserver --namespace stackrox --timeout=5m --watch=true local log="${ARTIFACT_DIR:-/tmp}/webhook_server_port_forward.log" - nohup "${BASH_SOURCE[0]}" restart_webhook_server_port_forward "${pod}" 0<&- &> "${log}" & + nohup "${BASH_SOURCE[0]}" restart_webhook_server_port_forward 0<&- &> "${log}" & sleep 1 } diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index 3aff3621dfee2..b9d082c1f112c 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -28,7 +28,22 @@ ci_export() { local env_name="$1" local env_value="$2" - if command -v cci-export >/dev/null; then + if is_GITHUB_ACTIONS; then + export "${env_name}"="${env_value}" + if [[ -z "${GITHUB_ENV:-}" ]]; then + die "GITHUB_ENV is unset in the environment even though GITHUB_ACTION is set" + fi + if [[ "$env_value" == *$'\n'* ]]; then + # GHA also supports multi-line values, which need to be added to $GITHUB_ENV with a + # special syntax. + # See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#multiline-strings + local delimiter="EOF_${env_name}_$$" + printf '%s<<%s\n%s\n%s\n' "$env_name" "$delimiter" "$env_value" "$delimiter" >> "$GITHUB_ENV" + else + # GHA needs the environment variables to be unquoted and unescaped, they are parsed literally. + echo "${env_name}=${env_value}" >> "$GITHUB_ENV" + fi + elif command -v cci-export >/dev/null; then # cci-export writes to $BASH_ENV which defaults to read-only /etc/initial-bash.env in the CI container if [[ -n "${BASH_ENV:-}" && ! -w "${BASH_ENV}" ]]; then BASH_ENV=$(mktemp) @@ -38,10 +53,6 @@ ci_export() { else export "$env_name"="$env_value" fi - - if [[ -n "${GITHUB_ENV:-}" ]]; then - printf '%s=%q\n' "${env_name}" "${env_value}" >> "$GITHUB_ENV" - fi } # set_ci_shared_export() - for openshift-ci and GHA this is state shared between steps. @@ -576,7 +587,10 @@ poll_for_system_test_images() { local image_list image_list="$(mktemp)" populate_stackrox_image_list "${image_list}" - info "Will poll for: $(awk '{print $1}' "${image_list}")" + info "Will poll for the following images:" + while IFS=' ' read -r name tag; do + info " ${name}:${tag}" + done < "${image_list}" local start_time start_time="$(date '+%s')" @@ -630,7 +644,7 @@ _image_prefetcher_prebuilt_start() { # _image_prefetcher_prebuilt_await case "$CI_JOB_NAME" in - *qa-e2e-tests) + *qa-e2e-tests*) image_prefetcher_start_set qa-e2e _set_quay_pull_policy ;; @@ -681,7 +695,7 @@ _image_prefetcher_system_start() { case "$CI_JOB_NAME" in # ROX-24818: GKE is excluded from system image prefetch as it causes # flakes in test. - *-operator-e2e-tests|*ocp*qa-e2e-tests) + *-operator-e2e-tests|*ocp*qa-e2e-tests*) image_prefetcher_start_set stackrox-images ;; # Enabling scanner V4 installation tests as well, even though they also run on GKE, @@ -799,7 +813,7 @@ _image_prefetcher_prebuilt_await() { # at the last moment before any of the prebuilt images is used. (See other existing examples.) # This way we save time since prefetching can happen in parallel with whatever other setup the test job needs. - *qa-e2e-tests) + *qa-e2e-tests*) image_prefetcher_await_set qa-e2e ;; *nongroovy-e2e-tests) @@ -822,7 +836,7 @@ _image_prefetcher_system_await() { case "$CI_JOB_NAME" in # ROX-24818: GKE is excluded from system image prefetch as it causes # flakes in test. - *-operator-e2e-tests|*ocp*qa-e2e-tests) + *-operator-e2e-tests|*ocp*qa-e2e-tests*) image_prefetcher_await_set stackrox-images ;; # Enabling scanner V4 installation tests as well, even though they also run on GKE, @@ -982,9 +996,12 @@ populate_stackrox_image_list() { local tag tag="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" + + local operator_controller_tag + operator_controller_tag="$(BUILD_TAG="${tag}" make -C operator --quiet --no-print-directory tag)" + local operator_metadata_tag - operator_metadata_tag="$(echo "v${tag}" | sed 's,x,0,')" - local operator_controller_tag="${tag//x/0}" + operator_metadata_tag="v${operator_controller_tag}" # Require images based on the job case "$CI_JOB_NAME" in @@ -1033,6 +1050,37 @@ scanner-v4-db ${tag} roxctl ${tag} END ;; + *qa-e2e-tests*) + if [[ "${USE_KONFLUX_IMAGES:-false}" == "true" ]]; then + cat >> "${image_list}" << END +release-operator ${operator_controller_tag} +release-operator-bundle ${operator_metadata_tag} +release-main ${operator_controller_tag} +release-central-db ${operator_controller_tag} +release-collector ${operator_controller_tag} +release-fact ${operator_controller_tag} +release-scanner ${operator_controller_tag} +release-scanner-db ${operator_controller_tag} +release-scanner-v4 ${operator_controller_tag} +release-scanner-v4-db ${operator_controller_tag} +release-roxctl ${operator_controller_tag} +END + else + cat >> "${image_list}" << END +stackrox-operator ${operator_controller_tag} +stackrox-operator-bundle ${operator_metadata_tag} +main ${tag} +central-db ${tag} +collector ${tag} +fact ${tag} +scanner ${tag} +scanner-db ${tag} +scanner-v4 ${tag} +scanner-v4-db ${tag} +roxctl ${tag} +END + fi + ;; *) cat >> "${image_list}" << END central-db ${tag} @@ -2668,19 +2716,20 @@ _record_cluster_info() { # Assumes (a) there is a single cluster under test (cut_*) and (b) all nodes # in the cluster are homogeneous. - # Product version. Currently used for OpenShift version. Could cover cloud - # provider versions for example. - local oc_version - oc_version="$(oc version -o json 2>&1 || true)" - local openshiftVersion - openshiftVersion=$(jq -r <<<"$oc_version" '.openshiftVersion') - set_ci_shared_export "cut_product_version" "$openshiftVersion" + # Product version. Currently used for OpenShift version. + if command -v oc &>/dev/null; then + local oc_version + oc_version="$(oc version -o json 2>/dev/null || true)" + local openshiftVersion + openshiftVersion=$(jq -r <<<"$oc_version" '.openshiftVersion // empty') + set_ci_shared_export "cut_product_version" "$openshiftVersion" + fi # K8s version. local kubectl_version - kubectl_version="$(kubectl version -o json 2>&1 || true)" + kubectl_version="$(kubectl version -o json 2>/dev/null || true)" local serverGitVersion - serverGitVersion=$(jq -r <<<"$kubectl_version" '.serverVersion.gitVersion') + serverGitVersion=$(jq -r <<<"$kubectl_version" '.serverVersion.gitVersion // empty') set_ci_shared_export "cut_k8s_version" "$serverGitVersion" # Node info: OS, Kernel & Container Runtime. diff --git a/scripts/ci/store-artifacts.sh b/scripts/ci/store-artifacts.sh index b26fc301efb77..3ac6e6598e9ee 100755 --- a/scripts/ci/store-artifacts.sh +++ b/scripts/ci/store-artifacts.sh @@ -108,7 +108,7 @@ set_gs_path_vars() { GS_JOB_URL="${GS_URL}/${WORKFLOW_SUBDIR}/${JOB_SUBDIR}" elif is_GITHUB_ACTIONS; then WORKFLOW_SUBDIR="${GITHUB_REPOSITORY}/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - JOB_SUBDIR="${GITHUB_JOB}" + JOB_SUBDIR="${CI_JOB_NAME:-${GITHUB_JOB}}" GS_JOB_URL="${GS_URL}/${WORKFLOW_SUBDIR}/${JOB_SUBDIR}" else die "Support is missing for this CI environment" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 966289392a7d5..f415aeec34e54 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -148,31 +148,31 @@ deploy_stackrox_with_roxie() { # - wait_for_api (implicit) local roxie_envrc; roxie_envrc="$(mktemp)" + # Note, we use early-readiness=false here so that roxie waits until all workloads are ready. + # For Scanner V2 this means that it will also wait until vulnerabilities are loaded into the DB. roxie deploy \ + --early-readiness=false --central-wait=40m --secured-cluster-wait=40m \ --envrc "$roxie_envrc" \ --config "$config_file" # Persist and load (extended) roxie environment, mimicking the effect of ci_export in a more concise way. extend_roxie_envrc "$roxie_envrc" - if [[ -n "${BASH_ENV:-}" ]]; then - cat "$roxie_envrc" >> "$BASH_ENV" - fi # shellcheck source=/dev/null source "$roxie_envrc" + # Re-export every variable from the envrc via ci_export so that it is + # persisted correctly for subsequent GHA steps (GITHUB_ENV) or Prow + # (BASH_ENV). The envrc has already been sourced above, so we read + # the properly unquoted values from the current shell environment + # instead of trying to strip Go %q quoting from the file. + local var_name + while IFS= read -r envrc_line; do + var_name="${envrc_line#export }" # strip "export " prefix + var_name="${var_name%%=*}" # strip "=value" suffix + ci_export "$var_name" "${!var_name}" + done < <(grep '^export ' "$roxie_envrc") record_build_info "${central_namespace}" - # This implements something between roxie's (upcoming) `--early-readiness=true` and `--early-readiness=false`. - # It just waits for sensor and collector workloads to be up and running. - # We use the same mechanism here instead of `--early-readiness=false`, because the latter - # would also wait for scanner (v2), which takes an enormous amount of time to be properly initialized - # and we don't want to slow down this deployment path using roxie. - sensor_wait "$securedcluster_namespace" - wait_for_collectors_to_be_operational "$securedcluster_namespace" - if retrying_kubectl /dev/null 2>&1; then - wait_for_scanner_V4 "$central_namespace" - fi - touch "${STATE_DEPLOYED}" rm -f "$roxie_envrc" @@ -268,6 +268,7 @@ extend_roxie_envrc() { export CLUSTER="${CLUSTER}" export API_HOSTNAME="${API_HOSTNAME}" export API_PORT="${API_PORT}" +export ROX_USERNAME="admin" EOF } From 507124d9b63acd6a734ce5ca2a43b0e6b361b6a0 Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Sun, 23 Aug 2026 23:47:18 +0200 Subject: [PATCH 045/638] ROX-33973: Convert ACM UserPermissions data to PermissionSet objects (#22240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann Co-authored-by: Claude Opus 4.6 (1M context) --- .../permission_set_builder.go | 155 ++++++ .../permission_set_builder_test.go | 525 ++++++++++++++++++ 2 files changed, 680 insertions(+) create mode 100644 pkg/sac/externalrolebroker/permission_set_builder.go create mode 100644 pkg/sac/externalrolebroker/permission_set_builder_test.go diff --git a/pkg/sac/externalrolebroker/permission_set_builder.go b/pkg/sac/externalrolebroker/permission_set_builder.go new file mode 100644 index 0000000000000..9ec33ea44f94f --- /dev/null +++ b/pkg/sac/externalrolebroker/permission_set_builder.go @@ -0,0 +1,155 @@ +package externalrolebroker + +import ( + "strings" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/uuid" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" +) + +var ( + // Kubernetes verbs that map to READ_ACCESS + readVerbs = set.NewFrozenStringSet( + "get", + "list", + "watch", + ) + + // Kubernetes verbs that map to READ_WRITE_ACCESS + writeVerbs = set.NewFrozenStringSet( + "create", + "delete", + "deletecollection", + "patch", + "update", + ) +) + +// ConvertClusterRoleToPermissionSet converts the rules within a ClusterRoleDefinition +// to a storage.PermissionSet. +// +// The function maps: +// +// - Core Kubernetes resources (namespaces, secrets, serviceaccounts) to their ACS equivalents +// +// - RBAC resources (roles, clusterroles, rolebindings, clusterrolebindings) to their ACS equivalents +// +// - Stackrox API resources (*.api.stackrox.io) to their ACS equivalents +// +// - Read verbs (get, list, watch) to storage.Access_READ_ACCESS +// +// - Write verbs (create, update, patch, delete, deletecollection) to storage,Access_READ_WRITE_ACCESS +// +// - Wildcard verbs (*) to storage.Access_READ_WRITE_ACCESS (most permissive) +// +// Only supported resources that have ACS equivalents are included in the results. +// The returned PermissionSet has a generated ID and an empty name/description +func ConvertClusterRoleToPermissionSet( + clusterRoleDef clusterviewv1alpha1.ClusterRoleDefinition, +) *storage.PermissionSet { + resourceToAccess := make(map[string]storage.Access) + + for _, rule := range clusterRoleDef.Rules { + if len(rule.APIGroups) == 0 { + continue + } + // PermissionSet cannot represent name-scoped access, so skip these + // to avoid granting broader access than the rule intended. + if len(rule.ResourceNames) > 0 { + continue + } + + access := computeAccessLevel(rule.Verbs) + + for _, k8sResource := range rule.Resources { + // Subresource rules (e.g. "secrets/status") are narrower than + // base-resource rules; PermissionSet cannot represent that scope, + // so skip them to avoid granting broader access. + if strings.Contains(k8sResource, "/") { + continue + } + for _, apiGroup := range rule.APIGroups { + grantMatchingResources(resourceToAccess, k8sResource, apiGroup, access) + } + } + } + + return &storage.PermissionSet{ + Id: uuid.NewV4().String(), + ResourceToAccess: resourceToAccess, + } +} + +// grantMatchingResources finds all resourceMapping entries matching the +// resource/apiGroup pair (supporting wildcards in either dimension) and +// grants the given access level to each. +func grantMatchingResources( + accessMapping map[string]storage.Access, + resource string, apiGroup string, + access storage.Access, +) { + if resource == rbacWildcard || apiGroup == rbacWildcard { + for key, acsResource := range resourceMapping { + mappedResource, mappedGroup, _ := strings.Cut(key, ".") + if (resource == rbacWildcard || resource == mappedResource) && + (apiGroup == rbacWildcard || apiGroup == mappedGroup) { + grantAccessToResource(accessMapping, acsResource, access) + } + } + return + } + if acsResource, found := resourceMapping[qualifiedResource(resource, apiGroup)]; found { + grantAccessToResource(accessMapping, acsResource, access) + } +} + +func grantAccessToResource( + accessMapping map[string]storage.Access, + acsResource permissions.ResourceMetadata, + access storage.Access, +) { + key := string(acsResource.GetResource()) + if oldAccess, found := accessMapping[key]; found { + if oldAccess < access { + accessMapping[key] = access + } + } else { + accessMapping[key] = access + } +} + +func qualifiedResource(k8sResource string, apiGroup string) string { + return strings.Join([]string{k8sResource, apiGroup}, ".") +} + +func computeAccessLevel(verbs []string) storage.Access { + hasRead := false + hasWrite := false + + for _, verb := range verbs { + // Wildcard grants full access + if verb == rbacWildcard { + return storage.Access_READ_WRITE_ACCESS + } + + if readVerbs.Contains(verb) { + hasRead = true + } + if writeVerbs.Contains(verb) { + hasWrite = true + } + } + // If any write verb is present, grant READ_WRITE_ACCESS + if hasWrite { + return storage.Access_READ_WRITE_ACCESS + } + // If only read verbs are present, grant READ_ACCESS + if hasRead { + return storage.Access_READ_ACCESS + } + // If no recognized read or write verb, grant NO_ACCESS + return storage.Access_NO_ACCESS +} diff --git a/pkg/sac/externalrolebroker/permission_set_builder_test.go b/pkg/sac/externalrolebroker/permission_set_builder_test.go new file mode 100644 index 0000000000000..9ea0bc8b6950a --- /dev/null +++ b/pkg/sac/externalrolebroker/permission_set_builder_test.go @@ -0,0 +1,525 @@ +package externalrolebroker + +import ( + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/sac/resources" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" +) + +func TestConvertClusterRoleToPermissionSet(t *testing.T) { + tests := map[string]struct { + clusterRoleDef clusterviewv1alpha1.ClusterRoleDefinition + expectedPermissions map[string]storage.Access + unexpectedResources []string + minPermissionCount int + }{ + "read-only namespace access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Namespace.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "write access to secrets": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create", "update", "delete"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Secret.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "read and write access to roles": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + Verbs: []string{"get", "list", "create", "update"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.K8sRole.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "read-only access to clusterroles": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.K8sRole.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "read and write access to clusterrolebindings": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterrolebindings"}, + Verbs: []string{"*"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.K8sRoleBinding.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "multiple resources with different access levels": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create", "delete"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Namespace.GetResource()): storage.Access_READ_ACCESS, + string(resources.Secret.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.K8sRole.GetResource()): storage.Access_READ_ACCESS, + string(resources.K8sRoleBinding.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "wildcard verbs grant full access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"*"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Secret.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "wildcard resources grant access to all base resources": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"*"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + minPermissionCount: 5, // All 5 base resources + expectedPermissions: map[string]storage.Access{ + string(resources.Namespace.GetResource()): storage.Access_READ_ACCESS, + string(resources.Secret.GetResource()): storage.Access_READ_ACCESS, + string(resources.ServiceAccount.GetResource()): storage.Access_READ_ACCESS, + string(resources.K8sRole.GetResource()): storage.Access_READ_ACCESS, + string(resources.K8sRoleBinding.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "subresource rules produce no access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets/status"}, + Verbs: []string{"get", "update"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{}, + }, + "non-base resources are ignored": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments", "statefulsets"}, + Verbs: []string{"get", "list", "create"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods", "configmaps"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{}, + }, + "mixed base and non-base resources": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods", "secrets", "configmaps"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Secret.GetResource()): storage.Access_READ_ACCESS, + }, + unexpectedResources: []string{"Pod", "ConfigMap"}, + }, + "escalating permissions on same resource": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create", "delete"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Secret.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "all base resources with full access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces", "secrets", "serviceaccounts"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"*"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Namespace.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.Secret.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.ServiceAccount.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.K8sRole.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.K8sRoleBinding.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "unknown verbs grant no access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"unknown", "invalid"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Secret.GetResource()): storage.Access_NO_ACCESS, + }, + }, + "empty rules": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{}, + }, + expectedPermissions: map[string]storage.Access{}, + }, + "rules with wrong API group are ignored": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"secrets"}, // Right resource, wrong API group + Verbs: []string{"get"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{}, + }, + "read-only access to alerts": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"alerts"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Alert.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "read-write access to deployments": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"deployments"}, + Verbs: []string{"get", "list", "create", "update", "delete"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Deployment.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "access to multiple StackRox API resources": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"compliance", "detection"}, + Verbs: []string{"get", "list"}, + }, + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"networkgraphs"}, + Verbs: []string{"*"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Compliance.GetResource()): storage.Access_READ_ACCESS, + string(resources.Detection.GetResource()): storage.Access_READ_ACCESS, + string(resources.NetworkGraph.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "access to vulnerability management resources": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"api.stackrox.io"}, + Resources: []string{"vulnerabilitymanagementrequests", "vulnerabilitymanagementapprovals"}, + Verbs: []string{"get", "list", "create"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.VulnerabilityManagementRequests.GetResource()): storage.Access_READ_WRITE_ACCESS, + string(resources.VulnerabilityManagementApprovals.GetResource()): storage.Access_READ_WRITE_ACCESS, + }, + }, + "named resource rules produce no access": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{"my-secret"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{}, + }, + "wildcard resources with specific API group": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"*"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.K8sRole.GetResource()): storage.Access_READ_ACCESS, + string(resources.K8sRoleBinding.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "specific resource with wildcard API group": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"nodes"}, + Verbs: []string{"get"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{ + string(resources.Node.GetResource()): storage.Access_READ_ACCESS, + }, + }, + "StackRox API resources with wrong API group are ignored": { + clusterRoleDef: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"alerts", "deployments"}, + Verbs: []string{"get"}, + }, + }, + }, + expectedPermissions: map[string]storage.Access{}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + permSet := ConvertClusterRoleToPermissionSet(tc.clusterRoleDef) + + // Verify PermissionSet structure + require.NotNil(t, permSet) + assert.NotEmpty(t, permSet.GetId(), "PermissionSet should have a generated ID") + assert.NotNil(t, permSet.GetResourceToAccess()) + + // Check minimum permission count if specified + if tc.minPermissionCount > 0 { + assert.GreaterOrEqual(t, len(permSet.GetResourceToAccess()), tc.minPermissionCount, + "Expected at least %d permissions", tc.minPermissionCount) + } + + // Verify expected permissions + for resource, expectedAccess := range tc.expectedPermissions { + actualAccess, exists := permSet.GetResourceToAccess()[resource] + assert.True(t, exists, "Expected resource %q to be present", resource) + assert.Equal(t, expectedAccess, actualAccess, + "Expected %s access for resource %q, got %s", + expectedAccess, resource, actualAccess) + } + + // Verify unexpected resources are not present + for _, resource := range tc.unexpectedResources { + _, exists := permSet.GetResourceToAccess()[resource] + assert.False(t, exists, "Did not expect resource %q to be present", resource) + } + + // If no min count is specified, verify exact match (including empty). + if tc.minPermissionCount == 0 { + assert.Len(t, permSet.GetResourceToAccess(), len(tc.expectedPermissions), + "Expected exactly %d permissions", len(tc.expectedPermissions)) + } + }) + } +} + +func TestComputeAccessLevel(t *testing.T) { + tests := map[string]struct { + verbs []string + expectedAccess storage.Access + }{ + "read-only verbs": { + verbs: []string{"get", "list", "watch"}, + expectedAccess: storage.Access_READ_ACCESS, + }, + "write verbs": { + verbs: []string{"create", "update", "delete"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "mixed read and write verbs": { + verbs: []string{"get", "list", "create"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "wildcard verb": { + verbs: []string{"*"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "wildcard with other verbs": { + verbs: []string{"get", "*", "list"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "patch verb": { + verbs: []string{"patch"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "deletecollection verb": { + verbs: []string{"deletecollection"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + "unknown verbs": { + verbs: []string{"unknown", "invalid"}, + expectedAccess: storage.Access_NO_ACCESS, + }, + "empty verbs": { + verbs: []string{}, + expectedAccess: storage.Access_NO_ACCESS, + }, + "only get": { + verbs: []string{"get"}, + expectedAccess: storage.Access_READ_ACCESS, + }, + "only create": { + verbs: []string{"create"}, + expectedAccess: storage.Access_READ_WRITE_ACCESS, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + access := computeAccessLevel(tc.verbs) + assert.Equal(t, tc.expectedAccess, access, + "Expected %s for verbs %v, got %s", + tc.expectedAccess, tc.verbs, access) + }) + } +} + +func TestConvertClusterRoleToPermissionSet_Integration(t *testing.T) { + // This test simulates a realistic cluster-admin-like role + clusterRoleDef := clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces", "secrets", "serviceaccounts"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, + }, + }, + } + + permSet := ConvertClusterRoleToPermissionSet(clusterRoleDef) + + require.NotNil(t, permSet) + assert.NotEmpty(t, permSet.GetId()) + assert.Len(t, permSet.GetResourceToAccess(), 5, "Should have all 5 base resources") + + // All resources should have READ_WRITE_ACCESS + for _, resource := range []string{ + string(resources.Namespace.GetResource()), + string(resources.Secret.GetResource()), + string(resources.ServiceAccount.GetResource()), + string(resources.K8sRole.GetResource()), + string(resources.K8sRoleBinding.GetResource()), + } { + access, exists := permSet.GetResourceToAccess()[resource] + assert.True(t, exists, "Expected resource %q to exist", resource) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, access, + "Expected READ_WRITE_ACCESS for %q", resource) + } +} From 06c0bd8fbaf242a57dbac52aee9914b241f5bd86 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:23:59 -0500 Subject: [PATCH 046/638] chore(ui): Refactor vulnerabilty reports routes and folders (#22411) --- .../VulnerabilityReporting.helpers.js | 6 +-- .../platform/src/Containers/MainPage/Body.tsx | 6 +-- .../MainPage/Navigation/NavigationSidebar.tsx | 5 ++- .../View/ImageVulnerabilityFiltersView.tsx | 0 ...eVulnerabilityFiltersViewForCollection.tsx | 0 .../View/ImageVulnerabilityReportView.tsx | 0 .../View/ImageVulnerabilityResourcesView.tsx | 0 .../Wizard/ImageVulnerabilityReportWizard.tsx | 4 +- .../ImageVulnerabilityReportWizardPage.tsx | 6 +-- .../Wizard/Step2/CollectionScopeSelect.tsx | 0 .../Wizard/Step2/ImageTypeSelect.tsx | 0 .../Step2/ImageVulnerabilityResourcesStep.tsx | 0 .../Step3/ImageVulnerabilityFiltersSince.tsx | 0 .../Step3/ImageVulnerabilityFiltersStep.tsx | 0 ...eVulnerabilityFiltersStepForCollection.tsx | 2 +- .../Step5/ImageVulnerabilityReviewStep.tsx | 0 ...ageVulnerabilityReportValidationSchemas.ts | 0 .../imageVulnerabilityReports.defaults.ts | 4 +- .../imageVulnerabilityReports.types.ts | 0 .../imageVulnerabilityReports.utils.ts | 4 +- .../NodeConfigurationsList.tsx} | 12 +++--- .../NodeVulnerabilityReportsLayout.tsx} | 38 +++++++++---------- .../NodeVulnerabilityReportsPage.tsx} | 20 +++++----- .../ViewVulnReport/ReportJobs.tsx | 2 +- .../ViewVulnReport/ViewVulnReportPage.tsx | 10 ++--- .../VulnReportingPage.tsx | 10 ++--- .../VulnReports/ConfigReportsTab.tsx | 6 +-- .../VulnReports/VulnReportingLayout.tsx | 9 ++--- .../pathsForVulnerabilityReporting.ts | 4 +- .../Deployment/DeploymentPage.tsx | 2 +- .../WorkloadCves/Image/ImagePage.tsx | 2 +- .../Overview/WorkloadCvesOverviewPage.tsx | 2 +- .../components/CreateViewBasedReportModal.tsx | 7 +--- ui/apps/platform/src/routePaths.ts | 15 ++++---- 34 files changed, 86 insertions(+), 90 deletions(-) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/View/ImageVulnerabilityReportView.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx (98%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx (98%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step2/CollectionScopeSelect.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step2/ImageTypeSelect.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx (99%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/Step5/ImageVulnerabilityReviewStep.tsx (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts (91%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts (100%) rename ui/apps/platform/src/Containers/Vulnerabilities/{ => Reports}/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts (98%) rename ui/apps/platform/src/Containers/Vulnerabilities/{NodeVulnerabilityReports/NodeConfigReportsTab.tsx => Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx} (97%) rename ui/apps/platform/src/Containers/Vulnerabilities/{NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx => Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx} (60%) rename ui/apps/platform/src/Containers/Vulnerabilities/{NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx => Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx} (50%) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/VulnerabilityReporting.helpers.js b/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/VulnerabilityReporting.helpers.js index ca8680f75f925..24a0ad1bc620e 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/VulnerabilityReporting.helpers.js +++ b/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/VulnerabilityReporting.helpers.js @@ -45,9 +45,9 @@ const routeMatcherMapForWizardStep2 = { }, }; -export const vulnerabilityReportsBasePath = '/main/vulnerabilities/reports'; -export const vulnerabilityReportsConfigurationPath = `${vulnerabilityReportsBasePath}/configuration`; -export const vulnerabilityReportsViewBasedPath = `${vulnerabilityReportsBasePath}/view-based`; +export const vulnerabilityReportsBasePath = '/main/vulnerabilities/reports/images'; +export const vulnerabilityReportsConfigurationPath = `${vulnerabilityReportsBasePath}/configurations`; +export const vulnerabilityReportsViewBasedPath = `${vulnerabilityReportsBasePath}/view-based-jobs`; // visit diff --git a/ui/apps/platform/src/Containers/MainPage/Body.tsx b/ui/apps/platform/src/Containers/MainPage/Body.tsx index 5bd1480d5e434..3fe90ac788863 100644 --- a/ui/apps/platform/src/Containers/MainPage/Body.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Body.tsx @@ -52,8 +52,8 @@ import { vulnerabilitiesUserWorkloadsPath, vulnerabilitiesVirtualMachineCvesPath, vulnerabilitiesWorkloadCvesPath, + vulnerabilityImageReportsPath, vulnerabilityNodeReportsPath, - vulnerabilityReportsPath, } from 'routePaths'; import type { RouteKey } from 'routePaths'; @@ -295,7 +295,7 @@ const routeComponentMap: Record = { 'vulnerabilities/node-reports': { component: asyncComponent( () => - import('Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage') + import('Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage') ), path: vulnerabilityNodeReportsPath, }, @@ -303,7 +303,7 @@ const routeComponentMap: Record = { component: asyncComponent( () => import('Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage') ), - path: vulnerabilityReportsPath, + path: vulnerabilityImageReportsPath, }, 'vulnerability-management': { component: asyncComponent(() => import('Containers/VulnMgmt/WorkflowLayout')), diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx index c7b13a6adb01b..65689c174401c 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx @@ -46,7 +46,7 @@ import { vulnerabilitiesUserWorkloadsPath, vulnerabilitiesViewPath, vulnerabilitiesVirtualMachineCvesPath, - vulnerabilityReportsPath, + vulnerabilityImageReportsPath, } from 'routePaths'; import NavigationContent from './NavigationContent'; @@ -98,7 +98,8 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc { type: 'link', content: 'Reports', - path: vulnerabilityReportsPath, + // TODO: point to the vulnerability reports overview page once it exists. + path: vulnerabilityImageReportsPath, routeKey: 'vulnerabilities/reports', }, { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityReportView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityReportView.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityReportView.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityReportView.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx similarity index 98% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx index 3f6b18abb558a..673e8d2167e56 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx @@ -23,7 +23,7 @@ import type { ImageVulnerabilityReportConfigurationForCollection, ImageVulnerabilityReportConfigurationForEntity, } from 'services/ReportsService.types'; -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; import { getValueForCvesSince } from '../imageVulnerabilityReports.utils'; @@ -138,7 +138,7 @@ function ImageVulnerabilityReportWizard({ navigate(-1); } else { // Go to page for new report. - navigate(`${vulnerabilityConfigurationReportsPath}/${data.id}`); + navigate(`${vulnerabilityConfigurationsReportsPath}/${data.id}`); } }) .catch((errorCaught) => { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx similarity index 98% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx index f94a5f9e0de99..4b56c365f3e35 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx @@ -37,12 +37,12 @@ import { getValueByCaseInsensitiveKey, searchValueAsArray, } from 'utils/searchUtils'; -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; import { attributesSeparateFromConfigForImageVulnerabilityReport, searchFilterConfigForImageVulnerabilityReport, -} from '../../searchFilterConfig'; +} from '../../../searchFilterConfig'; import { defaultImageVulnerabilityReportConfiguration } from '../imageVulnerabilityReports.defaults'; @@ -176,7 +176,7 @@ function ImageVulnerabilityReportWizardPage({ <> - + Image vulnerability reports {heading} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/CollectionScopeSelect.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/CollectionScopeSelect.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/CollectionScopeSelect.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/CollectionScopeSelect.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/ImageTypeSelect.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageTypeSelect.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/ImageTypeSelect.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageTypeSelect.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx similarity index 99% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx index 5abc7f98f8308..ccb8107eda1c8 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx @@ -8,7 +8,7 @@ import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; import type { OnSearchPayload } from 'Components/CompoundSearchFilter/types'; import { updateSearchFilter } from 'Components/CompoundSearchFilter/utils/utils'; -import { attributeForSeverityInBackendAndViewBasedReport } from '../../../searchFilterConfig'; +import { attributeForSeverityInBackendAndViewBasedReport } from '../../../../searchFilterConfig'; import type { ImageVulnerabilityFiltersConfigurationForCollection } from '../../imageVulnerabilityReports.types'; import { attributeForFixabilityInFiltersStepForCollection, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step5/ImageVulnerabilityReviewStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step5/ImageVulnerabilityReviewStep.tsx similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/Step5/ImageVulnerabilityReviewStep.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step5/ImageVulnerabilityReviewStep.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts similarity index 91% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts index 84410a28adb3f..d291e9182035c 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts @@ -4,8 +4,8 @@ import type { } from 'services/ReportsService.types'; import { getRequestQueryStringForSearchFilter } from 'utils/searchUtils'; -import { defaultStorage } from '../WorkloadCves/Overview/WorkloadCvesOverviewPage'; -import { fixableStatusToFixability, severityLabelToSeverity } from '../utils/searchUtils'; +import { defaultStorage } from '../../WorkloadCves/Overview/WorkloadCvesOverviewPage'; +import { fixableStatusToFixability, severityLabelToSeverity } from '../../utils/searchUtils'; const { preferences: { defaultFilters }, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts similarity index 100% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts similarity index 98% rename from ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts index b52ce41d6ffcf..fb3879a2f9537 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts @@ -16,7 +16,7 @@ import { searchValueAsArray, } from 'utils/searchUtils'; import { ensureExhaustive } from 'utils/type.utils'; -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; import type { ImageVulnerabilityFiltersConfiguration, @@ -195,5 +195,5 @@ export const imageTypeLabelMap: Record = { // 3. from search filter to request query (save report configuration) export function createScheduledReportForImageVulnerabilitiesURL(searchFilter: SearchFilter) { const query = getUrlQueryStringForSearchFilter(searchFilter); - return `${vulnerabilityConfigurationReportsPath}?action=createFromFilters&${query}`; + return `${vulnerabilityConfigurationsReportsPath}?action=createFromFilters&${query}`; } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx similarity index 97% rename from ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx index 11b2412fa98c1..ccb6b8578eec5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx @@ -23,8 +23,8 @@ import useURLPagination from 'hooks/useURLPagination'; import useURLSearch from 'hooks/useURLSearch'; import useURLSort from 'hooks/useURLSort'; import { - vulnerabilityNodeConfigurationReportDetailsPath, - vulnerabilityNodeConfigurationReportsPath, + vulnerabilityNodeConfigurationsReportsDetailsPath, + vulnerabilityNodeConfigurationsReportsPath, } from 'routePaths'; import { fetchNodeReportConfigurations, @@ -34,7 +34,7 @@ import { getTableUIState } from 'utils/getTableUIState'; const CreateReportsButton = () => { return ( - + ); @@ -47,7 +47,7 @@ const sortOptions = { defaultSortOption: { field: reportNameSearchKey, direction: 'asc' } as const, }; -function NodeConfigReportsTab() { +function NodeConfigurationsList() { const { hasReadWriteAccess, hasReadAccess } = usePermissions(); const hasWriteAccessForReport = hasReadWriteAccess('WorkflowAdministration') && hasReadAccess('Integration'); @@ -181,7 +181,7 @@ function NodeConfigReportsTab() { renderer={({ data }) => data.map((report) => { const reportURL = generatePath( - vulnerabilityNodeConfigurationReportDetailsPath, + vulnerabilityNodeConfigurationsReportsDetailsPath, { reportId: report.id, } @@ -223,4 +223,4 @@ function NodeConfigReportsTab() { ); } -export default NodeConfigReportsTab; +export default NodeConfigurationsList; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx similarity index 60% rename from ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx index cb18ddab84e19..e3390e19183f0 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx @@ -4,20 +4,20 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom-v5-compat'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; import { - vulnerabilityNodeConfigurationReportsPath, - vulnerabilityNodeViewBasedReportsPath, + vulnerabilityNodeConfigurationsReportsPath, + vulnerabilityNodeViewBasedJobsPath, } from 'routePaths'; -function NodeVulnerabilityReportingLayout() { +function NodeVulnerabilityReportsLayout() { const location = useLocation(); const navigate = useNavigate(); const { hasReadAccess } = usePermissions(); - const hasAccessForConfiguration = hasReadAccess('WorkflowAdministration'); + const hasAccessForConfigurations = hasReadAccess('WorkflowAdministration'); - const activeKey = location.pathname.startsWith(vulnerabilityNodeConfigurationReportsPath) - ? 'configuration' - : 'view-based'; + const activeKey = location.pathname.startsWith(vulnerabilityNodeConfigurationsReportsPath) + ? 'configurations' + : 'view-based-jobs'; return ( <> @@ -30,32 +30,32 @@ function NodeVulnerabilityReportingLayout() { activeKey={activeKey} onSelect={(_event, tabKey) => { navigate( - tabKey === 'configuration' - ? vulnerabilityNodeConfigurationReportsPath - : vulnerabilityNodeViewBasedReportsPath + tabKey === 'configurations' + ? vulnerabilityNodeConfigurationsReportsPath + : vulnerabilityNodeViewBasedJobsPath ); }} usePageInsets > - {hasAccessForConfiguration && ( + {hasAccessForConfigurations && ( )} @@ -64,4 +64,4 @@ function NodeVulnerabilityReportingLayout() { ); } -export default NodeVulnerabilityReportingLayout; +export default NodeVulnerabilityReportsLayout; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx similarity index 50% rename from ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx index 7aa347acbfe00..18e114e123394 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeVulnerabilityReports/NodeVulnerabilityReportingPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx @@ -2,31 +2,31 @@ import { Navigate, Route, Routes } from 'react-router-dom-v5-compat'; import usePermissions from 'hooks/usePermissions'; -import NodeConfigReportsTab from './NodeConfigReportsTab'; -import NodeVulnerabilityReportingLayout from './NodeVulnerabilityReportingLayout'; +import NodeConfigurationsList from './Configurations/NodeConfigurationsList'; +import NodeVulnerabilityReportsLayout from './NodeVulnerabilityReportsLayout'; -function NodeVulnerabilityReportingPage() { +function NodeVulnerabilityReportsPage() { const { hasReadAccess } = usePermissions(); const hasAccessForConfiguration = hasReadAccess('WorkflowAdministration'); return ( - }> - } /> + }> + } /> + ) : ( - + ) } /> - View-based reports} /> + View-based reports} /> ); } -export default NodeVulnerabilityReportingPage; +export default NodeVulnerabilityReportsPage; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ReportJobs.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ReportJobs.tsx index 694a64bf6f093..c64d1b7bd9b3a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ReportJobs.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ReportJobs.tsx @@ -47,7 +47,7 @@ import { runStates } from 'types/reportJob'; import type { RunState } from 'types/reportJob'; import ReportJobStatus from 'Components/ReportJob/ReportJobStatus'; -import ImageVulnerabilityReportView from '../../ImageVulnerabilityReports/View/ImageVulnerabilityReportView'; +import ImageVulnerabilityReportView from '../../Reports/ImageVulnerabilityReports/View/ImageVulnerabilityReportView'; import { attributesSeparateFromConfigForImageVulnerabilityReport, searchFilterConfigForImageVulnerabilityReport, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx index c0c78befa2059..d7dfc98dfb506 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx @@ -19,7 +19,7 @@ import { Title, } from '@patternfly/react-core'; -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; import DeleteModal from 'Components/PatternFly/DeleteModal'; import PageTitle from 'Components/PageTitle'; @@ -35,7 +35,7 @@ import ReportJobsHelpAction from 'Components/ReportJob/ReportJobsHelpAction'; import type { JobContextTab } from 'Components/ReportJob/types'; import { ensureJobContextTab } from 'Components/ReportJob/utils'; -import ImageVulnerabilityReportView from '../../ImageVulnerabilityReports/View/ImageVulnerabilityReportView'; +import ImageVulnerabilityReportView from '../../Reports/ImageVulnerabilityReports/View/ImageVulnerabilityReportView'; import { attributesSeparateFromConfigForImageVulnerabilityReport, searchFilterConfigForImageVulnerabilityReport, @@ -80,7 +80,7 @@ function ViewVulnReportPage() { deleteResults, } = useDeleteModal({ onCompleted: () => { - navigate(vulnerabilityConfigurationReportsPath); + navigate(vulnerabilityConfigurationsReportsPath); }, }); @@ -113,7 +113,7 @@ function ViewVulnReportPage() { title="Error fetching the report configuration" message={fetchError || 'No data available'} actionText="Go to reports" - url={vulnerabilityConfigurationReportsPath} + url={vulnerabilityConfigurationsReportsPath} /> ); } @@ -153,7 +153,7 @@ function ViewVulnReportPage() { - + Image vulnerability reports {reportConfiguration.name} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx index ccee7874305e0..a5668edb5ba68 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage.tsx @@ -5,7 +5,7 @@ import type { ReportPageAction } from 'Components/Reports/reports.types'; import usePageAction from 'hooks/usePageAction'; import usePermissions from 'hooks/usePermissions'; -import ImageVulnerabilityReportWizardPage from '../ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage'; +import ImageVulnerabilityReportWizardPage from '../Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage'; import ViewVulnReportPage from './ViewVulnReport/ViewVulnReportPage'; import VulnReportingLayout from './VulnReports/VulnReportingLayout'; @@ -27,14 +27,14 @@ function VulnReportingPage() { path="/" element={ } /> {isReportConfigurationEnabled && ( )} - } /> + } /> } />
); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx index 03994c92dc9a5..2fdfa788bd82b 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx @@ -24,7 +24,7 @@ import { import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { ExclamationCircleIcon, FileIcon, SearchIcon } from '@patternfly/react-icons'; -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; import useIsRouteEnabled from 'hooks/useIsRouteEnabled'; import usePermissions from 'hooks/usePermissions'; import useURLPagination from 'hooks/useURLPagination'; @@ -47,7 +47,7 @@ import useAuthStatus from 'hooks/useAuthStatus'; import { reportDownloadURL } from 'services/ReportsService'; import type { ImageVulnerabilityReportConfiguration } from 'services/ReportsService.types'; -import type { ImageVulnerabilityResourceScope } from '../../ImageVulnerabilityReports/imageVulnerabilityReports.types'; +import type { ImageVulnerabilityResourceScope } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types'; import useFetchReports from '../api/useFetchReports'; import useRunReport from '../api/useRunReport'; import { useWatchLastSnapshotForReports } from '../api/useWatchLastSnapshotForReports'; @@ -65,7 +65,7 @@ function isResourceScopeAbsent({ resourceScope }: ImageVulnerabilityReportConfig const CreateReportsButton = () => { return ( - + ); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx index ed726dcb21c72..f7dc672c26248 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx @@ -3,10 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; -import { - vulnerabilityConfigurationReportsPath, - vulnerabilityViewBasedReportsPath, -} from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath, vulnerabilityViewBasedJobsPath } from 'routePaths'; import ConfigReportsTab from './ConfigReportsTab'; import ViewBasedReportsTab from './ViewBasedReportsTab'; @@ -24,7 +21,7 @@ function VulnReportingLayout() { { id: 'report-configuration', title: 'Report configurations', - path: vulnerabilityConfigurationReportsPath, + path: vulnerabilityConfigurationsReportsPath, content: , }, ] @@ -32,7 +29,7 @@ function VulnReportingLayout() { { id: 'view-based-reports', title: 'View-based reports', - path: vulnerabilityViewBasedReportsPath, + path: vulnerabilityViewBasedJobsPath, content: , }, ]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts index e8b090d693838..652070c8615f0 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts @@ -1,3 +1,3 @@ -import { vulnerabilityConfigurationReportsPath } from 'routePaths'; +import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; -export const vulnerabilityConfigurationReportDetailsPath = `${vulnerabilityConfigurationReportsPath}/:reportId`; +export const vulnerabilityConfigurationReportDetailsPath = `${vulnerabilityConfigurationsReportsPath}/:reportId`; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx index 85e602d4f41c1..d5b5e5efaf2ef 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx @@ -31,7 +31,7 @@ import { getRegexScopedQueryString, parseQuerySearchFilter } from '../../utils/s import DeploymentPageResources from './DeploymentPageResources'; import DeploymentPageVulnerabilities from './DeploymentPageVulnerabilities'; import DeploymentPageDetails from './DeploymentPageDetails'; -import { createScheduledReportForImageVulnerabilitiesURL } from '../../ImageVulnerabilityReports/imageVulnerabilityReports.utils'; +import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; import CreateReportDropdown from '../components/CreateReportDropdown'; import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx index 226d26f49b32b..313d8edeedfc6 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx @@ -56,7 +56,7 @@ import { getImageBaseNameDisplay } from '../utils/images'; import { getRegexScopedQueryString, parseQuerySearchFilter } from '../../utils/searchUtils'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; import type { defaultColumns as deploymentResourcesDefaultColumns } from './DeploymentResourceTable'; -import { createScheduledReportForImageVulnerabilitiesURL } from '../../ImageVulnerabilityReports/imageVulnerabilityReports.utils'; +import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import CreateReportDropdown from '../components/CreateReportDropdown'; import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx index de581e93a88dd..5ff471d3d1cd4 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx @@ -65,7 +65,7 @@ import DefaultFilterModal from '../components/DefaultFilterModal'; import CreateReportDropdown from '../components/CreateReportDropdown'; import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; import { imageListQuery } from '../Tables/ImageOverviewTable'; -import { createScheduledReportForImageVulnerabilitiesURL } from '../../ImageVulnerabilityReports/imageVulnerabilityReports.utils'; +import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import useHasRequestExceptionsAbility from '../../hooks/useHasRequestExceptionsAbility'; import VulnerabilitiesOverview from './VulnerabilitiesOverview'; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx index 3625ac08040c5..321c118671849 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx @@ -4,7 +4,7 @@ import { Modal } from '@patternfly/react-core/deprecated'; import { Link } from 'react-router-dom'; import { runViewBasedReport } from 'services/ReportsService'; -import { vulnerabilityViewBasedReportsPath } from 'routePaths'; +import { vulnerabilityViewBasedJobsPath } from 'routePaths'; import useAnalytics, { VIEW_BASED_REPORT_GENERATED } from 'hooks/useAnalytics'; type Message = @@ -132,10 +132,7 @@ function CreateViewBasedReportModal({ variant="link" isInline component={(props) => ( - + )} > View status in reports table diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index f688a185caad4..e8dd3be7b80b7 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -99,13 +99,14 @@ export const vulnerabilitiesImagesWithoutCvesPath = `${vulnerabilitiesBasePath}/ export const vulnerabilitiesViewPath = `${vulnerabilitiesBasePath}/results/:viewTemplate/:viewId`; export const vulnerabilityReportsPath = `${vulnerabilitiesBasePath}/reports`; -export const vulnerabilityConfigurationReportsPath = `${vulnerabilityReportsPath}/configuration`; -export const vulnerabilityViewBasedReportsPath = `${vulnerabilityReportsPath}/view-based`; - -export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/node`; -export const vulnerabilityNodeConfigurationReportsPath = `${vulnerabilityNodeReportsPath}/configuration`; -export const vulnerabilityNodeConfigurationReportDetailsPath = `${vulnerabilityNodeConfigurationReportsPath}/:reportId`; -export const vulnerabilityNodeViewBasedReportsPath = `${vulnerabilityNodeReportsPath}/view-based`; +export const vulnerabilityImageReportsPath = `${vulnerabilityReportsPath}/images`; +export const vulnerabilityConfigurationsReportsPath = `${vulnerabilityImageReportsPath}/configurations`; +export const vulnerabilityViewBasedJobsPath = `${vulnerabilityImageReportsPath}/view-based-jobs`; + +export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/nodes`; +export const vulnerabilityNodeConfigurationsReportsPath = `${vulnerabilityNodeReportsPath}/configurations`; +export const vulnerabilityNodeConfigurationsReportsDetailsPath = `${vulnerabilityNodeConfigurationsReportsPath}/:reportId`; +export const vulnerabilityNodeViewBasedJobsPath = `${vulnerabilityNodeReportsPath}/view-based-jobs`; // Vulnerability Management 1.0 path for links from Dashboard: From 1af72f3d75f8b436b6b4eb5ebac9419539b4da4a Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 04:04:32 +0000 Subject: [PATCH 047/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to d6819ce (#22413) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 5baff41e3f74b..9f07200935c6a 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index db546bbe5ede2..751e04259fbe8 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index ea988575b6639..bf850548048d0 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 8dcfc450a1db2..c0cb4afdac167 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 9797991fd5eb7..2cf18c446b124 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 92af5f053f26f..1d49c60af112e 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index ef6002780997e..71af227a56948 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 8c8e1ea0be9c9..83074834a4ae4 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:2dd16eecb17524ecebf322d75505f37f584cf0a84703d9bbaf5bebda59b78dc7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 - name: kind value: task resolver: bundles From 7fcb30a64855fe12a041cabcc99783aabe91b4bb Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:15:24 +0200 Subject: [PATCH 048/638] ROX-36383: Enable VM scanning by default (#22331) --- CHANGELOG.md | 1 + central/clusters/deployer.go | 9 ++- central/clusters/deployer_test.go | 10 ++++ deploy/common/k8sbased.sh | 9 ++- .../feature-flag-values.yaml.htpl | 10 ++-- .../defaults/30-base-config.yaml.htpl | 4 +- .../templates/sensor.yaml.htpl | 4 +- operator/api/v1alpha1/securedcluster_types.go | 27 ++------- .../api/v1alpha1/zz_generated.deepcopy.go | 5 -- .../platform.stackrox.io_securedclusters.yaml | 13 +---- .../rhacs-operator.clusterserviceversion.yaml | 12 +--- .../platform.stackrox.io_securedclusters.yaml | 13 +---- .../rhacs-operator.clusterserviceversion.yaml | 18 ++---- .../securedcluster/defaults/static.go | 3 +- .../values/translation/translation.go | 10 ---- .../values/translation/translation_test.go | 57 +++++-------------- pkg/env/virtualmachine.go | 2 +- pkg/features/list.go | 3 +- .../helmtest/virtual-machines.test.yaml | 23 ++++---- tests/e2e/lib-compat.sh | 15 ----- tests/e2e/lib.sh | 8 +-- ...d-cluster-cr-with-scanner-v4.envsubst.yaml | 1 - .../e2e/yaml/secured-cluster-cr.envsubst.yaml | 1 - tests/vm_scanning_suite_test.go | 4 +- .../virtualMachineCvesOverviewPage.test.ts | 2 +- .../virtualMachinePage.test.ts | 8 ++- 26 files changed, 86 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e37a324239dd..a3d6937293fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Added Features +- ROX-32148: Virtual machine scanning is now enabled by default. RHEL VMs created with OpenShift Virtualization can be scanned for package vulnerabilities after installing roxagent in the guest. - ROX-34997: The Central CR now supports `spec.central.rolloutStrategy` (`Recreate` or `RollingUpdate`) to configure the central deployment rollout strategy. Default remains `Recreate`. - ROX-35181: Administrative events are now exposed as configurable custom Prometheus metrics (`rox_central_admin_event_*`), aggregated by Type, Level, Domain, ResourceType, and ResourceName. Requires permission to read Administration resource, globally scoped. - ROX-35545: Added ACL change as a file access operation for runtime policies. diff --git a/central/clusters/deployer.go b/central/clusters/deployer.go index 7aafce48cb341..8e01487cf2385 100644 --- a/central/clusters/deployer.go +++ b/central/clusters/deployer.go @@ -177,11 +177,10 @@ func getBaseMetaValues(c *storage.Cluster, imageFlavor *defaults.ImageFlavor, ch } func getFeatureFlagsAsManifestBundleEnv() map[string]string { - // For the environment variables we need to filter out ROX_SCANNER_V4, because it would - // wrongly enable Scanner V4 delegated scanning on secured clusters which are set up - // using manifest bundles. But delegated scanning is not supported for manifest bundle - // installed secured clusters. - skipFeatureFlags := set.NewFrozenStringSet("ROX_SCANNER_V4") + // ROX_SCANNER_V4 would enable delegated scanning, which bundles do not support. + // ROX_VIRTUAL_MACHINES is already set from virtualMachines.enabled; a duplicate + // env name would make kubectl apply fail to patch an existing Sensor Deployment. + skipFeatureFlags := set.NewFrozenStringSet("ROX_SCANNER_V4", "ROX_VIRTUAL_MACHINES") featureFlagVals := make(map[string]string) for _, feature := range features.Flags { envVar := feature.EnvVar() diff --git a/central/clusters/deployer_test.go b/central/clusters/deployer_test.go index 8b15d537962cd..c0b7ed70d8ef2 100644 --- a/central/clusters/deployer_test.go +++ b/central/clusters/deployer_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/helm/charts" "github.com/stackrox/rox/pkg/images/defaults" flavorUtils "github.com/stackrox/rox/pkg/images/defaults/testutils" @@ -312,6 +313,15 @@ func (s *deployerTestSuite) TestFieldsFromClusterAndRenderOpts() { } +// TestGetFeatureFlagsAsManifestBundleEnv_OmitsChartOwnedFlags checks that +// certain flags (normally already set from chart config) are not also injected as EnvVars. +func TestGetFeatureFlagsAsManifestBundleEnv_OmitsChartOwnedFlags(t *testing.T) { + t.Parallel() + env := getFeatureFlagsAsManifestBundleEnv() + assert.NotContains(t, env, features.ScannerV4.EnvVar()) + assert.NotContains(t, env, features.VirtualMachines.EnvVar()) +} + func TestRequiredFieldsArePresent(t *testing.T) { testutils.SetExampleVersion(t) diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index 33018eb4b4b8a..c2ad604ff3eef 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -912,11 +912,12 @@ function launch_sensor { fi if [[ "${ROX_VIRTUAL_MACHINES:-}" == "true" ]]; then - # Enables Sensor VSOCK RBAC and ROX_VIRTUAL_MACHINES on Sensor. extra_helm_config+=(--set "virtualMachines.enabled=true") - # Shorten pull-mode scraper cadence for VM e2e (production default is 5m). + # Shorten pull-mode scraper cadence for VM e2e (production default is 4h). # Floor is 1m (vmscraper.clampPollInterval); values below that are raised to 1m. extra_helm_config+=(--set "customize.envVars.ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL=${ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL:-1m}") + elif [[ "${ROX_VIRTUAL_MACHINES:-}" == "false" ]]; then + extra_helm_config+=(--set "virtualMachines.enabled=false") fi if [[ -n "$LOGLEVEL" ]]; then @@ -1063,6 +1064,10 @@ function launch_sensor { sensor_env+=("ROX_NETFLOW_CACHE_LIMITING=${ROX_NETFLOW_CACHE_LIMITING}") fi + if [[ -n "${ROX_VIRTUAL_MACHINES:-}" ]]; then + sensor_env+=("ROX_VIRTUAL_MACHINES=${ROX_VIRTUAL_MACHINES}") + fi + if [[ "${#sensor_env[@]}" -gt 0 ]]; then kubectl -n "${sensor_namespace}" set env deploy/sensor "${sensor_env[@]}" fi diff --git a/image/templates/helm/stackrox-secured-cluster/feature-flag-values.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/feature-flag-values.yaml.htpl index 4757ca8d638e8..5e36d45d6e007 100644 --- a/image/templates/helm/stackrox-secured-cluster/feature-flag-values.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/feature-flag-values.yaml.htpl @@ -1,10 +1,8 @@ [<- if .FeatureFlags >] -[] -[<- $skipFeatureFlags := list "ROX_SCANNER_V4" >] +[] +[<- $skipFeatureFlags := list "ROX_SCANNER_V4" "ROX_VIRTUAL_MACHINES" >] envVars: [<- $featureFlags := .FeatureFlags >] [<- range $flagName := keys $featureFlags | sortAlpha >] diff --git a/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl index 7a2c052b2337a..02280536c4b4f 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl @@ -118,11 +118,11 @@ network: enableNetworkPolicies: true virtualMachines: - enabled: false + enabled: [< .FeatureFlags.ROX_VIRTUAL_MACHINES >] scraper: concurrency: 20 maxResponseSizeKB: 16384 - pollInterval: "5m" + pollInterval: "4h" enableOpenShiftMonitoring: false diff --git a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl index fc1d08ed9cedd..b09d522a51afd 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl @@ -221,9 +221,9 @@ spec: - name: ROX_OPENSHIFT_API value: "true" {{- end }} - {{- if ._rox.virtualMachines.enabled }} - name: ROX_VIRTUAL_MACHINES - value: "true" + value: {{ quote ._rox.virtualMachines.enabled }} + {{- if ._rox.virtualMachines.enabled }} - name: ROX_VIRTUAL_MACHINES_SCRAPER_CONCURRENCY value: {{ quote ._rox.virtualMachines.scraper.concurrency }} - name: ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB diff --git a/operator/api/v1alpha1/securedcluster_types.go b/operator/api/v1alpha1/securedcluster_types.go index 50e0c056f3a0f..c04023ee039ff 100644 --- a/operator/api/v1alpha1/securedcluster_types.go +++ b/operator/api/v1alpha1/securedcluster_types.go @@ -116,7 +116,7 @@ type SecuredClusterSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName=ProcessIndicators,order=17,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:advanced"} ProcessIndicators *ProcessIndicatorsSpec `json:"processIndicators,omitempty"` - // Settings for virtual machine scanning (VSOCK RBAC and scraper). + // Settings for the virtual machine scraper. //+operator-sdk:csv:customresourcedefinitions:type=spec,order=18,displayName="Virtual Machines Settings" VirtualMachines *VirtualMachinesSpec `json:"virtualMachines,omitempty"` } @@ -545,32 +545,13 @@ func (v ProcessIndicatorConfigSwitch) Pointer() *ProcessIndicatorConfigSwitch { return &v } -// VirtualMachinesSpec configures VM scanning and VSOCK RBAC. +// VirtualMachinesSpec configures virtual machine scanning. type VirtualMachinesSpec struct { - // Whether virtual machine scanning and VSOCK RBAC are enabled. - // The default is: Disabled. - //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:select:Enabled","urn:alm:descriptor:com.tectonic.ui:select:Disabled"} - Mode *VirtualMachinesMode `json:"mode,omitempty"` - // Settings for the virtual machine scraper running in Sensor. - //+operator-sdk:csv:customresourcedefinitions:type=spec,order=2 + //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1 Scraper *VirtualMachinesScraperSpec `json:"scraper,omitempty"` } -// VirtualMachinesMode is the type for spec.virtualMachines.mode. -// +kubebuilder:validation:Enum=Enabled;Disabled -type VirtualMachinesMode string - -const ( - VirtualMachinesModeEnabled VirtualMachinesMode = "Enabled" - VirtualMachinesModeDisabled VirtualMachinesMode = "Disabled" -) - -// Pointer returns the given mode value as a pointer, needed in k8s resource structs. -func (v VirtualMachinesMode) Pointer() *VirtualMachinesMode { - return &v -} - // VirtualMachinesScraperSpec tunes the virtual machine scraper. type VirtualMachinesScraperSpec struct { // Maximum number of virtual machines scraped concurrently in each poll cycle. @@ -589,7 +570,7 @@ type VirtualMachinesScraperSpec struct { // How often the scraper polls virtual machines for new reports. // Minimum interval is 1m; shorter values are rounded up. // Shorter intervals refresh scans more often but increase the load on kubevirt API server. - // The default is: 5m. + // The default is: 4h. //+kubebuilder:validation:Pattern=`^[0-9]+(m|h)$` //+operator-sdk:csv:customresourcedefinitions:type=spec,order=3 PollInterval *string `json:"pollInterval,omitempty"` diff --git a/operator/api/v1alpha1/zz_generated.deepcopy.go b/operator/api/v1alpha1/zz_generated.deepcopy.go index af410a3c694d9..d0774f2e62b88 100644 --- a/operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/operator/api/v1alpha1/zz_generated.deepcopy.go @@ -2025,11 +2025,6 @@ func (in *VirtualMachinesScraperSpec) DeepCopy() *VirtualMachinesScraperSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachinesSpec) DeepCopyInto(out *VirtualMachinesSpec) { *out = *in - if in.Mode != nil { - in, out := &in.Mode, &out.Mode - *out = new(VirtualMachinesMode) - **out = **in - } if in.Scraper != nil { in, out := &in.Scraper, &out.Scraper *out = new(VirtualMachinesScraperSpec) diff --git a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml index f6d944598bb7b..3de0b1b37d921 100644 --- a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml @@ -1884,17 +1884,8 @@ spec: type: array type: object virtualMachines: - description: Settings for virtual machine scanning (VSOCK RBAC and - scraper). + description: Settings for the virtual machine scraper. properties: - mode: - description: |- - Whether virtual machine scanning and VSOCK RBAC are enabled. - The default is: Disabled. - enum: - - Enabled - - Disabled - type: string scraper: description: Settings for the virtual machine scraper running in Sensor. @@ -1919,7 +1910,7 @@ spec: How often the scraper polls virtual machines for new reports. Minimum interval is 1m; shorter values are rounded up. Shorter intervals refresh scans more often but increase the load on kubevirt API server. - The default is: 5m. + The default is: 4h. pattern: ^[0-9]+(m|h)$ type: string type: object diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index 5737c974e0cbd..e7a65a48e9f35 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -1066,7 +1066,7 @@ spec: path: processIndicators x-descriptors: - urn:alm:descriptor:com.tectonic.ui:advanced - - description: Settings for virtual machine scanning (VSOCK RBAC and scraper). + - description: Settings for the virtual machine scraper. displayName: Virtual Machines Settings path: virtualMachines - description: |- @@ -1607,14 +1607,6 @@ spec: - description: Allows you to specify additional trusted Root CAs. displayName: Additional CAs path: tls.additionalCAs - - description: |- - Whether virtual machine scanning and VSOCK RBAC are enabled. - The default is: Disabled. - displayName: Mode - path: virtualMachines.mode - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:select:Enabled - - urn:alm:descriptor:com.tectonic.ui:select:Disabled - description: Settings for the virtual machine scraper running in Sensor. displayName: Scraper path: virtualMachines.scraper @@ -1633,7 +1625,7 @@ spec: How often the scraper polls virtual machines for new reports. Minimum interval is 1m; shorter values are rounded up. Shorter intervals refresh scans more often but increase the load on kubevirt API server. - The default is: 5m. + The default is: 4h. displayName: Poll Interval path: virtualMachines.scraper.pollInterval statusDescriptors: diff --git a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml index e6992fb604ca3..237fe377697b9 100644 --- a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml @@ -1883,17 +1883,8 @@ spec: type: array type: object virtualMachines: - description: Settings for virtual machine scanning (VSOCK RBAC and - scraper). + description: Settings for the virtual machine scraper. properties: - mode: - description: |- - Whether virtual machine scanning and VSOCK RBAC are enabled. - The default is: Disabled. - enum: - - Enabled - - Disabled - type: string scraper: description: Settings for the virtual machine scraper running in Sensor. @@ -1918,7 +1909,7 @@ spec: How often the scraper polls virtual machines for new reports. Minimum interval is 1m; shorter values are rounded up. Shorter intervals refresh scans more often but increase the load on kubevirt API server. - The default is: 5m. + The default is: 4h. pattern: ^[0-9]+(m|h)$ type: string type: object diff --git a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml index 6304b985d2ac0..a88f178dc19bd 100644 --- a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml @@ -1136,14 +1136,9 @@ spec: * for new installations starting with ACS 4.8, the default is: AutoSense. displayName: Scanner V4 component path: scannerV4.scannerComponent - - description: |- - Whether virtual machine scanning and VSOCK RBAC are enabled. - The default is: Disabled. - displayName: Mode - path: virtualMachines.mode - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:select:Enabled - - urn:alm:descriptor:com.tectonic.ui:select:Disabled + - description: Settings for the virtual machine scraper running in Sensor. + displayName: Scraper + path: virtualMachines.scraper - description: |- Maximum number of virtual machines scraped concurrently in each poll cycle. The default is: 20. @@ -1227,9 +1222,6 @@ spec: The default is: 3. displayName: Default Replicas path: scannerV4.indexer.scaling.replicas - - description: Settings for the virtual machine scraper running in Sensor. - displayName: Scraper - path: virtualMachines.scraper - description: |- Maximum response size in KB from a virtual machine agent. Responses larger than this are rejected and that virtual machine is skipped until the next poll. @@ -1306,7 +1298,7 @@ spec: How often the scraper polls virtual machines for new reports. Minimum interval is 1m; shorter values are rounded up. Shorter intervals refresh scans more often but increase the load on kubevirt API server. - The default is: 5m. + The default is: 4h. displayName: Poll Interval path: virtualMachines.scraper.pollInterval - description: |- @@ -1426,7 +1418,7 @@ spec: path: processIndicators x-descriptors: - urn:alm:descriptor:com.tectonic.ui:advanced - - description: Settings for virtual machine scanning (VSOCK RBAC and scraper). + - description: Settings for the virtual machine scraper. displayName: Virtual Machines Settings path: virtualMachines - description: |- diff --git a/operator/internal/securedcluster/defaults/static.go b/operator/internal/securedcluster/defaults/static.go index 50400eb29344b..4ec1a560d3122 100644 --- a/operator/internal/securedcluster/defaults/static.go +++ b/operator/internal/securedcluster/defaults/static.go @@ -77,11 +77,10 @@ var staticDefaults = platform.SecuredClusterSpec{ ExcludeOpenshiftNs: platform.ProcessIndicatorConfigDisabled.Pointer(), }, VirtualMachines: &platform.VirtualMachinesSpec{ - Mode: platform.VirtualMachinesModeDisabled.Pointer(), Scraper: &platform.VirtualMachinesScraperSpec{ Concurrency: new(int32(20)), MaxResponseSizeKB: new(int32(16384)), - PollInterval: new("5m"), + PollInterval: new("4h"), }, }, } diff --git a/operator/internal/securedcluster/values/translation/translation.go b/operator/internal/securedcluster/values/translation/translation.go index 7b7775466adb5..5f2515c699b24 100644 --- a/operator/internal/securedcluster/values/translation/translation.go +++ b/operator/internal/securedcluster/values/translation/translation.go @@ -623,16 +623,6 @@ func getVirtualMachinesValues(vm *platform.VirtualMachinesSpec) *translation.Val return nil } cv := translation.NewValuesBuilder() - if vm.Mode != nil { - switch *vm.Mode { - case platform.VirtualMachinesModeEnabled: - cv.SetBoolValue("enabled", true) - case platform.VirtualMachinesModeDisabled: - cv.SetBoolValue("enabled", false) - default: - return cv.SetError(fmt.Errorf("invalid virtual machines mode %q", *vm.Mode)) - } - } // Scraper is always set by static defaulting; omit helm values when unset. if vm.Scraper != nil { sv := translation.NewValuesBuilder() diff --git a/operator/internal/securedcluster/values/translation/translation_test.go b/operator/internal/securedcluster/values/translation/translation_test.go index d2d28393f425a..5f8d7524da55a 100644 --- a/operator/internal/securedcluster/values/translation/translation_test.go +++ b/operator/internal/securedcluster/values/translation/translation_test.go @@ -1075,16 +1075,14 @@ func (s *TranslationTestSuite) TestTranslate() { }, }, }, - "virtual machines enabled": { + // getVirtualMachinesValues(nil) omits the virtualMachines helm key. + "virtual machines spec nil": { args: args{ client: newDefaultFakeClient(t), sc: platform.SecuredCluster{ ObjectMeta: metav1.ObjectMeta{Namespace: "stackrox"}, Spec: platform.SecuredClusterSpec{ ClusterName: new("test-cluster"), - VirtualMachines: &platform.VirtualMachinesSpec{ - Mode: platform.VirtualMachinesModeEnabled.Pointer(), - }, }, }, }, @@ -1105,12 +1103,11 @@ func (s *TranslationTestSuite) TestTranslate() { "enabled": true, }, }, - "virtualMachines": map[string]interface{}{ - "enabled": true, - }, }, }, - "virtual machines disabled": { + // Scraper knobs are forwarded; Helm virtualMachines.enabled is omitted + // so it follows ROX_VIRTUAL_MACHINES. + "virtual machines scraper defaults": { args: args{ client: newDefaultFakeClient(t), sc: platform.SecuredCluster{ @@ -1118,7 +1115,11 @@ func (s *TranslationTestSuite) TestTranslate() { Spec: platform.SecuredClusterSpec{ ClusterName: new("test-cluster"), VirtualMachines: &platform.VirtualMachinesSpec{ - Mode: platform.VirtualMachinesModeDisabled.Pointer(), + Scraper: &platform.VirtualMachinesScraperSpec{ + Concurrency: new(int32(20)), + MaxResponseSizeKB: new(int32(16384)), + PollInterval: new("4h"), + }, }, }, }, @@ -1141,38 +1142,10 @@ func (s *TranslationTestSuite) TestTranslate() { }, }, "virtualMachines": map[string]interface{}{ - "enabled": false, - }, - }, - }, - // Unreachable in production: static.go's defaulting flow always fills in - // Mode: Disabled before translate() runs. Kept to pin translate()'s own - // nil-handling in isolation. - "virtual machines unset": { - args: args{ - client: newDefaultFakeClient(t), - sc: platform.SecuredCluster{ - ObjectMeta: metav1.ObjectMeta{Namespace: "stackrox"}, - Spec: platform.SecuredClusterSpec{ - ClusterName: new("test-cluster"), - }, - }, - }, - want: chartutil.Values{ - "clusterName": "test-cluster", - "ca": map[string]string{"cert": "ca central content"}, - "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, - "monitoring": map[string]interface{}{ - "openshift": map[string]interface{}{ - "enabled": true, + "scraper": map[string]interface{}{ + "concurrency": int32(20), + "maxResponseSizeKB": int32(16384), + "pollInterval": "4h", }, }, }, @@ -1185,7 +1158,6 @@ func (s *TranslationTestSuite) TestTranslate() { Spec: platform.SecuredClusterSpec{ ClusterName: new("test-cluster"), VirtualMachines: &platform.VirtualMachinesSpec{ - Mode: platform.VirtualMachinesModeEnabled.Pointer(), Scraper: &platform.VirtualMachinesScraperSpec{ Concurrency: new(int32(5)), MaxResponseSizeKB: new(int32(1024)), @@ -1213,7 +1185,6 @@ func (s *TranslationTestSuite) TestTranslate() { }, }, "virtualMachines": map[string]interface{}{ - "enabled": true, "scraper": map[string]interface{}{ "concurrency": int32(5), "maxResponseSizeKB": int32(1024), diff --git a/pkg/env/virtualmachine.go b/pkg/env/virtualmachine.go index 8bb5899cd1b48..8d10dc903303e 100644 --- a/pkg/env/virtualmachine.go +++ b/pkg/env/virtualmachine.go @@ -58,7 +58,7 @@ var ( // VirtualMachinesScraperPollInterval defines how often the pull-mode scraper // polls VMs for new reports. - VirtualMachinesScraperPollInterval = registerDurationSetting("ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL", 5*time.Minute) + VirtualMachinesScraperPollInterval = registerDurationSetting("ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL", 4*time.Hour) // VirtualMachinesScraperTickInterval is how often VMScraper considers due VMs. // Independent of retry backoff so operators can slow the ticker without diff --git a/pkg/features/list.go b/pkg/features/list.go index a0a22bb13d91c..d3cf172eb4f14 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -190,7 +190,8 @@ var ( // This must be set in Scanner V4 Indexer to have any effect. ScannerV4MavenSearch = registerFeature("Enables Scanner V4 to reach out to ROX_SCANNER_V4_MAVEN_SEARCH_URL for additional information about Java packages", "ROX_SCANNER_V4_MAVEN_SEARCH") - VirtualMachines = registerFeature("Enables virtual machine management", "ROX_VIRTUAL_MACHINES") + // VirtualMachines gates VM inventory, scanning, and VSOCK RBAC in Sensor. + VirtualMachines = registerFeature("Enables virtual machine management", "ROX_VIRTUAL_MACHINES", enabled) // ScannerV4StoreExternalIndexReports enables storing index reports from delegated scans to Central's Scanner V4 Indexer. ScannerV4StoreExternalIndexReports = registerFeature("Enables storing index reports from delegated scans to Central's Scanner V4 Indexer", "ROX_SCANNER_V4_STORE_EXTERNAL_INDEX_REPORTS", enabled) diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/virtual-machines.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/virtual-machines.test.yaml index 1dd127096f1b0..7e9c158080dbf 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/virtual-machines.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/virtual-machines.test.yaml @@ -3,14 +3,15 @@ values: allowNone: true tests: -- name: "virtual machine VSOCK RBAC and env var are absent by default" +- name: "virtual machine scanning is enabled by default" expect: | - .clusterroles["stackrox:vsock-access"] | assertThat(. == null) - .clusterrolebindings["stackrox:vsock-access-binding"] | assertThat(. == null) - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES"] | assertThat(. == null) - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_CONCURRENCY"] | assertThat(. == null) - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB"] | assertThat(. == null) - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == null) + .clusterroles["stackrox:vsock-access"] | assertThat(. != null) + .clusterrolebindings["stackrox:vsock-access-binding"] | assertThat(. != null) + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES"] | assertThat(. == "true") + [container(.deployments.sensor; "sensor") | .env[] | select(.name == "ROX_VIRTUAL_MACHINES")] | length | assertThat(. == 1) + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_CONCURRENCY"] | assertThat(. == "20") + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB"] | assertThat(. == "16384") + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == "4h") - name: "virtual machine VSOCK RBAC and env var are created when enabled" set: @@ -19,9 +20,10 @@ tests: .clusterroles["stackrox:vsock-access"] | assertThat(. != null) .clusterrolebindings["stackrox:vsock-access-binding"] | assertThat(. != null) envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES"] | assertThat(. == "true") + [container(.deployments.sensor; "sensor") | .env[] | select(.name == "ROX_VIRTUAL_MACHINES")] | length | assertThat(. == 1) envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_CONCURRENCY"] | assertThat(. == "20") envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB"] | assertThat(. == "16384") - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == "5m") + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == "4h") - name: "virtual machine scraper knobs can be customized when enabled" set: @@ -34,13 +36,14 @@ tests: envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB"] | assertThat(. == "1024") envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == "1m") -- name: "virtual machine VSOCK RBAC and env var are absent when explicitly disabled" +- name: "virtual machine VSOCK RBAC is absent and the feature flag is off when explicitly disabled" set: virtualMachines.enabled: false expect: | .clusterroles["stackrox:vsock-access"] | assertThat(. == null) .clusterrolebindings["stackrox:vsock-access-binding"] | assertThat(. == null) - envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES"] | assertThat(. == null) + envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES"] | assertThat(. == "false") + [container(.deployments.sensor; "sensor") | .env[] | select(.name == "ROX_VIRTUAL_MACHINES")] | length | assertThat(. == 1) envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_CONCURRENCY"] | assertThat(. == null) envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_PULL_MAX_RESPONSE_SIZE_KB"] | assertThat(. == null) envVars(.deployments.sensor; "sensor")["ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL"] | assertThat(. == null) diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index c7983564ed3f9..2bb556537bcec 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -343,27 +343,12 @@ handle_file_activity_monitoring() { handle_virtual_machines_configuration() { local config_file="$1" - local virtual_machines="${ROX_VIRTUAL_MACHINES:-false}" local poll_interval="${ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL:-1m}" - local vm_mode_setting - - case "$virtual_machines" in - true) - vm_mode_setting="Enabled" - ;; - false) - vm_mode_setting="Disabled" - ;; - *) - die "Unsupported value for ROX_VIRTUAL_MACHINES: ${ROX_VIRTUAL_MACHINES}" - ;; - esac merge_yaml "$config_file" < { sortByTableHeader('Virtual machine'); cy.wait(`@${listVirtualMachinesAlias}`).then((interception) => { const { url } = interception.request; - expect(url).to.include('Virtual Machine Name'); + expect(decodeURIComponent(url)).to.include('Virtual Machine Name'); }); }); }); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts index 37c1c3c0689d0..9ac3268dbb262 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts @@ -45,7 +45,9 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { }); describe('Vulnerabilities tab', () => { - it('should render CVE rows from fixture data', () => { + // TODO(ROX-36464): intercepts do not isolate live VM CVE data, so row-count + // assertions fail when scanning is on. + it.skip('should render CVE rows from fixture data', () => { visitWithFixture(); cy.get('tbody tr:not([class*="expandable"])').should('have.length', 3); @@ -62,7 +64,9 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { }); }); - it('should display the correct number of affected components', () => { + // TODO(ROX-36464): intercepts do not isolate live VM CVE data, so row-count + // assertions fail when scanning is on. + it.skip('should display the correct number of affected components', () => { visitWithFixture(); cy.get('tbody tr:not([class*="expandable"])') From 44581415c2f2291c218ba9fc9319adbf2291c1ba Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Mon, 24 Aug 2026 07:27:41 -0700 Subject: [PATCH 049/638] ROX-31694: Fake file activity generation (#22238) Co-authored-by: Giles Hutton Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Piotr Rygielski <114479+vikin91@users.noreply.github.com> --- scale/workloads/file-activity-scale.yaml | 28 ++ scale/workloads/long-running.yaml | 5 + scale/workloads/sample.yaml | 11 + sensor/common/events/fake_file_activity.go | 21 ++ sensor/common/filesystem/pipeline/pipeline.go | 37 +++ sensor/common/pubsub/consumerid.go | 2 + sensor/common/pubsub/laneid.go | 2 + sensor/common/pubsub/topic.go | 2 + sensor/kubernetes/fake/fake.go | 11 + sensor/kubernetes/fake/fileactivity.go | 207 +++++++++++++ sensor/kubernetes/fake/fileactivity_test.go | 293 ++++++++++++++++++ sensor/kubernetes/fake/pools.go | 41 ++- sensor/kubernetes/fake/process.go | 7 +- sensor/kubernetes/fake/workload.go | 12 + sensor/kubernetes/main.go | 3 + sensor/kubernetes/sensor/pubsub.go | 1 + sensor/kubernetes/sensor/sensor.go | 1 + 17 files changed, 675 insertions(+), 9 deletions(-) create mode 100644 scale/workloads/file-activity-scale.yaml create mode 100644 sensor/common/events/fake_file_activity.go create mode 100644 sensor/kubernetes/fake/fileactivity.go create mode 100644 sensor/kubernetes/fake/fileactivity_test.go diff --git a/scale/workloads/file-activity-scale.yaml b/scale/workloads/file-activity-scale.yaml new file mode 100644 index 0000000000000..19bf8faf9406c --- /dev/null +++ b/scale/workloads/file-activity-scale.yaml @@ -0,0 +1,28 @@ +nodeWorkload: + numNodes: 10 + +numNamespaces: 3 + +deploymentWorkload: +- deploymentType: Deployment + numDeployments: 100 + numLabels: 5 + lifecycleDuration: 30m + updateInterval: 5m + podWorkload: + numContainers: 2 + numPods: 3 + lifecycleDuration: 10m + processWorkload: + processInterval: 30s + alertRate: 0 + +networkWorkload: + flowInterval: 60s + batchSize: 50 + +fileActivityWorkload: + activityInterval: 50ms + batchSize: 100 + numPaths: 500 + nodeEventPercent: 50 diff --git a/scale/workloads/long-running.yaml b/scale/workloads/long-running.yaml index 752089da52dce..c83eec051d1e0 100755 --- a/scale/workloads/long-running.yaml +++ b/scale/workloads/long-running.yaml @@ -24,3 +24,8 @@ rbacWorkload: numBindings: 1000 numRoles: 1000 numServiceAccounts: 1000 +fileActivityWorkload: + activityInterval: 50ms + batchSize: 10 + numPaths: 500 + nodeEventPercent: 50 diff --git a/scale/workloads/sample.yaml b/scale/workloads/sample.yaml index 3a5158b0e182a..05c0acecd67ce 100755 --- a/scale/workloads/sample.yaml +++ b/scale/workloads/sample.yaml @@ -81,3 +81,14 @@ virtualMachineWorkload: # Delay before the first index report for each VM (±20% max deviation applied) initialReportDelay: 10s +# File activity workload generates synthetic file access events (requires ROX_SENSITIVE_FILE_ACTIVITY=true) +fileActivityWorkload: + # Every time the specified interval is reached... + activityInterval: 1s + # ...create this many random file access events + batchSize: 100 + # Number of unique file paths to generate events for + numPaths: 200 + # Percentage of events generated as node-level (0-100), remainder are deployment-level + nodeEventPercent: 50 + diff --git a/sensor/common/events/fake_file_activity.go b/sensor/common/events/fake_file_activity.go new file mode 100644 index 0000000000000..790d4d4ef6aee --- /dev/null +++ b/sensor/common/events/fake_file_activity.go @@ -0,0 +1,21 @@ +package events + +import ( + sensorAPI "github.com/stackrox/rox/generated/internalapi/sensor" + "github.com/stackrox/rox/sensor/common/pubsub" +) + +// FakeFileActivityEvent wraps a file activity message from fake workloads. +// This allows fake workloads to publish file activities through the pubsub system rather +// than writing directly to a shared channel. +type FakeFileActivityEvent struct { + Activity *sensorAPI.FileActivity +} + +func (e *FakeFileActivityEvent) Topic() pubsub.Topic { + return pubsub.FakeFileActivityTopic +} + +func (e *FakeFileActivityEvent) Lane() pubsub.LaneID { + return pubsub.FakeFileActivityLane +} diff --git a/sensor/common/filesystem/pipeline/pipeline.go b/sensor/common/filesystem/pipeline/pipeline.go index 5b64a52078aec..8050c6df20ad9 100644 --- a/sensor/common/filesystem/pipeline/pipeline.go +++ b/sensor/common/filesystem/pipeline/pipeline.go @@ -16,6 +16,7 @@ import ( "github.com/stackrox/rox/sensor/common/clusterentities" "github.com/stackrox/rox/sensor/common/detector" detectorMetrics "github.com/stackrox/rox/sensor/common/detector/metrics" + "github.com/stackrox/rox/sensor/common/events" "github.com/stackrox/rox/sensor/common/metrics" "github.com/stackrox/rox/sensor/common/processsignal" "github.com/stackrox/rox/sensor/common/pubsub" @@ -86,6 +87,18 @@ func NewFileSystemPipeline(detector detector.Detector, clusterEntities *clustere p.wg.Add(1) go p.cleanupExpiredBuffers() } + + // Register consumer for fake file activities from fake workloads + if err := pubSubDispatcher.RegisterConsumerToLane( + pubsub.FakeFileActivityConsumer, + pubsub.FakeFileActivityTopic, + pubsub.FakeFileActivityLane, + p.handleFakeFileActivityEvent, + ); err != nil { + log.Errorf("Failed to register consumer for fake file activities: %v", err) + } else { + log.Debug("File system pipeline registered consumer for fake file activities") + } } if p.pubSubDispatcher == nil { @@ -344,6 +357,30 @@ func (p *Pipeline) processEnrichedIndicator(event pubsub.Event) error { return nil } +func (p *Pipeline) handleFakeFileActivityEvent(event pubsub.Event) error { + select { + case <-p.stopper.Flow().StopRequested(): + return nil + default: + } + + fakeEvent, ok := event.(*events.FakeFileActivityEvent) + if !ok { + log.Errorf("File system pipeline received unexpected event type for fake file activity: %T", event) + return fmt.Errorf("unexpected event type: %T", event) + } + + if fakeEvent.Activity == nil { + return nil + } + + // Process fake file activity directly instead of writing to activityChan. + // This avoids potential panic on shutdown when the channel is closed by filesystemService. + // The activityChan is owned by the gRPC service, which is its only writer. + p.processFileActivity(fakeEvent.Activity) + return nil +} + func (p *Pipeline) processFileActivity(fs *sensorAPI.FileActivity) { process := fs.GetProcess() if process == nil { diff --git a/sensor/common/pubsub/consumerid.go b/sensor/common/pubsub/consumerid.go index a7d8d11f76901..122c6741b36ed 100644 --- a/sensor/common/pubsub/consumerid.go +++ b/sensor/common/pubsub/consumerid.go @@ -9,6 +9,7 @@ const ( EnrichedProcessConsumer UnenrichedProcessConsumer FileActivityEnrichedProcessConsumer + FakeFileActivityConsumer DetectorProcessIndicatorConsumer DetectorNetworkFlowConsumer DetectorFileAccessConsumer @@ -28,6 +29,7 @@ var ( ResolverConsumer: "Resolver", EnrichedProcessConsumer: "EnrichedProcess", FileActivityEnrichedProcessConsumer: "FileActivityEnrichedProcess", + FakeFileActivityConsumer: "FakeFileActivity", UnenrichedProcessConsumer: "UnenrichedProcess", DetectorProcessIndicatorConsumer: "DetectorProcessIndicator", DetectorNetworkFlowConsumer: "DetectorNetworkFlow", diff --git a/sensor/common/pubsub/laneid.go b/sensor/common/pubsub/laneid.go index 4ff48be6b60e3..2b238fe7b513f 100644 --- a/sensor/common/pubsub/laneid.go +++ b/sensor/common/pubsub/laneid.go @@ -18,6 +18,7 @@ const ( ResolvedResourceEventLane SoftRestartLane ResourceSyncFinishedLane + FakeFileActivityLane ) var ( @@ -37,6 +38,7 @@ var ( ResolvedResourceEventLane: "ResolvedResourceEvent", SoftRestartLane: "SoftRestart", ResourceSyncFinishedLane: "ResourceSyncFinished", + FakeFileActivityLane: "FakeFileActivity", } ) diff --git a/sensor/common/pubsub/topic.go b/sensor/common/pubsub/topic.go index b7c4ba4214804..ed47c1dd82e83 100644 --- a/sensor/common/pubsub/topic.go +++ b/sensor/common/pubsub/topic.go @@ -18,6 +18,7 @@ const ( ResolvedResourceEventTopic SoftRestartTopic ResourceSyncFinishedTopic + FakeFileActivityTopic ) var ( @@ -37,6 +38,7 @@ var ( ResolvedResourceEventTopic: "ResolvedResourceEvent", SoftRestartTopic: "SoftRestart", ResourceSyncFinishedTopic: "ResourceSyncFinished", + FakeFileActivityTopic: "FakeFileActivity", } ) diff --git a/sensor/kubernetes/fake/fake.go b/sensor/kubernetes/fake/fake.go index cb9ee59bdff8d..13256a6875e7a 100644 --- a/sensor/kubernetes/fake/fake.go +++ b/sensor/kubernetes/fake/fake.go @@ -170,6 +170,7 @@ type WorkloadManager struct { networkManager manager.Manager vmIndexReportHandler index.Handler vmStore *vmStore.VirtualMachineStore + pubSubDispatcher common.PubSubDispatcher // VM readiness coordinator vmPrerequisitesReady *vmReadiness @@ -361,6 +362,11 @@ func (w *WorkloadManager) SetVMStore(store *vmStore.VirtualMachineStore) { log.Debugf("SetVMStore completed (VMs will be populated by informer events)") } +// SetPubSubDispatcher sets the pub/sub dispatcher for publishing synthetic file activities +func (w *WorkloadManager) SetPubSubDispatcher(dispatcher common.PubSubDispatcher) { + w.pubSubDispatcher = dispatcher +} + // Notify implements common.Notifiable to receive Sensor component event notifications func (w *WorkloadManager) Notify(e common.SensorComponentEvent) { switch e { @@ -552,6 +558,11 @@ func (w *WorkloadManager) initializePreexistingResources() { w.wg.Add(1) go w.manageFlows(w.shutdownCtx) + if w.workload.FileActivityWorkload.ActivityInterval > 0 { + w.wg.Add(1) + go w.manageFileActivity(w.shutdownCtx) + } + // Start VirtualMachine/VirtualMachineInstance workload if configured. // This unified workload handles both informer events AND index reports. // Index reports are only sent while VMs are "alive" in the lifecycle. diff --git a/sensor/kubernetes/fake/fileactivity.go b/sensor/kubernetes/fake/fileactivity.go new file mode 100644 index 0000000000000..d782108b2c505 --- /dev/null +++ b/sensor/kubernetes/fake/fileactivity.go @@ -0,0 +1,207 @@ +package fake + +import ( + "context" + "fmt" + "math/rand" + "time" + + sensorAPI "github.com/stackrox/rox/generated/internalapi/sensor" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stackrox/rox/sensor/common/events" + "google.golang.org/protobuf/types/known/timestamppb" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var fileActivityDirs = []string{ + "/etc/security", + "/etc/pam.d", + "/etc/ssh", + "/var/log", + "/var/run", + "/tmp", + "/etc/kubernetes", + "/etc/cni", + "/etc/sysconfig", + "/etc/audit", +} + +func (w *WorkloadManager) manageFileActivity(ctx context.Context) { + defer w.wg.Done() + w.sanitizeFileActivityParams() + + ticker := time.NewTicker(w.workload.FileActivityWorkload.ActivityInterval) + defer ticker.Stop() + paths := generateFileActivityPaths(w.workload.FileActivityWorkload.NumPaths) + + var nodeNames []string + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + + if !w.servicesInitialized.IsDone() { + continue + } + + if w.pubSubDispatcher == nil { + continue + } + + if len(nodeNames) == 0 { + nodeNames = w.listNodeNames(ctx) + if len(nodeNames) == 0 { + continue + } + } + + for range w.workload.FileActivityWorkload.BatchSize { + hostname := nodeNames[rand.Intn(len(nodeNames))] + activity := w.generateFileActivity(paths, hostname) + if activity == nil { + continue + } + + event := &events.FakeFileActivityEvent{ + Activity: activity, + } + + if err := w.pubSubDispatcher.Publish(event); err != nil { + if ctx.Err() != nil { + return + } + log.Fatalf("Failed to publish fake file activity: %v", err) + } + + select { + case <-ctx.Done(): + return + default: + } + } + } +} + +func (w *WorkloadManager) listNodeNames(ctx context.Context) []string { + nodeResp, err := w.fakeClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + log.Errorf("error listing nodes for file activity: %v", err) + return nil + } + names := make([]string, 0, len(nodeResp.Items)) + for _, node := range nodeResp.Items { + names = append(names, node.Name) + } + return names +} + +func (w *WorkloadManager) generateFileActivity(paths []string, hostname string) *sensorAPI.FileActivity { + path := paths[rand.Intn(len(paths))] + now := timestamppb.Now() + + var process *sensorAPI.ProcessSignal + if rand.Intn(100) >= w.workload.FileActivityWorkload.NodeEventPercent { + // Container event - try to get from pool or generate with container ID + containerID, ok := w.containerPool.randomElem() + if ok { + process = getRandomSensorProcess(containerID, w.processPool) + } + } + + if process == nil { + // Node event (or no container available) - generate process with empty container ID + process = getRandomNodeProcess() + } + + base := &sensorAPI.FileActivityBase{ + Path: path, + HostPath: "/host" + path, + } + + activity := &sensorAPI.FileActivity{ + Timestamp: now, + Process: process, + Hostname: hostname, + } + + switch rand.Intn(6) { + case 0: + activity.File = &sensorAPI.FileActivity_Open{ + Open: &sensorAPI.FileOpen{Activity: base}, + } + case 1: + activity.File = &sensorAPI.FileActivity_Creation{ + Creation: &sensorAPI.FileCreation{Activity: base}, + } + case 2: + activity.File = &sensorAPI.FileActivity_Unlink{ + Unlink: &sensorAPI.FileUnlink{Activity: base}, + } + case 3: + newPath := paths[rand.Intn(len(paths))] + activity.File = &sensorAPI.FileActivity_Rename{ + Rename: &sensorAPI.FileRename{ + Old: base, + New: &sensorAPI.FileActivityBase{ + Path: newPath, + HostPath: "/host" + newPath, + }, + }, + } + case 4: + activity.File = &sensorAPI.FileActivity_Permission{ + Permission: &sensorAPI.FilePermissionChange{ + Activity: base, + Mode: 0644, + }, + } + case 5: + activity.File = &sensorAPI.FileActivity_Ownership{ + Ownership: &sensorAPI.FileOwnershipChange{ + Activity: base, + Uid: 1000, + Gid: 1000, + Username: "testuser", + Group: "testgroup", + }, + } + } + + return activity +} + +func (w *WorkloadManager) sanitizeFileActivityParams() { + fa := &w.workload.FileActivityWorkload + if fa.NumPaths <= 0 { + defaultNumPaths := 50 + log.Infof("FileActivityWorkload: numPaths=%d is invalid, defaulting to %d", fa.NumPaths, defaultNumPaths) + fa.NumPaths = defaultNumPaths + } + if fa.BatchSize <= 0 { + defaultBatchSize := 1 + log.Infof("FileActivityWorkload: batchSize=%d is invalid, defaulting to %d", fa.BatchSize, defaultBatchSize) + fa.BatchSize = defaultBatchSize + } + if fa.NodeEventPercent < 0 || fa.NodeEventPercent > 100 { + defaultNodeEventPercent := 50 + log.Infof("FileActivityWorkload: nodeEventPercent=%d is invalid, defaulting to %d", fa.NodeEventPercent, defaultNodeEventPercent) + fa.NodeEventPercent = defaultNodeEventPercent + } + if fa.ActivityInterval <= 0 { + defaultActivityInterval := 50 * time.Millisecond + log.Infof("FileActivityWorkload: activityInterval=%s is invalid, defaulting to %s", fa.ActivityInterval, defaultActivityInterval) + fa.ActivityInterval = defaultActivityInterval + } +} + +func generateFileActivityPaths(n int) []string { + paths := make([]string, 0, n) + for range n { + dir := fileActivityDirs[rand.Intn(len(fileActivityDirs))] + paths = append(paths, fmt.Sprintf("%s/file-%s.conf", dir, uuid.NewV4().String()[:8])) + } + return paths +} diff --git a/sensor/kubernetes/fake/fileactivity_test.go b/sensor/kubernetes/fake/fileactivity_test.go new file mode 100644 index 0000000000000..0f5c1ebc8b87d --- /dev/null +++ b/sensor/kubernetes/fake/fileactivity_test.go @@ -0,0 +1,293 @@ +package fake + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestSanitizeFileActivityParams(t *testing.T) { + tests := map[string]struct { + input FileActivityWorkload + expectedNumPaths int + expectedBatchSize int + expectedNodeEventPercent int + expectedActivityInterval time.Duration + }{ + "all valid parameters should remain unchanged": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 10, + NodeEventPercent: 25, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 10, + expectedNodeEventPercent: 25, + expectedActivityInterval: 100 * time.Millisecond, + }, + "zero NumPaths should default to 50": { + input: FileActivityWorkload{ + NumPaths: 0, + BatchSize: 5, + NodeEventPercent: 50, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 50, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "negative NumPaths should default to 50": { + input: FileActivityWorkload{ + NumPaths: -10, + BatchSize: 5, + NodeEventPercent: 50, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 50, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "zero BatchSize should default to 1": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 0, + NodeEventPercent: 50, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 1, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "negative BatchSize should default to 1": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: -5, + NodeEventPercent: 50, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 1, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "negative NodeEventPercent should default to 50": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: -10, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "NodeEventPercent above 100 should default to 50": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: 150, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 100 * time.Millisecond, + }, + "NodeEventPercent of 0 (all container events) should remain 0": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: 0, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 0, + expectedActivityInterval: 100 * time.Millisecond, + }, + "NodeEventPercent of 100 (all node events) should remain 100": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: 100, + ActivityInterval: 100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 100, + expectedActivityInterval: 100 * time.Millisecond, + }, + "zero ActivityInterval should default to 50ms": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: 50, + ActivityInterval: 0, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 50 * time.Millisecond, + }, + "negative ActivityInterval should default to 50ms": { + input: FileActivityWorkload{ + NumPaths: 100, + BatchSize: 5, + NodeEventPercent: 50, + ActivityInterval: -100 * time.Millisecond, + }, + expectedNumPaths: 100, + expectedBatchSize: 5, + expectedNodeEventPercent: 50, + expectedActivityInterval: 50 * time.Millisecond, + }, + "all invalid parameters should all be defaulted": { + input: FileActivityWorkload{ + NumPaths: -5, + BatchSize: -10, + NodeEventPercent: 200, + ActivityInterval: -50 * time.Millisecond, + }, + expectedNumPaths: 50, + expectedBatchSize: 1, + expectedNodeEventPercent: 50, + expectedActivityInterval: 50 * time.Millisecond, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Create a minimal WorkloadManager with the test workload + wm := &WorkloadManager{ + workload: &Workload{ + FileActivityWorkload: tc.input, + }, + } + + // Run sanitization + wm.sanitizeFileActivityParams() + + // Verify results + assert.Equal(t, tc.expectedNumPaths, wm.workload.FileActivityWorkload.NumPaths, + "NumPaths mismatch") + assert.Equal(t, tc.expectedBatchSize, wm.workload.FileActivityWorkload.BatchSize, + "BatchSize mismatch") + assert.Equal(t, tc.expectedNodeEventPercent, wm.workload.FileActivityWorkload.NodeEventPercent, + "NodeEventPercent mismatch") + assert.Equal(t, tc.expectedActivityInterval, wm.workload.FileActivityWorkload.ActivityInterval, + "ActivityInterval mismatch") + }) + } +} + +func TestGenerateFileActivityPaths(t *testing.T) { + tests := map[string]struct { + numPaths int + }{ + "generate 10 paths": { + numPaths: 10, + }, + "generate 50 paths": { + numPaths: 50, + }, + "generate 100 paths": { + numPaths: 100, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + paths := generateFileActivityPaths(tc.numPaths) + + // Verify correct number of paths + assert.Len(t, paths, tc.numPaths, "Should generate exactly the requested number of paths") + + // Verify all paths are non-empty and start with a known directory + for _, path := range paths { + assert.NotEmpty(t, path, "Path should not be empty") + + // Verify path starts with one of the known directories + foundPrefix := false + for _, dir := range fileActivityDirs { + if len(path) > len(dir) && path[:len(dir)] == dir { + foundPrefix = true + break + } + } + assert.True(t, foundPrefix, "Path should start with a known directory prefix") + } + }) + } +} + +func TestGenerateFileActivity(t *testing.T) { + tests := map[string]struct { + nodeEventPercent int + expectNodeEvent bool // if true, we expect container ID to be empty (node event) + expectContainerEvent bool // if true, we expect container ID to be non-empty (container event) + }{ + "NodeEventPercent 100 should always generate node events": { + nodeEventPercent: 100, + expectNodeEvent: true, + expectContainerEvent: false, + }, + "NodeEventPercent 0 should always generate container events": { + nodeEventPercent: 0, + expectNodeEvent: false, + expectContainerEvent: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + wm := &WorkloadManager{ + workload: &Workload{ + FileActivityWorkload: FileActivityWorkload{ + NodeEventPercent: tc.nodeEventPercent, + }, + }, + containerPool: newPool(), + processPool: newProcessPool(), + } + + // Add test containers to the pool for container event testing + if tc.expectContainerEvent { + wm.containerPool.add("test-container-1") + wm.containerPool.add("test-container-2") + } + + paths := generateFileActivityPaths(10) + hostname := "test-node" + + // Generate multiple activities to test randomness + for range 50 { + activity := wm.generateFileActivity(paths, hostname) + + assert.NotNil(t, activity, "Activity should not be nil") + assert.NotNil(t, activity.GetProcess(), "Process should not be nil") + assert.Equal(t, hostname, activity.GetHostname(), "Hostname should match") + assert.NotNil(t, activity.GetTimestamp(), "Timestamp should not be nil") + assert.NotNil(t, activity.GetFile(), "File activity type should be set") + + // Verify process signal + process := activity.GetProcess() + assert.NotEmpty(t, process.GetId(), "Process ID should not be empty") + assert.NotEmpty(t, process.GetName(), "Process name should not be empty") + assert.NotEmpty(t, process.GetExecFilePath(), "Exec file path should not be empty") + + if tc.expectNodeEvent { + assert.Empty(t, process.GetContainerId(), "Container ID should be empty for node events") + } + if tc.expectContainerEvent { + assert.NotEmpty(t, process.GetContainerId(), "Container ID should not be empty for container events") + } + } + }) + } +} diff --git a/sensor/kubernetes/fake/pools.go b/sensor/kubernetes/fake/pools.go index bb711b657445d..fd3a210d291fc 100644 --- a/sensor/kubernetes/fake/pools.go +++ b/sensor/kubernetes/fake/pools.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/uuid" ) // pool is a thread-safe string pool using set.StringSet @@ -141,19 +142,20 @@ func generateAndAddIPToPool(ipPool *pool) string { return ip } -func getRandomOriginator(containerID string, pool *ProcessPool) *storage.NetworkProcessUniqueKey { - var process *storage.ProcessSignal - var percentMatchedProcess float32 = 0.5 - p := rand.Float32() - if p < percentMatchedProcess { +func getRandomProcessSignal(containerID string, pool *ProcessPool) *storage.ProcessSignal { + if rand.Float32() < 0.5 { // There is a chance that the process has been filtered out or hasn't gotten to // the central-db for some other reason so this is not a guarantee that the // process is in the central-db - process = pool.getRandomProcess(containerID) - } else { - process = getGoodProcess(containerID) + if process := pool.getRandomProcess(containerID); process != nil { + return process + } } + return getGoodProcess(containerID) +} +func getRandomOriginator(containerID string, pool *ProcessPool) *storage.NetworkProcessUniqueKey { + process := getRandomProcessSignal(containerID, pool) return getNetworkProcessUniqueKeyFromProcess(process) } @@ -168,3 +170,26 @@ func getNetworkProcessUniqueKeyFromProcess(process *storage.ProcessSignal) *stor return nil } + +func getSensorProcessFromStorageProcess(process *storage.ProcessSignal) *sensor.ProcessSignal { + return &sensor.ProcessSignal{ + Id: uuid.NewV4().String(), + ContainerId: process.GetContainerId(), + Name: process.GetName(), + Args: process.GetArgs(), + ExecFilePath: process.GetExecFilePath(), + Pid: uint32(rand.Intn(65535) + 1), + Uid: 1000, + Gid: 1000, + } +} + +func getRandomSensorProcess(containerID string, pool *ProcessPool) *sensor.ProcessSignal { + process := getRandomProcessSignal(containerID, pool) + return getSensorProcessFromStorageProcess(process) +} + +func getRandomNodeProcess() *sensor.ProcessSignal { + process := getGoodProcess("") + return getSensorProcessFromStorageProcess(process) +} diff --git a/sensor/kubernetes/fake/process.go b/sensor/kubernetes/fake/process.go index 2d6f5bf34ce33..6bb5b74d60168 100644 --- a/sensor/kubernetes/fake/process.go +++ b/sensor/kubernetes/fake/process.go @@ -472,8 +472,13 @@ func getActiveProcesses(containerID string) []*storage.ProcessSignal { } func getProcess(path, name, containerID string) *storage.ProcessSignal { + // For node events, containerID is empty + cid := containerID + if len(containerID) >= 12 { + cid = containerID[:12] + } return &storage.ProcessSignal{ - ContainerId: containerID[:12], + ContainerId: cid, Time: protocompat.TimestampNow(), Name: name, Args: "abc def ghi jkl lmn op qrs tuv", diff --git a/sensor/kubernetes/fake/workload.go b/sensor/kubernetes/fake/workload.go index 4d5172a0a8431..bf00665e71990 100644 --- a/sensor/kubernetes/fake/workload.go +++ b/sensor/kubernetes/fake/workload.go @@ -116,6 +116,17 @@ type VirtualMachineWorkload struct { NumPackages int `yaml:"numPackages"` } +// FileActivityWorkload defines the rate and shape of file activity events +type FileActivityWorkload struct { + ActivityInterval time.Duration `yaml:"activityInterval"` + BatchSize int `yaml:"batchSize"` + NumPaths int `yaml:"numPaths"` + // NodeEventPercent is the percentage of events generated as node-level (0-100). + // The remainder are deployment-level events with a container ID. + // Default: 50 + NodeEventPercent int `yaml:"nodeEventPercent"` +} + // Workload is the definition of a scale workload type Workload struct { DeploymentWorkload []DeploymentWorkload `yaml:"deploymentWorkload"` @@ -126,6 +137,7 @@ type Workload struct { ServiceWorkload ServiceWorkload `yaml:"serviceWorkload"` SecretWorkload SecretWorkload `yaml:"secretWorkload"` VirtualMachineWorkload VirtualMachineWorkload `yaml:"virtualMachineWorkload"` + FileActivityWorkload FileActivityWorkload `yaml:"fileActivityWorkload"` NumNamespaces int `yaml:"numNamespaces"` MatchLabels bool `yaml:"matchLabels"` } diff --git a/sensor/kubernetes/main.go b/sensor/kubernetes/main.go index 142b808d58637..142b84cfc3f96 100644 --- a/sensor/kubernetes/main.go +++ b/sensor/kubernetes/main.go @@ -127,6 +127,9 @@ func main() { select { case sig := <-sigs: log.Infof("Caught %s signal", sig) + if workloadManager != nil { + workloadManager.Stop() + } s.Stop() gcp.Singleton().Stop() case <-s.Stopped().Done(): diff --git a/sensor/kubernetes/sensor/pubsub.go b/sensor/kubernetes/sensor/pubsub.go index 4d05a2f1735ee..a8c3400b6544a 100644 --- a/sensor/kubernetes/sensor/pubsub.go +++ b/sensor/kubernetes/sensor/pubsub.go @@ -49,6 +49,7 @@ func buildPubSubDispatcher(eventPipelineQueueSize int) (common.PubSubDispatcher, lane.NewBlockingLane(pubsub.DetectorDeployAlertOutputLane), lane.NewBlockingLane(pubsub.SoftRestartLane), lane.NewBlockingLane(pubsub.ResourceSyncFinishedLane), + lane.NewBlockingLane(pubsub.FakeFileActivityLane), }, )) if err != nil { diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index 6ff0c17db3025..93778b0189040 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -280,6 +280,7 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { } if cfg.workloadManager != nil { + cfg.workloadManager.SetPubSubDispatcher(internalMessageDispatcher) cfg.workloadManager.SetSignalHandlers(processPipeline, networkFlowManager) if features.VirtualMachines.Enabled() && virtualMachineHandler != nil { cfg.workloadManager.SetVMIndexReportHandler(virtualMachineHandler) From 92c8db762d5fbf36e956d79c6b0adbdea1002e4e Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 24 Aug 2026 11:48:56 -0400 Subject: [PATCH 050/638] ROX-36492: Add configuration endpoints for node vulnerability reports (#22406) --- .../src/services/NodeReportsService.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts index d577cf5f28267..6371fed7e6992 100644 --- a/ui/apps/platform/src/services/NodeReportsService.ts +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -6,8 +6,41 @@ import { getListQueryParams, getRequestQueryStringForSearchFilter } from 'utils/ import { makeCancellableAxiosRequest } from './cancellationUtils'; import type { CancellableRequest } from './cancellationUtils'; import type { NodeVulnerabilityReportConfiguration } from './ReportsService.types'; +import type { Empty } from './types'; import axios from './instance'; +// https://github.com/stackrox/stackrox/blob/master/proto/api/v2/node_report_service.proto + +// Configuration of scheduled reports + +// PostNodeReportConfiguration +export function createNodeReportConfiguration( + configuration: NodeVulnerabilityReportConfiguration +): CancellableRequest { + return makeCancellableAxiosRequest((signal) => + axios + .post( + '/v2/reports/node/configurations', + configuration, + { signal } + ) + .then((response) => response.data) + ); +} + +// UpdateNodeReportConfiguration +export function updateNodeReportConfiguration( + reportId: string, + configuration: NodeVulnerabilityReportConfiguration +): CancellableRequest { + return makeCancellableAxiosRequest((signal) => + axios + .put(`/v2/reports/node/configurations/${reportId}`, configuration, { signal }) + .then((response) => response.data) + ); +} + +// ListNodeReportConfigurations export function fetchNodeReportConfigurations({ searchFilter, page, @@ -24,6 +57,7 @@ export function fetchNodeReportConfigurations({ ); } +// CountNodeReportConfigurations export function fetchNodeReportConfigurationsCount( searchFilter: SearchFilter ): CancellableRequest<{ count: number }> { @@ -39,3 +73,52 @@ export function fetchNodeReportConfigurationsCount( }) ); } + +// GetNodeReportConfiguration +export function fetchNodeReportConfiguration( + reportId: string +): CancellableRequest { + return makeCancellableAxiosRequest((signal) => + axios + .get( + `/v2/reports/node/configurations/${reportId}`, + { signal } + ) + .then((response) => response.data) + ); +} + +// DeleteNodeReportConfiguration +export function deleteNodeReportConfiguration(reportId: string): CancellableRequest { + return makeCancellableAxiosRequest((signal) => + axios + .delete(`/v2/reports/node/configurations/${reportId}`, { signal }) + .then((response) => response.data) + ); +} + +// Configuration-based jobs + +// RunNodeReport + +// GetNodeReportHistory + +// GetMyNodeReportHistory + +// Job management + +// GetNodeReportStatus + +// CancelNodeReport + +// DeleteNodeReport + +// View-based jobs + +// PostViewBasedNodeReport + +// GetViewBasedNodeReportHistory + +// GetViewBasedMyNodeReportHistory + +// Job download From 227e0a72a00267a94dcf3706f378402454211067 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:05:51 -0500 Subject: [PATCH 051/638] ROX-35135: Vulnerability reports landing page (#22414) --- .../reports/reportsOverviewNav.test.ts | 34 ++++++++ .../platform/src/Containers/MainPage/Body.tsx | 17 ++-- .../MainPage/Navigation/NavigationSidebar.tsx | 7 +- .../Containers/MainPage/Navigation/utils.ts | 2 +- .../Reports/VulnerabilityReportsPage.tsx | 83 +++++++++++++++++++ ui/apps/platform/src/routePaths.ts | 29 +++++-- 6 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VulnerabilityReportsPage.tsx diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts new file mode 100644 index 0000000000000..68e360a6b7555 --- /dev/null +++ b/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts @@ -0,0 +1,34 @@ +import withAuth from '../../../helpers/basicAuth'; +import { + interceptAndOverrideFeatureFlags, + interceptAndOverridePermissions, +} from '../../../helpers/request'; +import { visit } from '../../../helpers/visit'; +import pf6 from '../../../selectors/pf6'; + +const vulnerabilityReportsOverviewPath = '/main/vulnerabilities/reports'; +const vulnerabilityImageReportsPath = '/main/vulnerabilities/reports/images'; +const vulnerabilityNodeReportsPath = '/main/vulnerabilities/reports/nodes'; + +describe('Vulnerability Reports Overview Navigation', () => { + withAuth(); + + it('navigates to the image vulnerability reports page', () => { + interceptAndOverridePermissions({ Deployment: 'READ_ACCESS', Image: 'READ_ACCESS' }); + + visit(vulnerabilityReportsOverviewPath); + + cy.get(`${pf6.card}:contains("Image vulnerability reports") button`).click(); + cy.location('pathname').should('include', vulnerabilityImageReportsPath); + }); + + it('navigates to the node vulnerability reports page', () => { + interceptAndOverridePermissions({ Cluster: 'READ_ACCESS', Node: 'READ_ACCESS' }); + interceptAndOverrideFeatureFlags({ ROX_NODE_VULNERABILITY_REPORTS: true }); + + visit(vulnerabilityReportsOverviewPath); + + cy.get(`${pf6.card}:contains("Node vulnerability reports") button`).click(); + cy.location('pathname').should('include', vulnerabilityNodeReportsPath); + }); +}); diff --git a/ui/apps/platform/src/Containers/MainPage/Body.tsx b/ui/apps/platform/src/Containers/MainPage/Body.tsx index 3fe90ac788863..bb25fb03a5266 100644 --- a/ui/apps/platform/src/Containers/MainPage/Body.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Body.tsx @@ -54,6 +54,7 @@ import { vulnerabilitiesWorkloadCvesPath, vulnerabilityImageReportsPath, vulnerabilityNodeReportsPath, + vulnerabilityReportsPath, } from 'routePaths'; import type { RouteKey } from 'routePaths'; @@ -292,19 +293,25 @@ const routeComponentMap: Record = { component: makeVulnMgmtUserWorkloadView('images-without-cves'), path: vulnerabilitiesImagesWithoutCvesPath, }, - 'vulnerabilities/node-reports': { + 'vulnerabilities/reports': { component: asyncComponent( - () => - import('Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage') + () => import('Containers/Vulnerabilities/Reports/VulnerabilityReportsPage') ), - path: vulnerabilityNodeReportsPath, + path: vulnerabilityReportsPath, }, - 'vulnerabilities/reports': { + 'vulnerabilities/reports/images': { component: asyncComponent( () => import('Containers/Vulnerabilities/VulnerablityReporting/VulnReportingPage') ), path: vulnerabilityImageReportsPath, }, + 'vulnerabilities/reports/nodes': { + component: asyncComponent( + () => + import('Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage') + ), + path: vulnerabilityNodeReportsPath, + }, 'vulnerability-management': { component: asyncComponent(() => import('Containers/VulnMgmt/WorkflowLayout')), path: vulnManagementPath, diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx index 65689c174401c..d5012bbeaae06 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx @@ -46,7 +46,7 @@ import { vulnerabilitiesUserWorkloadsPath, vulnerabilitiesViewPath, vulnerabilitiesVirtualMachineCvesPath, - vulnerabilityImageReportsPath, + vulnerabilityReportsPath, } from 'routePaths'; import NavigationContent from './NavigationContent'; @@ -98,9 +98,8 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc { type: 'link', content: 'Reports', - // TODO: point to the vulnerability reports overview page once it exists. - path: vulnerabilityImageReportsPath, - routeKey: 'vulnerabilities/reports', + path: vulnerabilityReportsPath, + routeKey: ['vulnerabilities/reports/images', 'vulnerabilities/reports/nodes'], }, { type: 'separator', diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/utils.ts b/ui/apps/platform/src/Containers/MainPage/Navigation/utils.ts index eb1f74f612331..c4710ebfa7d61 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/utils.ts +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/utils.ts @@ -17,7 +17,7 @@ export type LinkDescription = { type: 'link'; content: string | TitleCallback | ReactElement; path: string; - routeKey: RouteKey; + routeKey: RouteKey | RouteKey[]; description?: string; isActive?: IsActiveCallback; // for example, exact match }; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VulnerabilityReportsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VulnerabilityReportsPage.tsx new file mode 100644 index 0000000000000..d0f37fb1db4d6 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VulnerabilityReportsPage.tsx @@ -0,0 +1,83 @@ +import { useNavigate } from 'react-router-dom-v5-compat'; +import { + Card, + CardBody, + CardHeader, + CardTitle, + Flex, + FlexItem, + Gallery, + GalleryItem, + PageSection, + Title, +} from '@patternfly/react-core'; + +import PageTitle from 'Components/PageTitle'; +import useIsRouteEnabled from 'hooks/useIsRouteEnabled'; +import { vulnerabilityImageReportsPath, vulnerabilityNodeReportsPath } from 'routePaths'; + +function VulnerabilityReportsPage() { + const navigate = useNavigate(); + const isRouteEnabled = useIsRouteEnabled(); + const isImageReportsEnabled = isRouteEnabled('vulnerabilities/reports/images'); + const isNodeReportsEnabled = isRouteEnabled('vulnerabilities/reports/nodes'); + + return ( + <> + + + + Vulnerability reports + + Configure scheduled vulnerability reports and view-based exports by report + type. + + + + + + {isImageReportsEnabled && ( + + + + navigate(vulnerabilityImageReportsPath), + selectableActionAriaLabel: + 'View Image vulnerability reports', + }} + > + Image vulnerability reports + + + CVE reports scoped to workload images, platform components, all + deployed images, and inactive images across your clusters. + + + + )} + {isNodeReportsEnabled && ( + + + navigate(vulnerabilityNodeReportsPath), + selectableActionAriaLabel: + 'View Node vulnerability reports', + }} + > + Node vulnerability reports + + + OS and runtime CVEs across nodes in selected clusters. + + + + )} + + + + ); +} + +export default VulnerabilityReportsPage; diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index e8dd3be7b80b7..af9c27b3a5a77 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -199,8 +199,9 @@ export type RouteKey = | 'violations' | 'vulnerabilities/exception-management' | 'vulnerabilities/node-cves' - | 'vulnerabilities/node-reports' | 'vulnerabilities/reports' + | 'vulnerabilities/reports/images' + | 'vulnerabilities/reports/nodes' | 'vulnerabilities/user-workloads' | 'vulnerabilities/platform' | 'vulnerabilities/all-images' @@ -372,17 +373,22 @@ const routeRequirementsMap: Record = { 'vulnerabilities/node-cves': { resourceAccessRequirements: everyResource(['Cluster', 'Node']), }, - 'vulnerabilities/node-reports': { - featureFlagRequirements: allEnabled(['ROX_NODE_VULNERABILITY_REPORTS']), - resourceAccessRequirements: everyResource(['Node']), - }, 'vulnerabilities/platform-cves': { featureFlagRequirements: allEnabled(['ROX_LEGACY_SCANNER']), resourceAccessRequirements: everyResource(['Cluster']), }, + // This is a lightweight page with cards that link to the individually gated report types + // below. NavigationSidebar decides link visibility by checking the sub-route keys directly 'vulnerabilities/reports': { + resourceAccessRequirements: everyResource([]), + }, + 'vulnerabilities/reports/images': { resourceAccessRequirements: everyResource(['Deployment', 'Image']), }, + 'vulnerabilities/reports/nodes': { + featureFlagRequirements: allEnabled(['ROX_NODE_VULNERABILITY_REPORTS']), + resourceAccessRequirements: everyResource(['Cluster', 'Node']), + }, 'vulnerabilities/user-workloads': { resourceAccessRequirements: everyResource(['Deployment', 'Image']), }, @@ -425,9 +431,15 @@ type RoutePredicates = { }; export function isRouteEnabled( - { hasReadAccess, isFeatureFlagEnabled }: RoutePredicates, - routeKey: RouteKey -) { + routePredicates: RoutePredicates, + routeKey: RouteKey | RouteKey[] +): boolean { + // An array is enabled if any one of the route keys is enabled. + if (Array.isArray(routeKey)) { + return routeKey.some((key) => isRouteEnabled(routePredicates, key)); + } + + const { hasReadAccess, isFeatureFlagEnabled } = routePredicates; const { featureFlagRequirements, resourceAccessRequirements } = routeRequirementsMap[routeKey]; const areFeatureFlagRequirementsMet = featureFlagRequirements @@ -495,6 +507,7 @@ const vulnerabilitiesPathToLabelMap: Record = { [vulnerabilitiesPlatformCvesPath]: 'Platform CVEs', [vulnerabilitiesNodeCvesPath]: 'Node CVEs', [vulnerabilityReportsPath]: 'Reports', + [vulnerabilityImageReportsPath]: 'Image reports', [exceptionManagementPath]: 'Exception Management', }; From b8c82cf0b371652774ffe4e97fef4e83098adf69 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:24:51 -0600 Subject: [PATCH 052/638] chore(scanner): Update SCANNER_VERSION (#21948) --- SCANNER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCANNER_VERSION b/SCANNER_VERSION index 272f66ef394cb..a8877242d134b 100644 --- a/SCANNER_VERSION +++ b/SCANNER_VERSION @@ -1 +1 @@ -2.40.x-76-g092f797e8e +2.40.x-150-g4a18014bc5 From a0c50370b556c694ec44e86af2392ddd268fef3d Mon Sep 17 00:00:00 2001 From: Cong Du Date: Mon, 24 Aug 2026 13:59:23 -0700 Subject: [PATCH 053/638] ROX-36441: fix flaky AC init container enforcement test (#22389) Co-authored-by: Claude Sonnet 4.6 (1M context) --- .../groovy/AdmissionControllerTest.groovy | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy index c45601902d8ae..07a5f9c9f3308 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy @@ -143,21 +143,7 @@ class AdmissionControllerTest extends BaseSpecification { def "Verify admission controller enforcement on create: #desc"() { when: "Create a deployment that violates an enforced policy" - // Retry to allow time for the admission controller to fetch scan data from - // Central. Policies that require image enrichment (e.g. severity) may not - // evaluate on the first attempt if the AC pod handling this request hasn't - // cached the scan results yet. The retry is harmless for fast-path policies - // (latest tag, bypass) since they pass on the first attempt. - def created - withRetry(ClusterService.isOpenShift4() ? 40 : 20, 1) { - created = orchestrator.createDeploymentNoWait(deployment) - if (created != launched) { - if (created) { - deleteDeploymentWithCaution(deployment) - } - assert created == launched - } - } + def created = createDeploymentWithEnforcementRetry(deployment, launched) then: "Verify the admission controller allows or blocks based on policy and bypass annotation" @@ -253,15 +239,33 @@ class AdmissionControllerTest extends BaseSpecification { } def deleteDeploymentWithCaution(Deployment deployment) { + orchestrator.deleteDeployment(deployment) def timer = new Timer(30, 1) - def deleted = false - while (!deleted && timer.IsValid()) { - orchestrator.deleteDeployment(deployment) - deleted = true + while (timer.IsValid()) { + if (orchestrator.getOrchestratorDeployment(deployment.namespace, deployment.name) == null) { + return + } } - if (!deleted) { - log.warn "Failed to delete deployment. Subsequent tests may be affected ..." + log.warn "Failed to confirm deletion of deployment ${deployment.name}. Subsequent tests may be affected ..." + } + + // Retry to allow time for the admission controller to fetch scan data from + // Central. Policies that require image enrichment (e.g. severity) may not + // evaluate on the first attempt if the AC pod handling this request hasn't + // cached the scan results yet. The retry is harmless for fast-path policies + // (latest tag, bypass) since they pass on the first attempt. + private boolean createDeploymentWithEnforcementRetry(Deployment deployment, boolean expected) { + def created + withRetry(ClusterService.isOpenShift4() ? 40 : 20, 1) { + created = orchestrator.createDeploymentNoWait(deployment) + if (created != expected) { + if (created) { + deleteDeploymentWithCaution(deployment) + } + assert created == expected + } } + return created } @Unroll @@ -433,7 +437,7 @@ class AdmissionControllerTest extends BaseSpecification { .addLabel("app", "test") .addInitContainer("init-0", initImage) - def created = orchestrator.createDeploymentNoWait(deployment) + def created = createDeploymentWithEnforcementRetry(deployment, allowed) then: "Verify admission controller allows or blocks based on init container image" From 7d6c33b6808bf82021b7d596435f4f53736bab65 Mon Sep 17 00:00:00 2001 From: Stephan Hesselmann Date: Mon, 24 Aug 2026 23:38:54 +0200 Subject: [PATCH 054/638] ROX-34488: verify sigstore bundle signatures via sigstore-go (#21909) Co-authored-by: Claude Opus 4.6 (1M context) --- go.mod | 2 +- go.sum | 2 - pkg/signatures/cosign_sig_verifier.go | 338 +++++++++++++++++---- pkg/signatures/cosign_sig_verifier_test.go | 256 +++++++++++++++- 4 files changed, 533 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index b2f3f8044d64d..bd702bba8ee2b 100644 --- a/go.mod +++ b/go.mod @@ -122,6 +122,7 @@ require ( github.com/sigstore/cosign/v3 v3.1.3 github.com/sigstore/rekor v1.5.3 github.com/sigstore/sigstore v1.10.9 + github.com/sigstore/sigstore-go v1.2.2 github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 @@ -407,7 +408,6 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect github.com/sigstore/rekor-tiles/v2 v2.3.0 // indirect - github.com/sigstore/sigstore-go v1.2.2 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.1 // indirect diff --git a/go.sum b/go.sum index 3857077a753f9..566bd71101b29 100644 --- a/go.sum +++ b/go.sum @@ -375,8 +375,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY= -github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936/go.mod h1:ttKPnOepYt4LLzD+loXQ1rT6EmpyIYHro7TAJuIIlHo= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= diff --git a/pkg/signatures/cosign_sig_verifier.go b/pkg/signatures/cosign_sig_verifier.go index 04e223e5df7cb..8771f770273d6 100644 --- a/pkg/signatures/cosign_sig_verifier.go +++ b/pkg/signatures/cosign_sig_verifier.go @@ -5,20 +5,25 @@ import ( "crypto" "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/json" "encoding/pem" + "fmt" "os" + "time" "github.com/google/go-containerregistry/pkg/name" gcrv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" - "github.com/sigstore/cosign/v3/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/sigstore/cosign/v3/pkg/cosign/bundle" "github.com/sigstore/cosign/v3/pkg/oci" "github.com/sigstore/cosign/v3/pkg/oci/static" rekorClient "github.com/sigstore/rekor/pkg/client" + sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/payload" @@ -48,6 +53,16 @@ var ( errUnverifiedBundle = errors.New("unverified transparency log bundle") ) +// verifiableSignature holds signature data ready for verification. Exactly one of +// the two fields is populated: +// - sigstoreBundle: raw sigstore bundle JSON, verified via verifySigstoreBundle (sigstore-go). +// - signature: reconstructed oci.Signature from decomposed SimpleSigning fields, verified via +// cosign.VerifyImageSignature. +type verifiableSignature struct { + sigstoreBundle []byte + signature oci.Signature +} + var once sync.Once func setupTufRootDir() { @@ -173,7 +188,7 @@ func (c *cosignSignatureVerifier) VerifySignature(ctx context.Context, return storage.ImageSignatureVerificationResult_FAILED_VERIFICATION, nil, errNoVerificationData } - sigs, hash, err := retrieveVerificationDataFromImage(image) + vsigs, hash, err := retrieveVerificationDataFromImage(image) if err != nil { return getVerificationResultStatusFromErr(err), nil, err } @@ -200,9 +215,9 @@ func (c *cosignSignatureVerifier) VerifySignature(ctx context.Context, var mutex sync.Mutex var wg sync.WaitGroup for _, opts := range c.verifierOpts { - for cnt, sig := range sigs { + for cnt, vsig := range vsigs { wg.Go(func() { - verifierRefs, err := verifyImageSignature(ctx, sig, hash, image, opts) + verifierRefs, err := verifySignature(ctx, vsig.sigstoreBundle, vsig.signature, hash, image, opts) mutex.Lock() defer mutex.Unlock() if err != nil { @@ -212,7 +227,6 @@ func (c *cosignSignatureVerifier) VerifySignature(ctx context.Context, ) return } - // Successful verification. Keep the image references. verifiedImageReferences.AddAll(verifierRefs...) }) } @@ -228,16 +242,132 @@ func (c *cosignSignatureVerifier) VerifySignature(ctx context.Context, return storage.ImageSignatureVerificationResult_FAILED_VERIFICATION, nil, allVerifyErrs } +// transparencyLogsFromKeys converts cosign's TrustedTransparencyLogPubKeys map into +// sigstore-go TransparencyLog entries. +func transparencyLogsFromKeys(keys *cosign.TrustedTransparencyLogPubKeys) map[string]*root.TransparencyLog { + if keys == nil || len(keys.Keys) == 0 { + return nil + } + logs := make(map[string]*root.TransparencyLog, len(keys.Keys)) + for logID, key := range keys.Keys { + idBytes, err := hex.DecodeString(logID) + if err != nil { + log.Warnf("Skipping transparency log key with malformed log ID %q: %v", logID, err) + continue + } + logs[logID] = &root.TransparencyLog{ + ID: idBytes, + PublicKey: key.PubKey, + HashFunc: crypto.SHA256, + SignatureHashFunc: crypto.SHA256, + // sigstore-go VerifySET rejects zero-value ValidityPeriodStart + // as "not set". Unix epoch is a non-zero sentinel meaning "always valid". + ValidityPeriodStart: time.Unix(0, 0), + } + } + return logs +} + +// trustedMaterialFromTlogKeys builds a root.TrustedMaterial from the Rekor and CTLog keys +// on CheckOpts. Returns nil if no custom keys are configured (verifySigstoreBundle will +// fall back to the public Sigstore TUF root). +func trustedMaterialFromTlogKeys(opts cosign.CheckOpts) (root.TrustedMaterial, error) { + rekorLogs := transparencyLogsFromKeys(opts.RekorPubKeys) + ctLogs := transparencyLogsFromKeys(opts.CTLogPubKeys) + if len(rekorLogs) == 0 && len(ctLogs) == 0 { + return nil, nil + } + tr, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, ctLogs, nil, rekorLogs) + if err != nil { + return nil, fmt.Errorf("building trusted root from transparency log keys: %w", err) + } + return tr, nil +} + +// trustedMaterialFromCertificateChain builds a root.TrustedMaterial from a custom certificate +// chain. The last cert in the chain is treated as the root; the rest are intermediates. +func trustedMaterialFromCertificateChain(chain []*x509.Certificate, opts cosign.CheckOpts) (root.TrustedMaterial, error) { + ca := &root.FulcioCertificateAuthority{ + Root: chain[len(chain)-1], + Intermediates: chain[:len(chain)-1], + } + return root.NewTrustedRoot( + root.TrustedRootMediaType01, + []root.CertificateAuthority{ca}, + transparencyLogsFromKeys(opts.CTLogPubKeys), + nil, + transparencyLogsFromKeys(opts.RekorPubKeys), + ) +} + +// augmentTrustedMaterialWithSigChain extracts intermediates from a SimpleSigning +// signature's embedded certificate chain and merges them into the TrustedMaterial. +// This is necessary because cosign's ValidateAndUnpackCertWithIntermediates ignores +// sig.Chain() intermediates when TrustedMaterial is set, but the configured chain +// may only contain the root CA (BYOPKI pattern where the signer provides the +// intermediate chain). +func augmentTrustedMaterialWithSigChain(cosignOpts *cosign.CheckOpts, sig oci.Signature) error { + sigChain, err := sig.Chain() + if err != nil || len(sigChain) <= 1 { + return err + } + sigIntermediates := sigChain[:len(sigChain)-1] + + cas := cosignOpts.TrustedMaterial.FulcioCertificateAuthorities() + augmented := make([]root.CertificateAuthority, 0, len(cas)) + for _, ca := range cas { + fca, ok := ca.(*root.FulcioCertificateAuthority) + if !ok { + augmented = append(augmented, ca) + continue + } + merged := &root.FulcioCertificateAuthority{ + Root: fca.Root, + Intermediates: append(append([]*x509.Certificate{}, fca.Intermediates...), sigIntermediates...), + ValidityPeriodStart: fca.ValidityPeriodStart, + ValidityPeriodEnd: fca.ValidityPeriodEnd, + } + augmented = append(augmented, merged) + } + + tm, err := root.NewTrustedRoot( + root.TrustedRootMediaType01, + augmented, + cosignOpts.TrustedMaterial.CTLogs(), + cosignOpts.TrustedMaterial.TimestampingAuthorities(), + cosignOpts.TrustedMaterial.RekorLogs(), + ) + if err != nil { + return fmt.Errorf("rebuilding trusted material with signature intermediates: %w", err) + } + cosignOpts.TrustedMaterial = tm + return nil +} + +// sigstoreTrustedRoot returns the public Sigstore trusted root from TUF. +func sigstoreTrustedRoot() (root.TrustedMaterial, error) { + tr, err := cosign.TrustedRoot() + if err != nil { + return nil, fmt.Errorf("loading sigstore trusted root: %w", err) + } + return tr, nil +} + func (c *cosignSignatureVerifier) createVerifierOpts(ctx context.Context) error { defaultOpts, err := c.defaultCosignCheckOpts(ctx) if err != nil { return errors.Wrap(err, "creating default cosign check opts") } + // Build TrustedMaterial once from the tlog keys for public key verifiers. + publicKeyTrustedMaterial, err := trustedMaterialFromTlogKeys(defaultOpts) + if err != nil { + return err + } + var verifierErrs error // Public key verifiers. for _, key := range c.parsedPublicKeys { - // For now, only supporting SHA256 as algorithm. v, err := signature.LoadVerifier(key, crypto.SHA256) if err != nil { verifierErrs = multierror.Append(verifierErrs, errors.Wrap(err, "creating verifier")) @@ -245,6 +375,7 @@ func (c *cosignSignatureVerifier) createVerifierOpts(ctx context.Context) error } opts := defaultOpts opts.SigVerifier = v + opts.TrustedMaterial = publicKeyTrustedMaterial c.verifierOpts = append(c.verifierOpts, opts) } @@ -317,9 +448,9 @@ func (c *cosignSignatureVerifier) defaultCosignCheckOpts(ctx context.Context) (c } func cosignCheckOptsFromCert(ctx context.Context, cert certVerificationData, opts cosign.CheckOpts) (cosign.CheckOpts, error) { - // Skip verifying the identities when the wildcard matching logic being used. This fixes an issue - // with verifying the identity which will yield an error when using the wildcard expressions _and_ the certificate - // to verify has no identities associated with it (i.e. within the BYOPKI use-case). + // Only set non-wildcard identities. CheckCertificatePolicy fails on BYOPKI certs + // that lack OIDC extensions. For the sigstore-go bundle path, verifySigstoreBundle + // injects wildcard identities when Identities is empty and SigVerifier is nil. if cert.oidcIssuerExpr != ".*" && cert.identityExpr != ".*" { opts.Identities = []cosign.Identity{{ IssuerRegExp: cert.oidcIssuerExpr, @@ -336,65 +467,87 @@ func cosignCheckOptsFromCert(ctx context.Context, cert certVerificationData, opt } } - // - If we have both cert and chain, we use both to verify the public key and the root. - // - If we only have the cert, we assume the fulcio trusted root. - // - If we only have the chain, we use this as the trusted root to verify certificates, if any. - // - If none is given, we set the fulcio roots. - switch { - case cert.cert != nil && len(cert.chain) > 0: - v, err := cosign.ValidateAndUnpackCertWithChain(cert.cert, cert.chain, &opts) + // Build TrustedMaterial: custom chain when provided, Sigstore (Fulcio) root otherwise. + // All certificate chain validation flows through TrustedMaterial — the legacy + // RootCerts/IntermediateCerts fields are not needed. + if len(cert.chain) > 0 { + opts.TrustedMaterial, err = trustedMaterialFromCertificateChain(cert.chain, opts) if err != nil { return opts, err } - opts.SigVerifier = v - return opts, nil - - case cert.cert != nil: - opts.RootCerts, err = fulcio.GetRoots() + } else { + opts.TrustedMaterial, err = sigstoreTrustedRoot() if err != nil { return opts, err } - opts.IntermediateCerts, err = fulcio.GetIntermediates() + } + + // When a leaf certificate is provided, validate it against TrustedMaterial and + // pin its public key as SigVerifier. This catches misconfigurations early (cert + // doesn't chain to the configured/Fulcio root, missing SCT) and pins the exact + // signing key so only signatures from this key are accepted. + if cert.cert != nil { + chains, err := verify.VerifyLeafCertificate(time.Now(), cert.cert, opts.TrustedMaterial) if err != nil { - return opts, err + return opts, fmt.Errorf("validating configured certificate against trust root: %w", err) } - v, err := cosign.ValidateAndUnpackCert(cert.cert, &opts) - if err != nil { + if err := cosign.CheckCertificatePolicy(cert.cert, &opts); err != nil { return opts, err } - opts.SigVerifier = v - return opts, nil - - case len(cert.chain) > 0: - pool := x509.NewCertPool() - for _, caCert := range cert.chain { - pool.AddCert(caCert) + if !opts.IgnoreSCT { + contains, err := cosign.ContainsSCT(cert.cert.Raw) + if err != nil { + return opts, err + } + if !contains { + return opts, errors.New("certificate does not include required embedded SCT") + } + if err := verify.VerifySignedCertificateTimestamp(chains, 1, opts.TrustedMaterial); err != nil { + return opts, fmt.Errorf("verifying signed certificate timestamp: %w", err) + } } - opts.RootCerts = pool - return opts, nil - - default: - opts.RootCerts, err = fulcio.GetRoots() + v, err := signature.LoadVerifier(cert.cert.PublicKey, crypto.SHA256) if err != nil { - return opts, err + return opts, errors.Wrap(err, "loading verifier from certificate") } - return opts, nil + opts.SigVerifier = v } + + return opts, nil } -func verifyImageSignature(ctx context.Context, signature oci.Signature, +// verifySignature dispatches signature verification based on format: +// - Sigstore bundle: delegates to verifySigstoreBundle which uses cosign.VerifyNewBundle +// (sigstore-go). The bundle carries its own verification material (cert, tlog entry). +// - SimpleSigning: delegates to cosign.VerifyImageSignature which verifies the signature +// and rekor bundle (transparency log proof) from the decomposed proto fields. +func verifySignature(ctx context.Context, sigstoreBundle []byte, sig oci.Signature, imageHash gcrv1.Hash, image *storage.Image, cosignOpts cosign.CheckOpts, ) ([]string, error) { - // If there is no error during the verification, the signature was successfully verified - // as well as the claims. - bundleVerified, err := cosign.VerifyImageSignature(ctx, signature, imageHash, &cosignOpts) + // Verify DSSE Sigstore bundle. + if len(sigstoreBundle) > 0 { + return verifySigstoreBundle(ctx, sigstoreBundle, imageHash, image, cosignOpts) + } + + // Verify SimpleSigning signature. + // cosign's ValidateAndUnpackCertWithIntermediates uses VerifyLeafCertificate when + // TrustedMaterial is set, which only considers CAs from the TrustedMaterial and + // ignores intermediates embedded in the signature. Merge them in so the chain + // builds correctly for BYOPKI setups where the configured chain is just the root. + if cosignOpts.TrustedMaterial != nil { + if err := augmentTrustedMaterialWithSigChain(&cosignOpts, sig); err != nil { + return nil, err + } + } + rekorVerified, err := cosign.VerifyImageSignature(ctx, sig, imageHash, &cosignOpts) if err != nil { return nil, err } - if !bundleVerified && !cosignOpts.IgnoreTlog { + if !rekorVerified && !cosignOpts.IgnoreTlog { return nil, errUnverifiedBundle } - refs, err := getVerifiedImageReference(signature, image) + + refs, err := getVerifiedImageReference(sig, image) if err != nil { return nil, errors.Wrap(err, "getting verified image references") } @@ -404,6 +557,46 @@ func verifyImageSignature(ctx context.Context, signature oci.Signature, return refs, nil } +// verifySigstoreBundle verifies a sigstore bundle directly via cosign.VerifyNewBundle. +// TrustedMaterial must be set by the caller (createVerifierOpts / cosignCheckOptsFromCert). +func verifySigstoreBundle(ctx context.Context, sigstoreBundle []byte, + imageHash gcrv1.Hash, image *storage.Image, cosignOpts cosign.CheckOpts, +) ([]string, error) { + if cosignOpts.TrustedMaterial == nil { + var err error + cosignOpts.TrustedMaterial, err = sigstoreTrustedRoot() + if err != nil { + return nil, fmt.Errorf("loading fallback trusted material for bundle verification: %w", err) + } + } + + // sigstore-go requires at least one identity for cert-based verification. + // cosignCheckOptsFromCert skips Identities for wildcard BYOPKI certs (they lack + // OIDC extensions that the legacy path would check), so inject them here. + if len(cosignOpts.Identities) == 0 && cosignOpts.SigVerifier == nil { + cosignOpts.Identities = []cosign.Identity{{SubjectRegExp: ".*", IssuerRegExp: ".*"}} + } + + bundle := &sgbundle.Bundle{} + if err := bundle.UnmarshalJSON(sigstoreBundle); err != nil { + return nil, fmt.Errorf("unmarshalling sigstore bundle: %w", err) + } + + digestBytes, err := hex.DecodeString(imageHash.Hex) + if err != nil { + return nil, fmt.Errorf("decoding image digest hex: %w", err) + } + + artifactPolicy := verify.WithArtifactDigest(imageHash.Algorithm, digestBytes) + if _, err := cosign.VerifyNewBundle(ctx, &cosignOpts, artifactPolicy, bundle); err != nil { + return nil, err + } + + // Sigstore bundles bind to the image digest, not a docker reference, so all + // names sharing the same digest are considered verified. + return getAllImageReferences(image), nil +} + // getVerificationResultStatusFromErr will map an error to a specific storage.ImageSignatureVerificationResult_Status. // This is done in an effort to return appropriate status codes to the client triggering the signature verification. func getVerificationResultStatusFromErr(err error) storage.ImageSignatureVerificationResult_Status { @@ -433,7 +626,13 @@ func unmarshalRekorBundle(byteBundle []byte) (*bundle.RekorBundle, error) { return rekorBundle, nil } -func retrieveVerificationDataFromImage(image *storage.Image) ([]oci.Signature, gcrv1.Hash, error) { +// retrieveVerificationDataFromImage reads stored signatures from the image proto and +// prepares them for verification. Two storage formats are handled: +// - SigstoreBundle set: the raw bundle JSON is carried through for verifySigstoreBundle. +// - SigstoreBundle empty (legacy): the decomposed fields (RawSignature, SignaturePayload, +// CertPem, CertChainPem, RekorBundle) are reassembled into an oci.Signature for +// cosign.VerifyImageSignature. +func retrieveVerificationDataFromImage(image *storage.Image) ([]verifiableSignature, gcrv1.Hash, error) { imgSHA := imgUtils.GetSHA(image) // If there is no digest associated with the image, we cannot safely do signature and claim verification. if imgSHA == "" { @@ -454,19 +653,29 @@ func retrieveVerificationDataFromImage(image *storage.Image) ([]oci.Signature, g "invalid hashing algorithm %s used, only SHA256 is supported", hash.Algorithm) } - // Each signature contains the base64 encoded version of it and the associated payload. - // In the future, this will also include potential rekor bundles for keyless verification. - signatures := make([]oci.Signature, 0, len(image.GetSignature().GetSignatures())) + vsigs := make([]verifiableSignature, 0, len(image.GetSignature().GetSignatures())) for _, imgSig := range image.GetSignature().GetSignatures() { if imgSig.GetCosign() == nil { continue } - b64Sig := base64.StdEncoding.EncodeToString(imgSig.GetCosign().GetRawSignature()) + + cosignSig := imgSig.GetCosign() + + // Sigstore bundle: carry the raw bundle for direct sigstore-go verification. + if len(cosignSig.GetSigstoreBundle()) > 0 { + vsigs = append(vsigs, verifiableSignature{ + sigstoreBundle: cosignSig.GetSigstoreBundle(), + }) + continue + } + + // Legacy decomposed fields. + b64Sig := base64.StdEncoding.EncodeToString(cosignSig.GetRawSignature()) sigOpts := []static.Option{ - static.WithCertChain(imgSig.GetCosign().GetCertPem(), imgSig.GetCosign().GetCertChainPem()), + static.WithCertChain(cosignSig.GetCertPem(), cosignSig.GetCertChainPem()), } - rekorBundle, err := unmarshalRekorBundle(imgSig.GetCosign().GetRekorBundle()) + rekorBundle, err := unmarshalRekorBundle(cosignSig.GetRekorBundle()) if err != nil { log.Errorf("Failed to unmarshal rekor bundle for image %q: %s", image.GetName().GetFullName(), err) } @@ -474,19 +683,21 @@ func retrieveVerificationDataFromImage(image *storage.Image) ([]oci.Signature, g sigOpts = append(sigOpts, static.WithBundle(rekorBundle)) } - sig, err := static.NewSignature(imgSig.GetCosign().GetSignaturePayload(), b64Sig, sigOpts...) + sig, err := static.NewSignature(cosignSig.GetSignaturePayload(), b64Sig, sigOpts...) if err != nil { return nil, gcrv1.Hash{}, errCorruptedSignature.CausedBy(err) } - signatures = append(signatures, sig) + vsigs = append(vsigs, verifiableSignature{ + signature: sig, + }) } - return signatures, hash, nil + return vsigs, hash, nil } // getVerifiedImageReferenceFromSignature retrieves the verified docker reference in the format of -// / from the payload of the oci.Signature and filters out image names that are verified by -// the docker reference using the image names associated with the storage.Image. +// / from the payload of the SimpleSigning oci.Signature and filters out image +// names that are verified by the docker reference using the image names associated with the storage.Image. func getVerifiedImageReference(signature oci.Signature, image *storage.Image) ([]string, error) { payloadBytes, err := signature.Payload() if err != nil { @@ -532,6 +743,19 @@ func getVerifiedImageReference(signature oci.Signature, image *storage.Image) ([ return verifiedImageReferences, nil } +func getAllImageReferences(image *storage.Image) []string { + imageNames := protoutils.SliceUnique( + append([]*storage.ImageName{image.GetName()}, image.GetNames()...), + ) + refs := make([]string, 0, len(imageNames)) + for _, n := range imageNames { + if fullName := n.GetFullName(); fullName != "" { + refs = append(refs, fullName) + } + } + return refs +} + func equalRegistryRepository(signatureIdentity, imageName string) (bool, error) { sigRef, err := name.ParseReference(signatureIdentity) if err != nil { diff --git a/pkg/signatures/cosign_sig_verifier_test.go b/pkg/signatures/cosign_sig_verifier_test.go index f15ff47975b83..c84be6dcaa2b7 100644 --- a/pkg/signatures/cosign_sig_verifier_test.go +++ b/pkg/signatures/cosign_sig_verifier_test.go @@ -2,11 +2,21 @@ package signatures import ( "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" "encoding/base64" + "encoding/hex" + "encoding/pem" "os" "testing" + "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/sigstore-go/pkg/root" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/images/types" @@ -710,20 +720,21 @@ func TestRetrieveVerificationDataFromImage_Success(t *testing.T) { b64CosignSignature, b64CosignSignaturePayload, nil, nil, nil) require.NoError(t, err, "error creating image") - sigs, hash, err := retrieveVerificationDataFromImage(img) + vsigs, hash, err := retrieveVerificationDataFromImage(img) require.NoError(t, err, "should not fail") - assert.Len(t, sigs, 1, "expected one signature") - sig := sigs[0] - b64sig, err := sig.Base64Signature() + assert.Len(t, vsigs, 1, "expected one signature") + vsig := vsigs[0] + b64sig, err := vsig.signature.Base64Signature() require.NoError(t, err) assert.Equal(t, b64CosignSignature, b64sig, "expected the base64 values of the signatures to match") - payload, err := sig.Payload() + payload, err := vsig.signature.Payload() require.NoError(t, err) expectedPayload, err := base64.StdEncoding.DecodeString(b64CosignSignaturePayload) require.NoError(t, err) assert.Equal(t, expectedPayload, payload, "expected the payloads of the signature to match") assert.Equal(t, imgHash, hash.String(), "expected the hash to match the image's hash") + assert.Empty(t, vsig.sigstoreBundle) } func TestRetrieveVerificationDataFromImage_Failure(t *testing.T) { @@ -754,6 +765,66 @@ func TestRetrieveVerificationDataFromImage_Failure(t *testing.T) { } } +func TestRetrieveVerificationDataFromImage_SigstoreBundle(t *testing.T) { + imgHash := "sha256:3a4d57227f02243dfc8a2849ec4a116646bed293b9e93cbf9d4a673a28ef6345" + bundleJSON := []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}`) + + cimg, err := imgUtils.GenerateImageFromString("docker.io/nginx@" + imgHash) + require.NoError(t, err) + img := types.ToImage(cimg) + img.Signature = &storage.ImageSignature{ + Signatures: []*storage.Signature{ + { + Signature: &storage.Signature_Cosign{ + Cosign: &storage.CosignSignature{ + SigstoreBundle: bundleJSON, + SignatureFormat: storage.CosignSignature_DEAD_SIMPLE_SIGNING_ENVELOPE, + }, + }, + }, + }, + } + + vsigs, hash, err := retrieveVerificationDataFromImage(img) + require.NoError(t, err) + assert.Len(t, vsigs, 1) + assert.Equal(t, bundleJSON, vsigs[0].sigstoreBundle) + assert.Nil(t, vsigs[0].signature) + assert.Equal(t, imgHash, hash.String()) +} + +func TestRetrieveVerificationDataFromImage_MixedFormats(t *testing.T) { + imgHash := "sha256:3a4d57227f02243dfc8a2849ec4a116646bed293b9e93cbf9d4a673a28ef6345" + bundleJSON := []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}`) + + img, err := generateImageWithCosignSignature("docker.io/nginx@"+imgHash, + "MEUCIDGMmJyxVKGPxvPk/QlRzMSGzcI8pYCy+MB7RTTpegzTAiEArssqWntVN8oJOMV0Aey0zhsNqRmEVQAY"+ + "ZNkn8hkAnXI=", + "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoidHRsLnNoL2Q4ZDM4O"+ + "TJkLTQ4YmQtNDY3MS1hNTQ2LTJlNzBhOTAwYjcwMiJ9LCJpbWFnZSI6eyJkb2NrZXItbWFuaWZlc3QtZGlnZXN0Ijoic2hhMjU2OmVlODli"+ + "MDA1MjhmZjRmMDJmMjQwNWU0ZWUyMjE3NDNlYmMzZjhlOGRkMGJmZDVjNGMyMGEyZmEyYWFhN2VkZTMifSwidHlwZSI6ImNvc2lnbiBjb25"+ + "0YWluZXIgaW1hZ2Ugc2lnbmF0dXJlIn0sIm9wdGlvbmFsIjpudWxsfQ==", + nil, nil, nil) + require.NoError(t, err) + // Add a second signature with a sigstore bundle. + img.GetSignature().Signatures = append(img.GetSignature().GetSignatures(), &storage.Signature{ + Signature: &storage.Signature_Cosign{ + Cosign: &storage.CosignSignature{ + SigstoreBundle: bundleJSON, + SignatureFormat: storage.CosignSignature_DEAD_SIMPLE_SIGNING_ENVELOPE, + }, + }, + }) + + vsigs, _, err := retrieveVerificationDataFromImage(img) + require.NoError(t, err) + assert.Len(t, vsigs, 2) + assert.NotNil(t, vsigs[0].signature, "first signature should be SimpleSigning") + assert.Empty(t, vsigs[0].sigstoreBundle) + assert.Nil(t, vsigs[1].signature, "second signature should be bundle-only") + assert.Equal(t, bundleJSON, vsigs[1].sigstoreBundle) +} + func TestEqualRegistryRepository(t *testing.T) { cases := map[string]struct { signatureIdentity string @@ -841,3 +912,178 @@ func generateImageWithCosignSignature(imgString, b64Sig, b64SigPayload string, } return img, nil } + +func TestTransparencyLogsFromKeys(t *testing.T) { + ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + validLogID := hex.EncodeToString([]byte("valid-log-id-0123456789ab")) + + cases := map[string]struct { + keys *cosign.TrustedTransparencyLogPubKeys + expected int + }{ + "nil keys returns nil": { + keys: nil, + expected: 0, + }, + "empty keys map returns nil": { + keys: &cosign.TrustedTransparencyLogPubKeys{Keys: map[string]cosign.TransparencyLogPubKey{}}, + expected: 0, + }, + "valid key is converted": { + keys: &cosign.TrustedTransparencyLogPubKeys{ + Keys: map[string]cosign.TransparencyLogPubKey{ + validLogID: {PubKey: ecKey.Public()}, + }, + }, + expected: 1, + }, + "malformed hex log ID is skipped": { + keys: &cosign.TrustedTransparencyLogPubKeys{ + Keys: map[string]cosign.TransparencyLogPubKey{ + "not-valid-hex!": {PubKey: ecKey.Public()}, + }, + }, + expected: 0, + }, + "mixed valid and invalid log IDs": { + keys: &cosign.TrustedTransparencyLogPubKeys{ + Keys: map[string]cosign.TransparencyLogPubKey{ + validLogID: {PubKey: ecKey.Public()}, + "not-valid-hex!": {PubKey: ecKey.Public()}, + }, + }, + expected: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + result := transparencyLogsFromKeys(tc.keys) + if tc.expected == 0 { + assert.Empty(t, result) + return + } + assert.Len(t, result, tc.expected) + for _, tl := range result { + assert.NotNil(t, tl.PublicKey) + assert.Equal(t, crypto.SHA256, tl.HashFunc) + assert.Equal(t, crypto.SHA256, tl.SignatureHashFunc) + assert.False(t, tl.ValidityPeriodStart.IsZero(), "ValidityPeriodStart must be non-zero for sigstore-go.") + } + }) + } +} + +func TestTrustedMaterialFromCertificateChain(t *testing.T) { + certPEM, err := os.ReadFile("testdata/cert.pem") + require.NoError(t, err) + chainPEM, err := os.ReadFile("testdata/chain.pem") + require.NoError(t, err) + + parseCert := func(t *testing.T, pemData []byte) *x509.Certificate { + block, _ := pem.Decode(pemData) + require.NotNil(t, block) + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + return cert + } + leafCert := parseCert(t, certPEM) + rootCert := parseCert(t, chainPEM) + + cases := map[string]struct { + chain []*x509.Certificate + expectedIntermediates int + }{ + "single cert becomes root with no intermediates": { + chain: []*x509.Certificate{rootCert}, + expectedIntermediates: 0, + }, + "two certs: first is intermediate, last is root": { + chain: []*x509.Certificate{leafCert, rootCert}, + expectedIntermediates: 1, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + tm, err := trustedMaterialFromCertificateChain(tc.chain, cosign.CheckOpts{}) + require.NoError(t, err) + require.NotNil(t, tm) + + cas := tm.FulcioCertificateAuthorities() + require.Len(t, cas, 1) + fca, ok := cas[0].(*root.FulcioCertificateAuthority) + require.True(t, ok) + assert.Equal(t, tc.chain[len(tc.chain)-1], fca.Root, "Last cert in chain should be root.") + assert.Len(t, fca.Intermediates, tc.expectedIntermediates) + }) + } +} + +func TestAugmentTrustedMaterialWithSigChain(t *testing.T) { + certPEM, err := os.ReadFile("testdata/cert.pem") + require.NoError(t, err) + chainPEM, err := os.ReadFile("testdata/chain.pem") + require.NoError(t, err) + + parseCert := func(t *testing.T, pemData []byte) *x509.Certificate { + block, _ := pem.Decode(pemData) + require.NotNil(t, block) + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + return cert + } + rootCert := parseCert(t, chainPEM) + + // Build a TrustedMaterial with one CA that has only a root (no intermediates). + baseTM, err := root.NewTrustedRoot( + root.TrustedRootMediaType01, + []root.CertificateAuthority{ + &root.FulcioCertificateAuthority{Root: rootCert}, + }, + nil, nil, nil, + ) + require.NoError(t, err) + + t.Run("short chain is a no-op", func(t *testing.T) { + // Chain() returns only the root (1 cert) => no intermediates to add. + sig, err := static.NewSignature(nil, "", static.WithCertChain(certPEM, chainPEM)) + require.NoError(t, err) + + opts := &cosign.CheckOpts{TrustedMaterial: baseTM} + err = augmentTrustedMaterialWithSigChain(opts, sig) + assert.NoError(t, err) + // TrustedMaterial should be unchanged because chain has only 1 cert. + assert.Equal(t, baseTM, opts.TrustedMaterial) + }) + + t.Run("no chain is a no-op", func(t *testing.T) { + sig, err := static.NewSignature(nil, "") + require.NoError(t, err) + + opts := &cosign.CheckOpts{TrustedMaterial: baseTM} + err = augmentTrustedMaterialWithSigChain(opts, sig) + assert.NoError(t, err) + assert.Equal(t, baseTM, opts.TrustedMaterial) + }) + + t.Run("multi-cert chain merges intermediates", func(t *testing.T) { + // Create a chain PEM with cert + root so Chain() returns 2 certs. + multiChainPEM := append(append([]byte{}, certPEM...), chainPEM...) + sig, err := static.NewSignature(nil, "", static.WithCertChain(certPEM, multiChainPEM)) + require.NoError(t, err) + + opts := &cosign.CheckOpts{TrustedMaterial: baseTM} + err = augmentTrustedMaterialWithSigChain(opts, sig) + require.NoError(t, err) + + // The TrustedMaterial should now have the intermediate from the sig chain. + assert.NotEqual(t, baseTM, opts.TrustedMaterial, "TrustedMaterial should be replaced.") + cas := opts.TrustedMaterial.FulcioCertificateAuthorities() + require.Len(t, cas, 1) + fca, ok := cas[0].(*root.FulcioCertificateAuthority) + require.True(t, ok) + assert.Len(t, fca.Intermediates, 1, "One intermediate should be merged from sig chain.") + }) +} From 31f9e99c731d616bfa01051f7fdd4d27af904c3d Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:37:34 +0000 Subject: [PATCH 055/638] Update registry.access.redhat.com/ubi9/go-toolset Docker tag to v9.8-1787559109 (#22430) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/operator-index-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 1d49c60af112e..945ce12704b5a 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787080706@sha256:71e89a1a51ab32cc30634d89ee4dc8ea40ad9991057fa1eae3b1af32bc7db73f + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787559109@sha256:643754d95cf8907b109b3e9182932e9c6e05334c97a74bb5cd991617e3d03080 imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: From c87b501c7c49d6de78ddcdd170d91326c66445a7 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:53:06 +0000 Subject: [PATCH 056/638] Refresh RPM lockfiles [SECURITY] (#22431) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 200 ++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index f055fdb10cc19..ca6b21c8a87cf 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -151,20 +151,20 @@ arches: name: postgresql-private-libs evr: 15.18-1.module+el9.8.0+24358+32c5830e sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.13-3.el9_8.1.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 32747 - checksum: sha256:f1878fee909c46fadc203680309dfe2f2cb5926796569dc71ef327f13e57f732 + size: 32440 + checksum: sha256:127b8467e15c3f233d87bdb85c57809332eefb2e7b483aca7a08790dd9b62b74 name: python3.12 - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-libs-3.12.13-3.el9_8.1.aarch64.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 10137763 - checksum: sha256:95a5f3c22a8604ce4e5b4c770ec455cf7fcda812a1a0bb7de988f388adf28155 + size: 10145691 + checksum: sha256:cca9ebdb4242036911ccc7271de7cb9e3d621c152b402f781ae22b700ab02231 name: python3.12-libs - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 1527128 @@ -949,13 +949,13 @@ arches: name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-10.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 660260 - checksum: sha256:dda4c59309d47d73d64291c53078cce330b8d5a9d95c903e133d9f37ec0e0a68 + size: 660770 + checksum: sha256:0fbb8043f9c02870c831da433f69b856a6674d02b60306d9cc01149ec89ed239 name: sqlite-libs - evr: 3.34.1-10.el9_8 - sourcerpm: sqlite-3.34.1-10.el9_8.src.rpm + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/s/systemd-252-67.el9_8.4.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 4156431 @@ -1069,12 +1069,12 @@ arches: checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 name: postgresql evr: 15.18-1.module+el9.8.0+24358+32c5830e - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.13-3.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 20886822 - checksum: sha256:7e01f587203e8d951fb7a38dff1c893cb1a3de5d7e762be6d6623cafd7c7435f + size: 20894742 + checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 name: python3.12 - evr: 3.12.13-3.el9_8.1 + evr: 3.12.14-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 9384965 @@ -1573,12 +1573,12 @@ arches: checksum: sha256:771a5ed07cdb690c75cd428a8113e2ee471270044e6cb8025d92c54e32c180ff name: shadow-utils evr: 2:4.9-16.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-10.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-11.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 25116090 - checksum: sha256:4387ae8d5a5a0b972b7a8612dca277499a43e7991163c7519e8a0ced2e57c90a + size: 25117596 + checksum: sha256:4efcc956008f0468362b41bdba79111c41027b682bcdea3ecca3744cf50ebfd7 name: sqlite - evr: 3.34.1-10.el9_8 + evr: 3.34.1-11.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/s/systemd-252-67.el9_8.4.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 45009674 @@ -1622,10 +1622,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/d82babc1f602404451bbd77e9b374581493872d17676995a9c26bfbb619f67e2-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/7cf8b3393b778ce2e2b45da66e8112c75fca9fc036d9890c28f42445d7f63d5f-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms size: 64613 - checksum: sha256:d82babc1f602404451bbd77e9b374581493872d17676995a9c26bfbb619f67e2 + checksum: sha256:7cf8b3393b778ce2e2b45da66e8112c75fca9fc036d9890c28f42445d7f63d5f - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1775,20 +1775,20 @@ arches: name: postgresql-private-libs evr: 15.18-1.module+el9.8.0+24358+32c5830e sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.13-3.el9_8.1.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 32808 - checksum: sha256:18f196faebc9865f11e1ca27f82cc69b92e5354cc5580072793beabb70eeec91 + size: 32494 + checksum: sha256:8e3fcabf92bf5be325dbb114ab2921b92c3a3007c9bab8b24f93d3c2a5bb44c8 name: python3.12 - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-libs-3.12.13-3.el9_8.1.ppc64le.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 10087484 - checksum: sha256:56acbc33e3d1bf7f5795fc53937a8e60e2d2f62e5eb668f017ecaa0255e42cd4 + size: 10093244 + checksum: sha256:32e4a045a4ce07f8db54811f223def1404fbabe4b7b521d48a56c7480f6b1057 name: python3.12-libs - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 1527128 @@ -2580,13 +2580,13 @@ arches: name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/s/sqlite-libs-3.34.1-10.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 758081 - checksum: sha256:6eac353037fe537e4639455a4ce8595b6b8c345913f84868b2a3288c6b54000a + size: 758085 + checksum: sha256:f51af80eda44e36b51b315a90e44a0bfd2c6c7d4ce4aadd40bac23af3fc43cb8 name: sqlite-libs - evr: 3.34.1-10.el9_8 - sourcerpm: sqlite-3.34.1-10.el9_8.src.rpm + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/s/systemd-252-67.el9_8.4.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 4435450 @@ -2700,12 +2700,12 @@ arches: checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 name: postgresql evr: 15.18-1.module+el9.8.0+24358+32c5830e - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.13-3.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 20886822 - checksum: sha256:7e01f587203e8d951fb7a38dff1c893cb1a3de5d7e762be6d6623cafd7c7435f + size: 20894742 + checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 name: python3.12 - evr: 3.12.13-3.el9_8.1 + evr: 3.12.14-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 9384965 @@ -3210,12 +3210,12 @@ arches: checksum: sha256:771a5ed07cdb690c75cd428a8113e2ee471270044e6cb8025d92c54e32c180ff name: shadow-utils evr: 2:4.9-16.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-10.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-11.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 25116090 - checksum: sha256:4387ae8d5a5a0b972b7a8612dca277499a43e7991163c7519e8a0ced2e57c90a + size: 25117596 + checksum: sha256:4efcc956008f0468362b41bdba79111c41027b682bcdea3ecca3744cf50ebfd7 name: sqlite - evr: 3.34.1-10.el9_8 + evr: 3.34.1-11.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/s/systemd-252-67.el9_8.4.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 45009674 @@ -3259,10 +3259,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/f8f33029fdd64d396f30ae5d7b714e83dd8326e22b357e8a2818d78dbadbb8b6-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/0621e21f421625934a7a35529a4daa5df035b14e50ebcc270955cee939299f52-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms size: 64720 - checksum: sha256:f8f33029fdd64d396f30ae5d7b714e83dd8326e22b357e8a2818d78dbadbb8b6 + checksum: sha256:0621e21f421625934a7a35529a4daa5df035b14e50ebcc270955cee939299f52 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3412,20 +3412,20 @@ arches: name: postgresql-private-libs evr: 15.18-1.module+el9.8.0+24358+32c5830e sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.13-3.el9_8.1.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 32622 - checksum: sha256:1b8026822b4302d08915e55b692f2cf274ed1a537d797d962883a7e8b7fc386a + size: 32298 + checksum: sha256:1d9c6400225666643a98052596e6a10a3e0f84c81daf0fef28867331749bf2f9 name: python3.12 - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-libs-3.12.13-3.el9_8.1.s390x.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 9971141 - checksum: sha256:20b2d5c7372f2f46c180d0e34806c99375326c1bb17afc2f4355715bfa28753d + size: 9974169 + checksum: sha256:000bf9abc0a7627f453b50e1a94181a2f6729927d8d3324aa27ab8b251fb1ed1 name: python3.12-libs - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 1527128 @@ -4210,13 +4210,13 @@ arches: name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/s/sqlite-libs-3.34.1-10.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 652115 - checksum: sha256:d773b3c1eac55a53efd669c87906804b31cf065a8f9885e6370b739c8f213f9d + size: 653093 + checksum: sha256:5897571d31b7cc9d3e8ef0e7dc9dbf8230bea207c9e926d4fef9fb635ab16f64 name: sqlite-libs - evr: 3.34.1-10.el9_8 - sourcerpm: sqlite-3.34.1-10.el9_8.src.rpm + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/s/systemd-252-67.el9_8.4.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 4167506 @@ -4324,12 +4324,12 @@ arches: checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 name: postgresql evr: 15.18-1.module+el9.8.0+24358+32c5830e - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.13-3.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 20886822 - checksum: sha256:7e01f587203e8d951fb7a38dff1c893cb1a3de5d7e762be6d6623cafd7c7435f + size: 20894742 + checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 name: python3.12 - evr: 3.12.13-3.el9_8.1 + evr: 3.12.14-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 9384965 @@ -4828,12 +4828,12 @@ arches: checksum: sha256:771a5ed07cdb690c75cd428a8113e2ee471270044e6cb8025d92c54e32c180ff name: shadow-utils evr: 2:4.9-16.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-10.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-11.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 25116090 - checksum: sha256:4387ae8d5a5a0b972b7a8612dca277499a43e7991163c7519e8a0ced2e57c90a + size: 25117596 + checksum: sha256:4efcc956008f0468362b41bdba79111c41027b682bcdea3ecca3744cf50ebfd7 name: sqlite - evr: 3.34.1-10.el9_8 + evr: 3.34.1-11.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/s/systemd-252-67.el9_8.4.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 45009674 @@ -4877,10 +4877,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/9191bef9cd1cd3847a15e36de138e212935ac4d2dcc070c1dc20cff4e1b2fc60-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/28d710080e2d47dce0fb30a77c16850d99fbe79f86acc634be3809452de38b05-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms size: 62927 - checksum: sha256:9191bef9cd1cd3847a15e36de138e212935ac4d2dcc070c1dc20cff4e1b2fc60 + checksum: sha256:28d710080e2d47dce0fb30a77c16850d99fbe79f86acc634be3809452de38b05 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -5023,20 +5023,20 @@ arches: name: postgresql-private-libs evr: 15.18-1.module+el9.8.0+24358+32c5830e sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.13-3.el9_8.1.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 32810 - checksum: sha256:e3824eb610d0a5c444a9c49462dcd3c31e9ef2ce0b6f42f5b75e0d492a64deb6 + size: 32484 + checksum: sha256:10640d8db1555fb7ae4e63c62be4b8f341032fb3ee52cc7176047438ab893b1f name: python3.12 - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-libs-3.12.13-3.el9_8.1.x86_64.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 10207533 - checksum: sha256:2135bca6d950ad5e8b3cc18e370fdeeedbe7bff413362247b28e0336d3bccb9d + size: 10211157 + checksum: sha256:0eaade47a20924ba0fe3fb0a93898e053772248793069f40c1ba80c5115fddbb name: python3.12-libs - evr: 3.12.13-3.el9_8.1 - sourcerpm: python3.12-3.12.13-3.el9_8.1.src.rpm + evr: 3.12.14-1.el9_8 + sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 1527128 @@ -5814,13 +5814,13 @@ arches: name: shadow-utils evr: 2:4.9-16.el9 sourcerpm: shadow-utils-4.9-16.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-10.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/s/sqlite-libs-3.34.1-11.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 664960 - checksum: sha256:7dc2202e08184890c851dcb2218a9abce14da150f12dfb8663ca306416e1d8d4 + size: 665095 + checksum: sha256:e5c20e933ec01f746a6c59a94cb99f46c6138dab3f387f0d02dab47f356e2e98 name: sqlite-libs - evr: 3.34.1-10.el9_8 - sourcerpm: sqlite-3.34.1-10.el9_8.src.rpm + evr: 3.34.1-11.el9_8 + sourcerpm: sqlite-3.34.1-11.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/s/systemd-252-67.el9_8.4.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 4401138 @@ -5934,12 +5934,12 @@ arches: checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 name: postgresql evr: 15.18-1.module+el9.8.0+24358+32c5830e - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.13-3.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 20886822 - checksum: sha256:7e01f587203e8d951fb7a38dff1c893cb1a3de5d7e762be6d6623cafd7c7435f + size: 20894742 + checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 name: python3.12 - evr: 3.12.13-3.el9_8.1 + evr: 3.12.14-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 9384965 @@ -6438,12 +6438,12 @@ arches: checksum: sha256:771a5ed07cdb690c75cd428a8113e2ee471270044e6cb8025d92c54e32c180ff name: shadow-utils evr: 2:4.9-16.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-10.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/s/sqlite-3.34.1-11.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 25116090 - checksum: sha256:4387ae8d5a5a0b972b7a8612dca277499a43e7991163c7519e8a0ced2e57c90a + size: 25117596 + checksum: sha256:4efcc956008f0468362b41bdba79111c41027b682bcdea3ecca3744cf50ebfd7 name: sqlite - evr: 3.34.1-10.el9_8 + evr: 3.34.1-11.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/s/systemd-252-67.el9_8.4.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 45009674 @@ -6487,7 +6487,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/742742f4aeb2fd9c8093031004809b35f2dceb373b6b0d083f00845e374cb570-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/95263aa61868e76c0831959590c4ef16bf051151b74e33442a6bef37d14c0366-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms size: 67289 - checksum: sha256:742742f4aeb2fd9c8093031004809b35f2dceb373b6b0d083f00845e374cb570 + checksum: sha256:95263aa61868e76c0831959590c4ef16bf051151b74e33442a6bef37d14c0366 From a85c37bef2d949ff12d647434c51df7996b31ad6 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Tue, 25 Aug 2026 09:07:56 +0200 Subject: [PATCH 057/638] ROX-35606: add signingKeyBundle.configMapName field (#22369) Co-authored-by: Test Co-authored-by: Claude Opus 4.6 Co-authored-by: Stephan Hesselmann --- .../internal/config-shape.yaml | 2 + .../01-central-13-deployment.yaml.htpl | 11 +++++ .../values-public.yaml.example.htpl | 11 ++++- operator/api/v1alpha1/central_types.go | 11 ++++- .../api/v1alpha1/zz_generated.deepcopy.go | 5 +++ .../platform.stackrox.io_centrals.yaml | 13 ++++++ .../rhacs-operator.clusterserviceversion.yaml | 14 +++++++ .../bases/platform.stackrox.io_centrals.yaml | 13 ++++++ .../rhacs-operator.clusterserviceversion.yaml | 32 +++++++++++---- .../central/values/translation/translation.go | 6 +++ .../values/translation/translation_test.go | 41 +++++++++++++++++++ .../helmtest/signing-key-bundle.test.yaml | 31 ++++++++++++++ 12 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 pkg/helm/charts/tests/centralservices/testdata/helmtest/signing-key-bundle.test.yaml diff --git a/image/templates/helm/stackrox-central/internal/config-shape.yaml b/image/templates/helm/stackrox-central/internal/config-shape.yaml index 72c34ddb354fc..46786401bd947 100644 --- a/image/templates/helm/stackrox-central/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-central/internal/config-shape.yaml @@ -86,6 +86,8 @@ central: mounts: configMaps: null # [string] secrets: null # [string] + signingKeyBundle: + configMapName: null # string extraMounts: null # [dict] db: nodeSelector: null # string | dict diff --git a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl index 0c83dce0a216b..9505542ea935c 100644 --- a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl +++ b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl @@ -215,6 +215,11 @@ spec: mountPath: /run/secrets/stackrox.io/ocp-tls readOnly: true {{- end }} + {{- if ._rox.central.signingKeyBundle.configMapName }} + - name: signing-key-bundle + mountPath: /tmp/redhat-signing-keys + readOnly: true + {{- end }} {{- range $extraMount := (default list ._rox.central.extraMounts) }} - name: {{ $extraMount.name }} {{- $extraMount.mount | toYaml | nindent 10 }} @@ -307,6 +312,12 @@ spec: secret: secretName: central-ocp-tls {{- end }} + {{- if ._rox.central.signingKeyBundle.configMapName }} + - name: signing-key-bundle + configMap: + name: {{ ._rox.central.signingKeyBundle.configMapName }} + optional: true + {{- end }} {{- range $extraMount := (default list ._rox.central.extraMounts) }} - name: {{ $extraMount.name }} {{- $extraMount.source | toYaml | nindent 8 }} diff --git a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl index d405ac4034bcb..0592d03c24a6e 100644 --- a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl +++ b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl @@ -226,8 +226,15 @@ # reencrypt: # enabled: true # -# # Additional volume mounts for the Central container. Only few people will require this. -# extraMounts: +# +# # Name of a ConfigMap containing a Red Hat signing key bundle. The bundle must be stored with the key `bundle.json`. +# # When set, the ConfigMap is mounted into Central at /tmp/redhat-signing-keys/. +# # To rotate keys, update the ConfigMap directly — no helm upgrade needed. +# signingKeyBundle: +# configMapName: "redhat-signing-key-bundle" +# +# # Additional volume mounts for the Central container. Only few people will require this. +# extraMounts: # - name: my-configmap # the name of the volume # # The source of the volume. This will be embedded as-is in the `volume:` section of the # # pod spec. diff --git a/operator/api/v1alpha1/central_types.go b/operator/api/v1alpha1/central_types.go index 8f9d435da2f05..e0f9703aed71a 100644 --- a/operator/api/v1alpha1/central_types.go +++ b/operator/api/v1alpha1/central_types.go @@ -168,13 +168,20 @@ type CentralComponentSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,order=7,displayName="Declarative Configuration",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} DeclarativeConfiguration *DeclarativeConfiguration `json:"declarativeConfiguration,omitempty"` + // References a ConfigMap containing the Red Hat signing key bundle (key `bundle.json`). + // This allows air-gapped customers to provide or update the signing keys used for + // signature verification of Red Hat container images. The ConfigMap is managed + // externally; key rotation requires only editing the ConfigMap, not a CR update. + //+operator-sdk:csv:customresourcedefinitions:type=spec,order=8,displayName="Signing Key Bundle",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} + SigningKeyBundle *LocalConfigMapReference `json:"signingKeyBundle,omitempty"` + // Configures the encryption of notifier secrets stored in the Central DB. - //+operator-sdk:csv:customresourcedefinitions:type=spec,order=8,displayName="Notifier Secrets Encryption",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} + //+operator-sdk:csv:customresourcedefinitions:type=spec,order=9,displayName="Notifier Secrets Encryption",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} NotifierSecretsEncryption *NotifierSecretsEncryption `json:"notifierSecretsEncryption,omitempty"` // Configures the rollout strategy for the Central deployment. // The default is: Recreate. - //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Rollout Strategy",order=9 + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Rollout Strategy",order=10 RolloutStrategy *RolloutStrategy `json:"rolloutStrategy,omitempty"` //+operator-sdk:csv:customresourcedefinitions:type=spec,order=99 diff --git a/operator/api/v1alpha1/zz_generated.deepcopy.go b/operator/api/v1alpha1/zz_generated.deepcopy.go index d0774f2e62b88..00a97401d5ab1 100644 --- a/operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/operator/api/v1alpha1/zz_generated.deepcopy.go @@ -217,6 +217,11 @@ func (in *CentralComponentSpec) DeepCopyInto(out *CentralComponentSpec) { *out = new(DeclarativeConfiguration) (*in).DeepCopyInto(*out) } + if in.SigningKeyBundle != nil { + in, out := &in.SigningKeyBundle, &out.SigningKeyBundle + *out = new(LocalConfigMapReference) + **out = **in + } if in.NotifierSecretsEncryption != nil { in, out := &in.NotifierSecretsEncryption, &out.NotifierSecretsEncryption *out = new(NotifierSecretsEncryption) diff --git a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml index 5ed63ce403b9b..0dfdf85a0f630 100644 --- a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml @@ -601,6 +601,19 @@ spec: - Recreate - RollingUpdate type: string + signingKeyBundle: + description: |- + References a ConfigMap containing the Red Hat signing key bundle (key `bundle.json`). + This allows air-gapped customers to provide or update the signing keys used for + signature verification of Red Hat container images. The ConfigMap is managed + externally; key rotation requires only editing the ConfigMap, not a CR update. + properties: + name: + description: The name of the referenced config map. + type: string + required: + - name + type: object telemetry: description: |- Configures telemetry settings for Central. If enabled, Central transmits telemetry and diagnostic diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index e7a65a48e9f35..933b94b863bb5 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -181,6 +181,15 @@ spec: path: central.declarativeConfiguration x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + References a ConfigMap containing the Red Hat signing key bundle (key `bundle.json`). + This allows air-gapped customers to provide or update the signing keys used for + signature verification of Red Hat container images. The ConfigMap is managed + externally; key rotation requires only editing the ConfigMap, not a CR update. + displayName: Signing Key Bundle + path: central.signingKeyBundle + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Configures the encryption of notifier secrets stored in the Central DB. displayName: Notifier Secrets Encryption path: central.notifierSecretsEncryption @@ -480,6 +489,11 @@ spec: path: central.persistence.persistentVolumeClaim.storageClassName x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: The name of the referenced config map. + displayName: Name + path: central.signingKeyBundle.name + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: |- Specifies whether Telemetry is enabled. The default is: true. diff --git a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml index 6daa3a06e0a21..3a8cfbede7a24 100644 --- a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml @@ -600,6 +600,19 @@ spec: - Recreate - RollingUpdate type: string + signingKeyBundle: + description: |- + References a ConfigMap containing the Red Hat signing key bundle (key `bundle.json`). + This allows air-gapped customers to provide or update the signing keys used for + signature verification of Red Hat container images. The ConfigMap is managed + externally; key rotation requires only editing the ConfigMap, not a CR update. + properties: + name: + description: The name of the referenced config map. + type: string + required: + - name + type: object telemetry: description: |- Configures telemetry settings for Central. If enabled, Central transmits telemetry and diagnostic diff --git a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml index a88f178dc19bd..8012caafb1752 100644 --- a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml @@ -570,10 +570,13 @@ spec: path: customize x-descriptors: - urn:alm:descriptor:com.tectonic.ui:advanced - - description: Configures the encryption of notifier secrets stored in the Central - DB. - displayName: Notifier Secrets Encryption - path: central.notifierSecretsEncryption + - description: |- + References a ConfigMap containing the Red Hat signing key bundle (key `bundle.json`). + This allows air-gapped customers to provide or update the signing keys used for + signature verification of Red Hat container images. The ConfigMap is managed + externally; key rotation requires only editing the ConfigMap, not a CR update. + displayName: Signing Key Bundle + path: central.signingKeyBundle x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden - description: Overlays @@ -581,16 +584,22 @@ spec: path: overlays x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden - - description: |- - Configures the rollout strategy for the Central deployment. - The default is: Recreate. - displayName: Rollout Strategy - path: central.rolloutStrategy + - description: Configures the encryption of notifier secrets stored in the Central + DB. + displayName: Notifier Secrets Encryption + path: central.notifierSecretsEncryption + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Monitoring configuration. displayName: Monitoring path: monitoring x-descriptors: - urn:alm:descriptor:com.tectonic.ui:advanced + - description: |- + Configures the rollout strategy for the Central deployment. + The default is: Recreate. + displayName: Rollout Strategy + path: central.rolloutStrategy - description: Network configuration. displayName: Network path: network @@ -925,6 +934,11 @@ spec: path: central.persistence.persistentVolumeClaim.storageClassName x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: The name of the referenced config map. + displayName: Name + path: central.signingKeyBundle.name + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap - description: The name of the referenced secret. displayName: Name path: imagePullSecrets[0].name diff --git a/operator/internal/central/values/translation/translation.go b/operator/internal/central/values/translation/translation.go index f011e180b52aa..637901c6ce3e9 100644 --- a/operator/internal/central/values/translation/translation.go +++ b/operator/internal/central/values/translation/translation.go @@ -271,6 +271,12 @@ func getCentralComponentValues(ctx context.Context, c *platform.CentralComponent cv.AddChild("declarativeConfiguration", getDeclarativeConfigurationValues(c.DeclarativeConfiguration)) + if c.SigningKeyBundle != nil && c.SigningKeyBundle.Name != "" { + signingKeyBundle := translation.NewValuesBuilder() + signingKeyBundle.SetStringValue("configMapName", c.SigningKeyBundle.Name) + cv.AddChild("signingKeyBundle", &signingKeyBundle) + } + if c.GetNotifierSecretsEncryptionEnabled() { notifierSecretsEncryption := translation.NewValuesBuilder() notifierSecretsEncryption.SetBoolValue("enabled", true) diff --git a/operator/internal/central/values/translation/translation_test.go b/operator/internal/central/values/translation/translation_test.go index ad618191b9c97..3a90a8eeb72c5 100644 --- a/operator/internal/central/values/translation/translation_test.go +++ b/operator/internal/central/values/translation/translation_test.go @@ -243,6 +243,41 @@ func TestTranslate(t *testing.T) { }, }, + "signing key bundle with empty name is not emitted": { + args: args{ + c: platform.Central{ + Spec: platform.CentralSpec{ + Central: &platform.CentralComponentSpec{ + SigningKeyBundle: &platform.LocalConfigMapReference{ + Name: "", + }, + }, + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "stackrox", + }, + }, + pvcs: []*corev1.PersistentVolumeClaim{defaultPvc}, + }, + want: chartutil.Values{ + "monitoring": map[string]interface{}{ + "openshift": map[string]interface{}{ + "enabled": true, + }, + }, + "central": map[string]interface{}{ + "exposeMonitoring": false, + "db": map[string]interface{}{ + "persistence": map[string]interface{}{ + "persistentVolumeClaim": map[string]interface{}{ + "createClaim": false, + }, + }, + }, + }, + }, + }, + "empty spec no pvc": { args: args{ c: platform.Central{ @@ -389,6 +424,9 @@ func TestTranslate(t *testing.T) { }, }, }, + SigningKeyBundle: &platform.LocalConfigMapReference{ + Name: "redhat-signing-key-bundle", + }, NotifierSecretsEncryption: &platform.NotifierSecretsEncryption{ Enabled: pointer.Bool(true), }, @@ -652,6 +690,9 @@ func TestTranslate(t *testing.T) { }, }, }, + "signingKeyBundle": map[string]interface{}{ + "configMapName": "redhat-signing-key-bundle", + }, "notifierSecretsEncryption": map[string]interface{}{ "enabled": true, }, diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/signing-key-bundle.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/signing-key-bundle.test.yaml new file mode 100644 index 0000000000000..e18cf885cb404 --- /dev/null +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/signing-key-bundle.test.yaml @@ -0,0 +1,31 @@ +values: + imagePullSecrets: + allowNone: true +tests: +- name: "No signing key bundle by default" + expect: | + [.deployments.central.spec.template.spec.containers[0].volumeMounts[] + | select(.name == "signing-key-bundle")] | assertThat(length == 0) + [.deployments.central.spec.template.spec.volumes[] + | select(.name == "signing-key-bundle")] | assertThat(length == 0) + +- name: "Setting signingKeyBundle.configMapName adds volume and volumeMount" + values: + central: + signingKeyBundle: + configMapName: "redhat-signing-key-bundle" + expect: | + .deployments.central.spec.template.spec.containers[0].volumeMounts[] + | select(.name == "signing-key-bundle") | assertThat(. != null) + .deployments.central.spec.template.spec.containers[0].volumeMounts[] + | select(.name == "signing-key-bundle") | assertThat(.readOnly == true) + .deployments.central.spec.template.spec.containers[0].volumeMounts[] + | select(.name == "signing-key-bundle") | assertThat(.mountPath == "/tmp/redhat-signing-keys") + .deployments.central.spec.template.spec.volumes[] | select(.name == "signing-key-bundle") + | assertThat(. != null) + .deployments.central.spec.template.spec.volumes[] | select(.name == "signing-key-bundle") + | assertThat(.configMap != null) + .deployments.central.spec.template.spec.volumes[] | select(.name == "signing-key-bundle") + | assertThat(.configMap.name == "redhat-signing-key-bundle") + .deployments.central.spec.template.spec.volumes[] | select(.name == "signing-key-bundle") + | assertThat(.configMap.optional == true) From 96d7e5e09fd01553362bcc4cdb636a33aa5a0dcc Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:32:44 +0200 Subject: [PATCH 058/638] ROX-35849: Jitter VMScraper first-wave and cadence schedules (#22402) --- pkg/env/virtualmachine.go | 7 + sensor/common/virtualmachine/ACK_MODEL.md | 2 +- .../common/virtualmachine/metrics/metrics.go | 58 ++++ .../virtualmachine/vmscraper/schedule.go | 36 ++ .../virtualmachine/vmscraper/schedule_test.go | 23 ++ .../virtualmachine/vmscraper/scraper.go | 75 ++++- .../virtualmachine/vmscraper/scraper_test.go | 15 +- .../vmscraper/spreading_test.go | 316 ++++++++++++++++++ 8 files changed, 513 insertions(+), 19 deletions(-) create mode 100644 sensor/common/virtualmachine/vmscraper/spreading_test.go diff --git a/pkg/env/virtualmachine.go b/pkg/env/virtualmachine.go index 8d10dc903303e..ef4e34dc71b26 100644 --- a/pkg/env/virtualmachine.go +++ b/pkg/env/virtualmachine.go @@ -80,4 +80,11 @@ var ( // vulnerability definitions, mirroring ROX_REPROCESSING_INTERVAL and ROX_NODE_SCANNING_INTERVAL's // role for image and node scanning. VirtualMachinesScraperMandatoryRefreshInterval = registerDurationSetting("ROX_VIRTUAL_MACHINES_SCRAPER_MANDATORY_REFRESH_INTERVAL", 4*time.Hour) + + // VirtualMachinesScraperSteadySpreadFraction is the fraction of the poll interval used as the + // one-sided post-poll random band W for cadence reschedule (nextAttemptAt = + // now + pollInterval + U(0, W)). Default 2/3. Internal Sensor env only; not Operator/Helm-exposed. + // Valid range is [0.01, 1] so W is not effectively zero; out-of-range values fall back to the default. + VirtualMachinesScraperSteadySpreadFraction = RegisterFloatSetting("ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION", 2.0/3). + WithMinimum(0.01).WithMaximum(1) ) diff --git a/sensor/common/virtualmachine/ACK_MODEL.md b/sensor/common/virtualmachine/ACK_MODEL.md index b6c6a785bfb65..ca9318eeed1c1 100644 --- a/sensor/common/virtualmachine/ACK_MODEL.md +++ b/sensor/common/virtualmachine/ACK_MODEL.md @@ -4,7 +4,7 @@ VM index reports flow directly from Sensor to Central; Compliance is not involve `VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central through `vmIndex.Handler`, the same handler used by every other producer of `v1.IndexReport`s. -A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks the per-VM schedule. Each VM is due at its own `nextAttemptAt`. Success and permanent failures reschedule at `ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL`. Retryable pull failures and NACKs share one exponential backoff (`ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF`, default 10s, doubled each time, capped at `min(poll interval, 30m)`). +A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks the per-VM schedule. Each VM is due at its own `nextAttemptAt`. Success and permanent failures reschedule at pollInterval + U(0, W), where W is `ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION` times the poll interval (default 2/3). Retryable pull failures and NACKs share one exponential backoff (`ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF`, default 10s, doubled each time, capped at `min(poll interval, 30m)`). Central's `SensorACK` is delivered to Sensor components whose `Accepts()` matches. Only `VMScraper` accepts `SensorACK_VM_INDEX_REPORT`; `Handler` does not. diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index 8442c64f2a4d9..35bcb8530a988 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -223,6 +223,60 @@ var PullTrackedVMs = prometheus.NewGauge( }, ) +// PullDueVMs is how many VMs were eligible to scrape at the start of the last +// tick (nextAttemptAt had arrived and they were not already in flight). +var PullDueVMs = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_due_vms", + Help: "How many VMs were eligible to scrape at the beginning of the last scraper tick", + }, +) + +// PullStartsPerTick is how many VM scrapes each tick launches. Idle ticks +// (nobody due) are omitted so the histogram is not dominated by zeros. +var PullStartsPerTick = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_starts_per_tick", + Help: "How many VM scrapes the scraper starts in a single tick. " + + "Idle ticks are not observed. Compare with vsock_pull_due_vms: " + + "spread due times keep both small; a mass of large starts is a dump.", + Buckets: []float64{0, 1, 2, 3, 5, 8, 10, 15, 20, 30, 50, 100}, + }, +) + +// PullForwardInterarrivalSeconds is the Sensor-level gap between consecutive +// successful forwards to Central. The first forward after start is not observed. +var PullForwardInterarrivalSeconds = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_forward_interarrival_seconds", + Help: "Seconds between consecutive successful VM index-report forwards " + + "from this Sensor to Central. The first forward after Sensor start does not count.", + // 10ms to ~47h. Sized for a 24h poll in extreme cases so those + // gaps stay in a finite bucket instead of +Inf. + Buckets: prometheus.ExponentialBuckets(0.01, 2, 25), + }, +) + +// PullScheduleOffsetSeconds is the random extra delay drawn when a VM returns +// to cadence after success or a permanent non-retry outcome (retries/NACKs do not). +var PullScheduleOffsetSeconds = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_schedule_offset_seconds", + Help: "Random extra delay (seconds) added on top of the poll interval " + + "when scheduling a VM's next attempt after a return-to-cadence outcome.", + // 250ms to ~36h. Sized for a 24h poll in extreme cases (W up to 24h). + Buckets: prometheus.ExponentialBuckets(0.25, 2, 20), + }, +) + func init() { prometheus.MustRegister( IndexReportsSent, @@ -240,5 +294,9 @@ func init() { PullRequestsTotal, PullTicksTotal, PullTrackedVMs, + PullDueVMs, + PullStartsPerTick, + PullForwardInterarrivalSeconds, + PullScheduleOffsetSeconds, ) } diff --git a/sensor/common/virtualmachine/vmscraper/schedule.go b/sensor/common/virtualmachine/vmscraper/schedule.go index 7f62472d748a4..15f4438ed2065 100644 --- a/sensor/common/virtualmachine/vmscraper/schedule.go +++ b/sensor/common/virtualmachine/vmscraper/schedule.go @@ -10,6 +10,10 @@ const ( maxReconcile = 5 * time.Minute // defaultTickInterval is the scraper scheduler step. Independent of retry-backoff. defaultTickInterval = 10 * time.Second + // maxNewVMIndexReportWindow caps the schedule window for a newly added VM's + // first index report when the poll interval is long. + maxNewVMIndexReportWindow = 20 * time.Minute + newVMIndexReportWindowPollDivisor = 3 ) func backoffCap(pollInterval time.Duration) time.Duration { @@ -28,3 +32,35 @@ func nextBackoff(current, pollInterval, initial time.Duration) time.Duration { } return min(current*2, limit) } + +// steadySpreadWidth is how wide the post-poll random band is when a VM +// returns to normal cadence (spreadFraction × pollInterval). +func steadySpreadWidth(pollInterval time.Duration, spreadFraction float64) time.Duration { + if pollInterval <= 0 || spreadFraction <= 0 { + return 0 + } + return time.Duration(float64(pollInterval) * spreadFraction) +} + +// newVMIndexReportWindow is the schedule window for a newly added VM's first +// index report (min(maxNewVMIndexReportWindow, poll/3)). +func newVMIndexReportWindow(pollInterval time.Duration) time.Duration { + if pollInterval <= 0 { + return 0 + } + return min(maxNewVMIndexReportWindow, pollInterval/newVMIndexReportWindowPollDivisor) +} + +// randOffset draws a delay in [0, max] from a unit sample in [0, 1]. +func randOffset(max time.Duration, unit float64) time.Duration { + if max <= 0 { + return 0 + } + if unit < 0 { + unit = 0 + } + if unit > 1 { + unit = 1 + } + return time.Duration(float64(max) * unit) +} diff --git a/sensor/common/virtualmachine/vmscraper/schedule_test.go b/sensor/common/virtualmachine/vmscraper/schedule_test.go index e22bf8da85726..fdc118dc03ea4 100644 --- a/sensor/common/virtualmachine/vmscraper/schedule_test.go +++ b/sensor/common/virtualmachine/vmscraper/schedule_test.go @@ -64,3 +64,26 @@ func TestReconcilePeriod(t *testing.T) { assert.Equal(t, 5*time.Minute, reconcilePeriod(time.Hour)) assert.Equal(t, time.Minute, reconcilePeriod(time.Minute)) } + +func TestSteadySpreadWidth(t *testing.T) { + t.Parallel() + assert.Equal(t, 40*time.Minute, steadySpreadWidth(time.Hour, 2.0/3)) + assert.Equal(t, 0, int(steadySpreadWidth(time.Hour, 0))) + assert.Equal(t, 0, int(steadySpreadWidth(0, 2.0/3))) +} + +func TestNewVMIndexReportWindow(t *testing.T) { + t.Parallel() + assert.Equal(t, maxNewVMIndexReportWindow, newVMIndexReportWindow(time.Hour)) + assert.Equal(t, 100*time.Second, newVMIndexReportWindow(5*time.Minute)) + assert.Equal(t, time.Duration(0), newVMIndexReportWindow(0)) +} + +func TestRandOffset(t *testing.T) { + t.Parallel() + assert.Equal(t, time.Duration(0), randOffset(0, 0.5)) + assert.Equal(t, 20*time.Minute, randOffset(40*time.Minute, 0.5)) + assert.Equal(t, time.Duration(0), randOffset(40*time.Minute, 0)) + assert.Equal(t, 40*time.Minute, randOffset(40*time.Minute, 1)) + assert.Equal(t, time.Duration(0), randOffset(40*time.Minute, -1)) +} diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index aa1811e688b19..a74781846bb07 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "math/rand" "strings" "sync/atomic" "time" @@ -82,15 +83,20 @@ type VMScraper struct { perVMTimeout time.Duration mandatoryRefreshAfter time.Duration concurrency int + spreadFraction float64 warnMaxBytes int stopper concurrency.Stopper started atomic.Bool now func() time.Time - - mu sync.Mutex - vmState map[string]*vmState - lastReconcile time.Time - inFlight set.StringSet + // randFloat64 returns a unit sample in [0, 1] for schedule offsets; tests inject a fixed source. + randFloat64 func() float64 + lastSpreadWarnNumVMs int + + mu sync.Mutex + vmState map[string]*vmState + lastReconcile time.Time + inFlight set.StringSet + lastForwardTime time.Time // Sensor-level; for forward interarrival metric } type vmState struct { @@ -119,6 +125,7 @@ func New(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client perVMTimeout: env.VirtualMachinesScraperPerVMTimeout.DurationSetting(), mandatoryRefreshAfter: env.VirtualMachinesScraperMandatoryRefreshInterval.DurationSetting(), concurrency: env.VirtualMachinesScraperConcurrency.IntegerSetting(), + spreadFraction: env.VirtualMachinesScraperSteadySpreadFraction.FloatSetting(), // Warn once a report is halfway to the hard response-size ceiling, // so operators get advance notice before reports start actually // being rejected at that limit. @@ -126,6 +133,7 @@ func New(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client vmState: make(map[string]*vmState), inFlight: set.NewStringSet(), now: time.Now, + randFloat64: rand.Float64, } } @@ -259,12 +267,19 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { } due := s.dueKeys() + metrics.PullDueVMs.Set(float64(len(due))) log.Debugf("VMScraper: tick: %d due VMs %v (concurrency=%d, reconcile=%v)", len(due), due, s.concurrency, reconcile) metrics.PullTicksTotal.Inc() concurrency.WithLock(&s.mu, func() { metrics.PullTrackedVMs.Set(float64(len(s.vmState))) }) + // A tick launches every due VM, so this is the start count. + started := len(due) + if started > 0 { + metrics.PullStartsPerTick.Observe(float64(started)) + } + var successCount atomic.Int32 g, gCtx := errgroup.WithContext(ctx) g.SetLimit(s.concurrency) @@ -284,17 +299,22 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { metrics.PullTickDurationSeconds.Observe(elapsed.Seconds()) } +// reconcile syncs vmState with currently running VMs: drop gone ones, and +// schedule first attempts for new ones across the initial scan window (new +// guests are often still booting; Sensor restart rehydrates many at once). func (s *VMScraper) reconcile() { vms := s.store.ListRunning() liveKeys := set.NewStringSet() + var numVMs int concurrency.WithLock(&s.mu, func() { now := s.now() + newVMWindow := newVMIndexReportWindow(s.interval) for _, vm := range vms { key := vm.Key() liveKeys.Add(key) st, ok := s.vmState[key] if !ok { - st = &vmState{nextAttemptAt: now, vmID: vm.ID} + st = &vmState{nextAttemptAt: now.Add(randOffset(newVMWindow, s.randFloat64())), vmID: vm.ID} s.vmState[key] = st } st.vmID = vm.ID @@ -304,8 +324,25 @@ func (s *VMScraper) reconcile() { delete(s.vmState, key) } } + numVMs = len(s.vmState) s.lastReconcile = now }) + s.warnIfSpreadSaturated(numVMs) +} + +// warnIfSpreadSaturated logs when running VMs exceed what this Sensor's +// cadence spread can serialize at one scrape per tick. +func (s *VMScraper) warnIfSpreadSaturated(numVMs int) { + if s.tickInterval <= 0 || s.lastSpreadWarnNumVMs == numVMs { + return + } + s.lastSpreadWarnNumVMs = numVMs + capacity := int(steadySpreadWidth(s.interval, s.spreadFraction) / s.tickInterval) + if capacity <= 0 || numVMs <= capacity { + return + } + log.Warnf("VMScraper: %d running VMs exceed what ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL=%s can space out on this Sensor (about %d); index reports may be forwarded in bursts.", + numVMs, s.interval, capacity) } func (s *VMScraper) dueKeys() []string { @@ -426,6 +463,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool newGen := result.Meta.GetReportGeneration() s.commitVMState(key, newGen, result.Meta.GetEpoch()) + s.observeForwardInterarrival() next := s.scheduleAfterAttempt(key, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=forwarded next=%s", key, next) @@ -438,6 +476,18 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool return true } +// observeForwardInterarrival records the Sensor-level gap between successful +// forwards. The first forward after start does not observe a sample. +func (s *VMScraper) observeForwardInterarrival() { + concurrency.WithLock(&s.mu, func() { + now := s.now() + if !s.lastForwardTime.IsZero() { + metrics.PullForwardInterarrivalSeconds.Observe(now.Sub(s.lastForwardTime).Seconds()) + } + s.lastForwardTime = now + }) +} + type scrapeOutcome int const ( @@ -446,8 +496,9 @@ const ( scrapeNonRetryable ) -// scheduleAfterAttempt updates nextAttemptAt/backoff for key and returns the -// delay until the next attempt (0 if the slot was dropped). +// scheduleAfterAttempt sets when this VM may be tried again and returns that +// delay. Retries use short exponential backoff; success / permanent failure +// return to poll cadence with a random offset in [0, steadyWidth]. func (s *VMScraper) scheduleAfterAttempt(key string, outcome scrapeOutcome) time.Duration { return concurrency.WithLock1(&s.mu, func() time.Duration { st, ok := s.vmState[key] @@ -461,10 +512,12 @@ func (s *VMScraper) scheduleAfterAttempt(key string, outcome scrapeOutcome) time st.nextAttemptAt = now.Add(st.backoff) return st.backoff case scrapeOK, scrapeNonRetryable: - // Both return to the normal poll cadence without growing backoff. st.backoff = 0 - st.nextAttemptAt = now.Add(s.interval) - return s.interval + offset := randOffset(steadySpreadWidth(s.interval, s.spreadFraction), s.randFloat64()) + metrics.PullScheduleOffsetSeconds.Observe(offset.Seconds()) + delay := s.interval + offset + st.nextAttemptAt = now.Add(delay) + return delay default: return 0 } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index a53f3e72dc518..2ebe838f1cf3d 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -57,12 +57,11 @@ func (m *mockStore) Get(id virtualmachine.VMID) *virtualmachine.Info { type mockDialer struct { err error errQueue []error - callIdx int + callIdx atomic.Int32 } func (m *mockDialer) Dial(_ context.Context, _, _ string, _ uint32, _ bool) (io.ReadWriteCloser, error) { - idx := m.callIdx - m.callIdx++ + idx := int(m.callIdx.Add(1) - 1) if idx < len(m.errQueue) && m.errQueue[idx] != nil { return nil, m.errQueue[idx] } @@ -764,10 +763,12 @@ func newTestScraper(store RunningVMStore, sender IndexReportSender, dialer VMDia concurrency: 1, // Half of the 16MiB default pull response-size ceiling — same // derivation New() uses from env.VirtualMachinesPullMaxResponseSizeKB. - warnMaxBytes: 8 << 20, - vmState: make(map[string]*vmState), - inFlight: set.NewStringSet(), - now: clock.Now, + warnMaxBytes: 8 << 20, + spreadFraction: 2.0 / 3, + vmState: make(map[string]*vmState), + inFlight: set.NewStringSet(), + now: clock.Now, + randFloat64: func() float64 { return 0 }, }, clock } diff --git a/sensor/common/virtualmachine/vmscraper/spreading_test.go b/sensor/common/virtualmachine/vmscraper/spreading_test.go new file mode 100644 index 0000000000000..adf8718224130 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/spreading_test.go @@ -0,0 +1,316 @@ +package vmscraper + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/logging" + "github.com/stackrox/rox/sensor/common/virtualmachine" + "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" + "github.com/stackrox/rox/sensor/common/virtualmachine/vsockclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +// sequencedRand returns successive values from seq, then the last value. +func sequencedRand(seq []float64) func() float64 { + var i atomic.Int32 + return func() float64 { + idx := int(i.Add(1) - 1) + if idx >= len(seq) { + return seq[len(seq)-1] + } + return seq[idx] + } +} + +func TestVMScraper_CadenceRescheduleUsesSteadyBand(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "vm-a", 1)}} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + }) + s.spreadFraction = 2.0 / 3 + // First draw (reconcile insert) keeps the VM due now; second is the cadence offset. + s.randFloat64 = sequencedRand([]float64{0, 0.5}) + + start := clock.Now() + s.pollOnce(context.Background()) + + steadyWidth := steadySpreadWidth(s.interval, s.spreadFraction) + want := start.Add(s.interval + steadyWidth/2) + assert.Equal(t, want, cachedNextAttemptAt(t, s, "ns/vm-a")) + assert.Greater(t, histogramSampleCount(t, metrics.PullScheduleOffsetSeconds), uint64(0)) +} + +func TestVMScraper_ManyCadencedSuccessesSpanSteadyBand(t *testing.T) { + const numVMs = 5 + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) + } + store := &mockStore{vms: vms} + s, clock := newTestScraper(store, &safeSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.interval = time.Hour + s.spreadFraction = 2.0 / 3 + s.reconcileEvery = reconcilePeriod(s.interval) + s.randFloat64 = sequencedRand([]float64{0, 0.25, 0.5, 0.75, 1}) + + now := clock.Now() + concurrency.WithLock(&s.mu, func() { + for _, vm := range vms { + s.vmState[vm.Key()] = &vmState{ + vmID: vm.ID, + nextAttemptAt: now, + } + } + s.lastReconcile = now + }) + + s.tick(context.Background(), false) + + steadyWidth := steadySpreadWidth(s.interval, s.spreadFraction) + minNext, maxNext := now.Add(s.interval), now.Add(s.interval+steadyWidth) + var earliest, latest time.Time + concurrency.WithLock(&s.mu, func() { + for _, st := range s.vmState { + if earliest.IsZero() || st.nextAttemptAt.Before(earliest) { + earliest = st.nextAttemptAt + } + if latest.IsZero() || st.nextAttemptAt.After(latest) { + latest = st.nextAttemptAt + } + assert.False(t, st.nextAttemptAt.Before(minNext)) + assert.False(t, st.nextAttemptAt.After(maxNext)) + } + }) + assert.GreaterOrEqual(t, latest.Sub(earliest), steadyWidth/2, + "distinct RNG draws should spread nextAttemptAt across a wide band") +} + +func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { + const numVMs = 10 + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) + } + store := &mockStore{vms: vms} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.interval = time.Hour + units := make([]float64, numVMs) + for i := range units { + units[i] = float64(i) / float64(numVMs-1) + } + s.randFloat64 = sequencedRand(units) + + s.reconcile() + + newVMWindow := newVMIndexReportWindow(s.interval) + now := clock.Now() + var earliest, latest time.Time + concurrency.WithLock(&s.mu, func() { + require.Len(t, s.vmState, numVMs) + for _, st := range s.vmState { + assert.False(t, st.nextAttemptAt.Before(now)) + assert.False(t, st.nextAttemptAt.After(now.Add(newVMWindow))) + if earliest.IsZero() || st.nextAttemptAt.Before(earliest) { + earliest = st.nextAttemptAt + } + if latest.IsZero() || st.nextAttemptAt.After(latest) { + latest = st.nextAttemptAt + } + } + }) + assert.Greater(t, latest.Sub(earliest), time.Duration(0), + "mass insert must not schedule every VM at exactly now") + assert.GreaterOrEqual(t, latest.Sub(earliest), newVMWindow/2) +} + +func TestVMScraper_SingleInsertUsesNewVMIndexReportSpread(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "only", 1)}} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s.concurrency = 20 + s.randFloat64 = func() float64 { return 0.5 } + + s.reconcile() + + newVMWindow := newVMIndexReportWindow(s.interval) + assert.Equal(t, clock.Now().Add(newVMWindow/2), cachedNextAttemptAt(t, s, "ns/only"), + "every first schedule uses the new-VM index report window, including a lone insert with free slots") +} + +func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns", "a", 1), + makeVM("ns", "b", 2), + }} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s.concurrency = 20 + s.randFloat64 = sequencedRand([]float64{0.25, 0.75}) + + s.reconcile() + + newVMWindow := newVMIndexReportWindow(s.interval) + assert.Equal(t, clock.Now().Add(newVMWindow/4), cachedNextAttemptAt(t, s, "ns/a")) + assert.Equal(t, clock.Now().Add(3*newVMWindow/4), cachedNextAttemptAt(t, s, "ns/b")) +} + +func TestVMScraper_RetryPathDoesNotUseSteadyBand(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}} + client := &mockProtocolClient{errQueue: []error{vsockclient.ErrNotReady}} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) + // Insert due now; a later unit of 1 would be full steadyWidth if cadence used RNG. + s.randFloat64 = sequencedRand([]float64{0, 1}) + + start := clock.Now() + s.pollOnce(context.Background()) + assert.Equal(t, initialBackoff, cachedNextAttemptAt(t, s, "ns1/vm-a").Sub(start), + "retryable path must use short backoff, not pollInterval+steadyWidth") + assert.Equal(t, initialBackoff, cachedBackoff(t, s, "ns1/vm-a")) +} + +func TestVMScraper_NACKPathDoesNotUseSteadyBand(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + store := &mockStore{vms: []*virtualmachine.Info{vm}} + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + }) + s.randFloat64 = sequencedRand([]float64{0, 1}) + s.pollOnce(context.Background()) + + start := clock.Now() + s.handleNACK(string(vm.ID) + ":1") + assert.Equal(t, initialBackoff, cachedNextAttemptAt(t, s, "ns1/vm-a").Sub(start)) + assert.Equal(t, initialBackoff, cachedBackoff(t, s, "ns1/vm-a")) +} + +func TestSpreadFractionEnvDefault(t *testing.T) { + assert.InDelta(t, 2.0/3, env.VirtualMachinesScraperSteadySpreadFraction.DefaultValue(), 1e-9) + assert.InDelta(t, 2.0/3, env.VirtualMachinesScraperSteadySpreadFraction.FloatSetting(), 1e-9) +} + +func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { + const numVMs = 5 + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) + } + s, clock := newTestScraper(&mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.reconcileEvery = reconcilePeriod(s.interval) + + now := clock.Now() + concurrency.WithLock(&s.mu, func() { + for _, vm := range vms { + s.vmState[vm.Key()] = &vmState{ + vmID: vm.ID, + nextAttemptAt: now, + } + } + s.lastReconcile = now + }) + + beforeCount := histogramSampleCount(t, metrics.PullStartsPerTick) + beforeSum := histogramSampleSum(t, metrics.PullStartsPerTick) + s.tick(context.Background(), false) + assert.Equal(t, beforeCount+1, histogramSampleCount(t, metrics.PullStartsPerTick)) + assert.InDelta(t, beforeSum+float64(numVMs), histogramSampleSum(t, metrics.PullStartsPerTick), 1e-9) +} + +func TestVMScraper_StartsPerTickSkipsIdleTicks(t *testing.T) { + s, clock := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + now := clock.Now() + concurrency.WithLock(&s.mu, func() { + s.lastReconcile = now + }) + + before := histogramSampleCount(t, metrics.PullStartsPerTick) + s.tick(context.Background(), false) + assert.Equal(t, before, histogramSampleCount(t, metrics.PullStartsPerTick), + "idle ticks must not observe starts-per-tick") +} + +func TestVMScraper_WarnIfSpreadSaturated(t *testing.T) { + // newTestScraper uses a 5m poll, 2/3 spread, 10s tick (capacity 20). + cases := map[string]struct { + numVMs int + tickInterval time.Duration + spreadFraction float64 + wantLogs int + }{ + "under capacity does not warn": { + numVMs: 20, + tickInterval: defaultTickInterval, + spreadFraction: 2.0 / 3, + }, + "over capacity warns": { + numVMs: 21, + tickInterval: defaultTickInterval, + spreadFraction: 2.0 / 3, + wantLogs: 1, + }, + "zero tick does not warn": { + numVMs: 100, + tickInterval: time.Duration(0), + spreadFraction: 2.0 / 3, + }, + "zero spread does not warn": { + numVMs: 100, + tickInterval: defaultTickInterval, + spreadFraction: 0, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + // Must not use t.Parallel() because it temporarily modifies the package logger. + core, logs := observer.New(zap.WarnLevel) + orig := log + log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} + t.Cleanup(func() { log = orig }) + + s, _ := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s.tickInterval = tc.tickInterval + s.spreadFraction = tc.spreadFraction + s.warnIfSpreadSaturated(tc.numVMs) + // Counting the number of Warn logs emitted to not assert on the log msg text. + assert.Equal(t, tc.wantLogs, logs.Len()) + }) + } +} + +func TestVMScraper_ForwardInterarrivalObservesAfterFirst(t *testing.T) { + s, _ := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + before := histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds) + s.observeForwardInterarrival() + assert.Equal(t, before, histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds), + "first forward does not observe a gap") + s.observeForwardInterarrival() + assert.Equal(t, before+1, histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds)) +} + +func histogramSampleCount(t *testing.T, h prometheus.Histogram) uint64 { + t.Helper() + return histogramMetric(t, h).GetSampleCount() +} + +func histogramSampleSum(t *testing.T, h prometheus.Histogram) float64 { + t.Helper() + return histogramMetric(t, h).GetSampleSum() +} + +func histogramMetric(t *testing.T, h prometheus.Histogram) *dto.Histogram { + t.Helper() + var m dto.Metric + require.NoError(t, h.(prometheus.Metric).Write(&m)) + return m.GetHistogram() +} From 49923a0d28cc6e163227d42254753573a494bd19 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:51:47 +0000 Subject: [PATCH 059/638] Update registry.access.redhat.com/ubi9/ubi:latest Docker digest to b8c53f9 (#22440) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- image/rhel/konflux.Dockerfile | 2 +- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 46c6f6846c664..c423d9a34be53 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -61,7 +61,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 434c3e6e1e22b..25bbc5cdb464d 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index 0cdd1097ca8be..aa94ddd450aa6 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index 2807f8d5c84cb..df250db70de0f 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:5426a8f45e80a07168a30ea24d84f266094b3756624a5508cc53927e6ee39e09 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer COPY --from=ubi-micro-base / /out/ From 0e32e0fbd617d81039fbc779df114a09f2b80a44 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:30:04 +0200 Subject: [PATCH 060/638] ROX-34981: Address GHCR failure from `master` (#22418) --- .github/workflows/e2e-qa-tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index bae02574b6ff8..71b052df595d8 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -259,6 +259,8 @@ jobs: REDHAT_PASSWORD: ${{ secrets.REDHAT_PASSWORD }} GOOGLE_CREDENTIALS_GCR_SCANNER_V2: ${{ secrets.GOOGLE_CREDENTIALS_GCR_SCANNER_V2 }} GOOGLE_CREDENTIALS_GCR_NO_ACCESS_KEY_V2: ${{ secrets.GOOGLE_CREDENTIALS_GCR_NO_ACCESS_KEY_V2 }} + GHCR_REGISTRY_USER: ${{ secrets.GHCR_REGISTRY_USER }} + GHCR_REGISTRY_PASSWORD: ${{ secrets.GHCR_REGISTRY_PASSWORD }} AZURE_REGISTRY_PASSWORD: ${{ secrets.AZURE_REGISTRY_PASSWORD }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From 5e5665ec71e5c53286cf12d8a03adb638ee39af2 Mon Sep 17 00:00:00 2001 From: David Vail Date: Tue, 25 Aug 2026 08:56:21 -0400 Subject: [PATCH 061/638] ROX-36337: Removes feature flag and dead code for sensor version skew (#22377) --- pkg/features/list.go | 3 - .../cypress/fixtures/clusters/health.json | 18 +- .../integration/clusters/Clusters.helpers.js | 9 - .../clusters/Clusters.selectors.js | 1 - .../clustersSensorCompatibility.test.js | 9 - .../integration/systemHealth/clusters.test.js | 70 +-- ...lusterLabelsConfigurationStatusSummary.tsx | 6 +- .../Clusters/ClusterSummaryGrid.tsx | 32 +- .../src/Containers/Clusters/ClustersTable.tsx | 76 +--- .../Clusters/ClustersTablePanel.tsx | 119 +---- .../Clusters/Components/AutoUpgradeToggle.tsx | 68 --- .../Components/SensorCompatibilitySummary.tsx | 54 --- .../Clusters/Components/SensorUpgrade.tsx | 34 -- .../Components/SensorUpgradePanel.tsx | 107 ----- .../Clusters/cluster.helpers.test.js | 419 ------------------ .../Containers/Clusters/cluster.helpers.tsx | 265 ----------- .../Containers/Clusters/searchFilterConfig.ts | 1 - .../ClustersHealth/ClustersHealth.utils.ts | 36 +- .../ClustersHealth/ClustersHealthCards.tsx | 23 +- .../ClustersHealth/SensorUpgradeCard.tsx | 90 ---- .../platform/src/services/ClustersService.ts | 45 -- ui/apps/platform/src/types/featureFlag.ts | 1 - 22 files changed, 85 insertions(+), 1401 deletions(-) delete mode 100644 ui/apps/platform/src/Containers/Clusters/Components/AutoUpgradeToggle.tsx delete mode 100644 ui/apps/platform/src/Containers/Clusters/Components/SensorCompatibilitySummary.tsx delete mode 100644 ui/apps/platform/src/Containers/Clusters/Components/SensorUpgrade.tsx delete mode 100644 ui/apps/platform/src/Containers/Clusters/Components/SensorUpgradePanel.tsx delete mode 100644 ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/SensorUpgradeCard.tsx diff --git a/pkg/features/list.go b/pkg/features/list.go index d3cf172eb4f14..30473d2703d7d 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -146,9 +146,6 @@ var ( // UISecretsPageMigration enables the secrets list page under the Risk section UISecretsPageMigration = registerFeature("Display secrets list page under Risk section", "ROX_UI_SECRETS_PAGE_MIGRATION") - // SensorCompatibilityStatus enables sensor compatibility status UI in place of sensor upgrade status - SensorCompatibilityStatus = registerFeature("Display sensor compatibility status instead of sensor upgrade status", "ROX_SENSOR_COMPATIBILITY_STATUS") - // LegacyScanner enables the legacy scanner (Scanner V2) integration. LegacyScanner = registerFeature("Enable legacy scanner (Scanner V2) integration", "ROX_LEGACY_SCANNER", enabled) ) diff --git a/ui/apps/platform/cypress/fixtures/clusters/health.json b/ui/apps/platform/cypress/fixtures/clusters/health.json index 6d6538a318fd2..d35b1d8953e7f 100644 --- a/ui/apps/platform/cypress/fixtures/clusters/health.json +++ b/ui/apps/platform/cypress/fixtures/clusters/health.json @@ -150,7 +150,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2020-09-07T13:00:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_INCOMPATIBLE_BEHIND" }, "dynamicConfig": { "admissionControllerConfig": { @@ -219,7 +220,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2020-09-29T13:01:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_MATCHED" }, "dynamicConfig": { "admissionControllerConfig": { @@ -358,7 +360,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2020-09-30T13:01:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_COMPATIBLE_BEHIND" }, "dynamicConfig": { "admissionControllerConfig": { @@ -505,7 +508,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2020-10-31T13:01:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_MATCHED" }, "dynamicConfig": { "admissionControllerConfig": { @@ -573,7 +577,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2021-08-26T13:01:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_INCOMPATIBLE_AHEAD" }, "dynamicConfig": { "admissionControllerConfig": { @@ -641,7 +646,8 @@ }, "certExpiryStatus": { "sensorCertExpiry": "2021-08-31T13:01:00Z" - } + }, + "sensorVersionCompatibility": "SENSOR_VERSION_COMPATIBILITY_MATCHED" }, "dynamicConfig": { "admissionControllerConfig": { diff --git a/ui/apps/platform/cypress/integration/clusters/Clusters.helpers.js b/ui/apps/platform/cypress/integration/clusters/Clusters.helpers.js index c754e1bf77aba..a6899db765458 100644 --- a/ui/apps/platform/cypress/integration/clusters/Clusters.helpers.js +++ b/ui/apps/platform/cypress/integration/clusters/Clusters.helpers.js @@ -6,7 +6,6 @@ import { } from '../../helpers/request'; import { visit } from '../../helpers/visit'; -export const sensorUpgradesConfigAlias = 'sensorupgrades/config'; export const clustersAlias = 'clusters'; export const clusterDefaultsAlias = 'cluster-defaults'; export const delegatedRegistryConfigAlias = 'delegatedregistryconfig'; @@ -20,15 +19,7 @@ const routeMatcherMapForClusterDefaults = { }, }; -// With conditional rendering of side panel, -// commented requests only when it opens. const routeMatcherMapForClusters = { - /* - [sensorUpgradesConfigAlias]: { - method: 'GET', - url: '/v1/sensorupgrades/config', - }, - */ [clustersAlias]: { method: 'GET', url: 'v1/clusters', diff --git a/ui/apps/platform/cypress/integration/clusters/Clusters.selectors.js b/ui/apps/platform/cypress/integration/clusters/Clusters.selectors.js index dea869193505f..3a8b4820742fd 100644 --- a/ui/apps/platform/cypress/integration/clusters/Clusters.selectors.js +++ b/ui/apps/platform/cypress/integration/clusters/Clusters.selectors.js @@ -26,7 +26,6 @@ export const selectors = { totalRegisteredNodes: '[data-testid="totalRegisteredNodes"]', }), collectorInfoComplete: '[data-testid="collectorInfoComplete"]', - sensorUpgrade: '[data-testid="sensorUpgrade"]', sensorVersion: '[data-testid="sensorVersion"]', centralVersion: '[data-testid="centralVersion"]', credentialExpiration: '[data-testid="credentialExpiration"]', diff --git a/ui/apps/platform/cypress/integration/clusters/clustersSensorCompatibility.test.js b/ui/apps/platform/cypress/integration/clusters/clustersSensorCompatibility.test.js index 9a8786a33bfdd..0c9618d77f571 100644 --- a/ui/apps/platform/cypress/integration/clusters/clustersSensorCompatibility.test.js +++ b/ui/apps/platform/cypress/integration/clusters/clustersSensorCompatibility.test.js @@ -1,6 +1,4 @@ import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; -import { interceptAndOverrideFeatureFlags } from '../../helpers/request'; import { clustersAlias, visitClusters } from './Clusters.helpers'; @@ -103,14 +101,7 @@ const clustersResponse = { describe('Clusters Sensor Compatibility Status', () => { withAuth(); - before(function () { - if (!hasFeatureFlag('ROX_SENSOR_COMPATIBILITY_STATUS')) { - this.skip(); - } - }); - beforeEach(() => { - interceptAndOverrideFeatureFlags({ ROX_SENSOR_COMPATIBILITY_STATUS: true }); cy.intercept('GET', 'v1/metadata', { body: metadata }).as('metadata'); }); diff --git a/ui/apps/platform/cypress/integration/systemHealth/clusters.test.js b/ui/apps/platform/cypress/integration/systemHealth/clusters.test.js index 7c93e6fcb0783..a81e81f3d1713 100644 --- a/ui/apps/platform/cypress/integration/systemHealth/clusters.test.js +++ b/ui/apps/platform/cypress/integration/systemHealth/clusters.test.js @@ -29,11 +29,13 @@ describe('System Health Clusters without fixture', () => { }); }); - it('should go to Clusters via click link in Sensor upgrade card', () => { + it('should go to Clusters via click link in Sensor compatibility status card', () => { visitSystemHealth(); interactAndVisitClusters(() => { - cy.get(getCardHeaderDescendantSelector('Sensor upgrade', clustersLink)).click(); + cy.get( + getCardHeaderDescendantSelector('Sensor compatibility status', clustersLink) + ).click(); }); }); @@ -148,31 +150,32 @@ describe('System Health Clusters with fixture', () => { cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 7)); }); - it('should have phrase in Sensor upgrade card header', () => { + it('should have phrase in Sensor compatibility status card header', () => { setClock(currentDatetime); // call before visit visitSystemHealth({ clusters: { fixture: clustersFixturePath }, }); - const cardTitle = 'Sensor upgrade'; + const cardTitle = 'Sensor compatibility status'; - cy.get(getCardHeaderDescendantSelector(cardTitle, 'div:contains("2 degraded")')); + cy.get( + getCardHeaderDescendantSelector(cardTitle, 'div:contains("2 incompatible sensors")') + ); }); - it('should have counts in Sensor upgrade card', () => { + it('should have counts in Sensor compatibility status card', () => { setClock(currentDatetime); // call before visit visitSystemHealth({ clusters: { fixture: clustersFixturePath }, }); - const cardTitle = 'Sensor upgrade'; + const cardTitle = 'Sensor compatibility status'; const nthRow = 1; // Clusters - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Up to date', 4)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Failed', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Out of date', 2)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unavailable', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Uninitialized', 1)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Matched', 3)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Incompatible', 2)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Compatible', 1)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unknown', 1)); cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 7)); }); @@ -279,14 +282,21 @@ describe('System Health Clusters subset 3', () => { cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 3)); }); - it('should have not have counts in Sensor upgrade card', () => { + it('should have counts in Sensor compatibility status card', () => { setClock(currentDatetime); // call before visit visitSystemHealthWithClustersFixtureFilteredByNames(clustersFixturePath, clusterNames); - const cardTitle = 'Sensor upgrade'; + const cardTitle = 'Sensor compatibility status'; + const nthRow = 1; // Clusters - cy.get(getCardHeaderDescendantSelector(cardTitle, 'div:contains("3 healthy")')); - cy.get(getCardBodyDescendantSelector(cardTitle, 'table')).should('not.exist'); + cy.get( + getCardHeaderDescendantSelector(cardTitle, 'div:contains("No incompatible sensors")') + ); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Matched', 2)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Incompatible', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Compatible', 1)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unknown', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 3)); }); it('should have phrase in Credential expiration card header', () => { @@ -377,18 +387,17 @@ describe('System Health Clusters subset 1 Uninitialized', () => { cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 1)); }); - it('should have counts in Sensor upgrade card', () => { + it('should have counts in Sensor compatibility status card', () => { setClock(currentDatetime); // call before visit visitSystemHealthWithClustersFixtureFilteredByNames(clustersFixturePath, clusterNames); - const cardTitle = 'Sensor upgrade'; + const cardTitle = 'Sensor compatibility status'; const nthRow = 1; // Clusters - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Up to date', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Failed', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Out of date', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unavailable', 0)); - cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Uninitialized', 1)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Matched', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Incompatible', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Compatible', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unknown', 1)); cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 1)); }); @@ -423,14 +432,21 @@ describe('System Health Clusters subset 1 Healthy', () => { cy.get(getCardBodyDescendantSelector(cardTitle, 'table')).should('not.exist'); }); - it('should have not have counts in Sensor upgrade card', () => { + it('should have counts in Sensor compatibility status card', () => { setClock(currentDatetime); // call before visit visitSystemHealthWithClustersFixtureFilteredByNames(clustersFixturePath, clusterNames); - const cardTitle = 'Sensor upgrade'; + const cardTitle = 'Sensor compatibility status'; + const nthRow = 1; // Clusters - cy.get(getCardHeaderDescendantSelector(cardTitle, 'div:contains("1 healthy")')); - cy.get(getCardBodyDescendantSelector(cardTitle, 'table')).should('not.exist'); + cy.get( + getCardHeaderDescendantSelector(cardTitle, 'div:contains("No incompatible sensors")') + ); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Matched', 1)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Incompatible', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Compatible', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Unknown', 0)); + cy.get(getTableCellContainsSelector(cardTitle, nthRow, 'Total', 1)); }); it('should have not have counts in Credential expiration card', () => { diff --git a/ui/apps/platform/src/Containers/Clusters/ClusterLabelsConfigurationStatusSummary.tsx b/ui/apps/platform/src/Containers/Clusters/ClusterLabelsConfigurationStatusSummary.tsx index 3426000b9f742..81b2b9b1be894 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClusterLabelsConfigurationStatusSummary.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClusterLabelsConfigurationStatusSummary.tsx @@ -3,7 +3,6 @@ import { Alert, Content, Flex, FlexItem, Title } from '@patternfly/react-core'; import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import ExternalLink from 'Components/PatternFly/IconText/ExternalLink'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import useMetadata from 'hooks/useMetadata'; import type { Cluster, ClusterManagerType, CompleteClusterConfig } from 'types/cluster.proto'; import type { DecommissionedClusterRetentionInfo } from 'types/clusterService.proto'; @@ -41,8 +40,6 @@ function ClusterLabelsConfigurationStatusSummary({ handleChange, handleChangeAdmissionControllerEnforcementBehavior, }: ClusterLabelsConfigurationStatusSummaryProps): ReactElement { - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isSensorCompatStatusEnabled = isFeatureFlagEnabled('ROX_SENSOR_COMPATIBILITY_STATUS'); const metadata = useMetadata(); const compatibleVersions = metadata?.compatibleSensorVersions ?? []; @@ -165,14 +162,13 @@ function ClusterLabelsConfigurationStatusSummary({ > )} - {selectedCluster.id && selectedCluster.status && isSensorCompatStatusEnabled && ( + {selectedCluster.id && selectedCluster.status && ( Compatibility status {clusterInfo.status && ( @@ -35,25 +24,6 @@ export function ClusterSummaryGrid({ )} - {clusterInfo.status && !isSensorCompatStatusEnabled && ( - - {isSensorCompatStatusEnabled ? ( - - - - ) : ( - - )} - - )} {clusterInfo.status && ( diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx index 1498ace9eae49..21bfd8eb38888 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx @@ -13,7 +13,6 @@ import { } from '@patternfly/react-table'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import useMetadata from 'hooks/useMetadata'; import type { Cluster } from 'types/cluster.proto'; import type { ClusterIdToRetentionInfo } from 'types/clusterService.proto'; @@ -28,8 +27,6 @@ import ClusterStatus from './Components/ClusterStatus'; import CredentialExpiration from './Components/CredentialExpiration'; import SensorCompatibility from './Components/SensorCompatibility'; import SensorCompatibilityPanel from './Components/SensorCompatibilityPanel'; -import SensorUpgrade from './Components/SensorUpgrade'; -import SensorUpgradePanel from './Components/SensorUpgradePanel'; export type ClustersTableProps = { centralVersion: string; @@ -40,7 +37,6 @@ export type ClustersTableProps = { onDeleteCluster: (cluster: Cluster) => (event: MouseEvent) => void; toggleAllClusters: () => void; toggleCluster: (clusterId) => void; - upgradeSingleCluster: (clusterId: string) => void; }; export const EXPANDABLE_COLUMN = { @@ -60,16 +56,11 @@ function ClustersTable({ onDeleteCluster, toggleAllClusters, toggleCluster, - upgradeSingleCluster, }: ClustersTableProps): ReactElement { const [expanded, setExpanded] = useState({}); - const { isFeatureFlagEnabled } = useFeatureFlags(); const metadata = useMetadata(); const compatibleVersions = metadata?.compatibleSensorVersions ?? []; - const isSensorCompatStatusEnabled = isFeatureFlagEnabled('ROX_SENSOR_COMPATIBILITY_STATUS'); - const sensorColumnLabel = isSensorCompatStatusEnabled - ? 'Sensor compatibility status' - : 'Sensor upgrade status'; + const sensorColumnLabel = 'Sensor compatibility status'; function toggle(clusterId: string, col: ExpandableColumnId) { setExpanded((prev) => ({ @@ -177,7 +168,7 @@ function ClustersTable({ - {isSensorCompatStatusEnabled ? ( - - ) : ( - - )} + {/* TODO: needs update for upgrade */} @@ -260,38 +243,17 @@ function ClustersTable({ - {isSensorCompatStatusEnabled ? ( - - ) : ( - - )} + diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx index 60d9c982ac820..fff2044ba3faa 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom-v5-compat'; import { Button, @@ -15,7 +15,6 @@ import { } from '@patternfly/react-core'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; -import CloseButton from 'Components/CloseButton'; import CompoundSearchFilter from 'Components/CompoundSearchFilter/components/CompoundSearchFilter'; import CompoundSearchFilterLabels from 'Components/CompoundSearchFilter/components/CompoundSearchFilterLabels'; import { @@ -34,16 +33,10 @@ import useInterval from 'hooks/useInterval'; import useMetadata from 'hooks/useMetadata'; import usePermissions from 'hooks/usePermissions'; import useURLSearch from 'hooks/useURLSearch'; -import { - deleteClusters, - fetchClustersWithRetentionInfo, - upgradeCluster, - upgradeClusters, -} from 'services/ClustersService'; +import { deleteClusters, fetchClustersWithRetentionInfo } from 'services/ClustersService'; import type { Cluster } from 'types/cluster.proto'; import type { ClusterIdToRetentionInfo } from 'types/clusterService.proto'; import { getTableUIState } from 'utils/getTableUIState'; -import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; import { applyRegexSearchModifiers, convertToRestSearch, @@ -60,9 +53,8 @@ import { } from 'routePaths'; import ClustersTable from './ClustersTable'; -import AutoUpgradeToggle from './Components/AutoUpgradeToggle'; import SecureClusterModal from './ClusterRegistrationSecrets/SecureClusterModal'; -import { clusterTablePollingInterval, getUpgradeableClusters } from './cluster.helpers'; +import { clusterTablePollingInterval } from './cluster.helpers'; import NoClustersPage from './NoClustersPage'; import { searchFilterConfig } from './searchFilterConfig'; @@ -77,7 +69,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { const { hasReadAccess, hasReadWriteAccess } = usePermissions(); const hasReadAccessForAdministration = hasReadAccess('Administration'); - const hasWriteAccessForAdministration = hasReadWriteAccess('Administration'); const hasWriteAccessForCluster = hasReadWriteAccess('Cluster'); const { currentUser } = useAuthStatus(); @@ -85,10 +76,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { const [isModalOpen, setIsModalOpen] = useState(false); - const isSensorCompatibilityStatusEnabled = isFeatureFlagEnabled( - 'ROX_SENSOR_COMPATIBILITY_STATUS' - ); - function onFocusInstallMenu() { const element = document.getElementById('toggle-descriptions'); if (element !== null) { @@ -115,7 +102,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { ); const [checkedClusterIds, setCheckedClusterIds] = useState([]); - const [upgradableClusters, setUpgradableClusters] = useState([]); const [showDialog, setShowDialog] = useState(false); const [errorForClustersWithRetentionInfo, setErrorForClustersWithRetentionInfo] = useState< Error | undefined @@ -132,41 +118,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { const [clusterIdToRetentionInfo, setClusterIdToRetentionInfo] = useState({}); - type NotificationAction = { - type: 'ADD_NOTIFICATION' | 'REMOVE_NOTIFICATION'; - payload: string; - }; - - function notificationsReducer(state: string[], action: NotificationAction) { - switch (action.type) { - case 'ADD_NOTIFICATION': { - return [...state, action.payload]; - } - case 'REMOVE_NOTIFICATION': { - return state.filter((note) => note !== action.payload); - } - default: { - return state; - } - } - } - const [notifications, dispatch] = useReducer(notificationsReducer, []); - - const messages = notifications.map((note) => ( -
- {note} - { - dispatch({ type: 'REMOVE_NOTIFICATION', payload: note }); - }} - className="border-base-400 border-l" - /> -
- )); - const fetchClustersList = useCallback( (showLoadingSpinner: boolean) => { if (showLoadingSpinner) { @@ -209,31 +160,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { return ; } - function upgradeSingleCluster(id) { - upgradeCluster(id) - .then(() => { - fetchClustersList(true); - }) - .catch((error) => { - const serverError = getAxiosErrorMessage(error); - const givenCluster = currentClusters.find((cluster) => cluster.id === id); - const clusterName = givenCluster ? givenCluster.name : '-'; - const payload = `Failed to trigger upgrade for cluster ${clusterName}. Error: ${serverError}`; - - dispatch({ type: 'ADD_NOTIFICATION', payload }); - }); - } - - function upgradeSelectedClusters() { - // Although return works around typescript-eslint/no-floating-promises error, - // catch block would be better. - return upgradeClusters(checkedClusterIds).then(() => { - setCheckedClusterIds([]); - - fetchClustersList(true); - }); - } - function deleteSelectedClusters() { setShowDialog(true); } @@ -260,16 +186,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { }); } - function calculateUpgradeableClusters(selection) { - const currentlySelectedClusters = currentClusters.filter((cluster) => - selection.includes(cluster.id) - ); - - const upgradeableList = getUpgradeableClusters(currentlySelectedClusters); - - setUpgradableClusters(upgradeableList); - } - const onDeleteHandler = (cluster: Cluster) => (e) => { e.stopPropagation(); setCheckedClusterIds([cluster.id]); @@ -282,7 +198,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { : [...checkedClusterIds, id]; setCheckedClusterIds(selection); - calculateUpgradeableClusters(selection); } function toggleAllClusters() { @@ -293,7 +208,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { ? [] : currentClusters.map(({ id }) => id); setCheckedClusterIds(selection); - calculateUpgradeableClusters(selection); } // After there is a response, if there are clusters or search filter. @@ -405,27 +319,6 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { } /> - {!isSensorCompatibilityStatusEnabled && - hasWriteAccessForAdministration && ( - - - - )} - {!isSensorCompatibilityStatusEnabled && - hasWriteAccessForAdministration && ( - - - - )} {hasWriteAccessForCluster && ( - ) : ( - 'None' - )} - - - )} - - Sensor version - {sensorVersion} - - - Central version - - {centralVersion} - - - - - - - ); -} - -export default SensorUpgradePanel; diff --git a/ui/apps/platform/src/Containers/Clusters/cluster.helpers.test.js b/ui/apps/platform/src/Containers/Clusters/cluster.helpers.test.js index ddcfd9b3d60d0..3ae22d3035da6 100644 --- a/ui/apps/platform/src/Containers/Clusters/cluster.helpers.test.js +++ b/ui/apps/platform/src/Containers/Clusters/cluster.helpers.test.js @@ -1,10 +1,8 @@ import { - findUpgradeState, formatBuildDate, formatCloudProvider, formatKubernetesVersion, getCredentialExpirationStatus, - getUpgradeableClusters, } from './cluster.helpers'; describe('cluster helpers', () => { @@ -200,423 +198,6 @@ describe('cluster helpers', () => { }); }); - describe('getUpgradeableClusters', () => { - it('should return 0 when no clusters are unpgradeable', () => { - const clusters = [ - { - id: 'f7ae6b5f-6329-4ed9-a439-83181991a526', - name: 'K8S', - status: { - upgradeStatus: { - upgradability: 'UP_TO_DATE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }, - }, - }, - { - id: '26eac883-1f09-4123-971b-8b00ee63f5fd', - name: 'remote1', - status: { - upgradeStatus: { - upgradability: 'UP_TO_DATE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }, - }, - }, - ]; - - const upgradeableClusters = getUpgradeableClusters(clusters); - - expect(upgradeableClusters.length).toEqual(0); - }); - - it('should the number of ugradeable clusters', () => { - const clusters = [ - { - id: 'f7ae6b5f-6329-4ed9-a439-83181991a526', - name: 'K8S', - status: { - upgradeStatus: { - upgradability: 'UP_TO_DATE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }, - }, - }, - { - id: '26eac883-1f09-4123-971b-8b00ee63f5fd', - name: 'remote1', - status: { - upgradeStatus: { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }, - }, - }, - ]; - - const upgradeableClusters = getUpgradeableClusters(clusters); - - expect(upgradeableClusters.length).toEqual(1); - }); - }); - - describe('findUpgradeState', () => { - it('should return null if upgradeStatus is null', () => { - const testUpgradeStatus = null; - - const received = findUpgradeState(testUpgradeStatus); - - expect(received).toEqual(null); - }); - - it('should return "Up to date with Central" if upgradeStatus -> upgradability is UP_TO_DATE', () => { - const testUpgradeStatus = { - upgradability: 'UP_TO_DATE', - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Up to date with Central', type: 'current' }; - expect(received).toEqual(expected); - }); - - it(`should return "Sensor is not running the same version as Central" if upgradeStatus -> upgradability is MANUAL_UPGRADE_REQUIRED`, () => { - const testUpgradeStatus = { - upgradability: 'MANUAL_UPGRADE_REQUIRED', - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Sensor is not running the same version as Central', - type: 'intervention', - }; - expect(received).toEqual(expected); - }); - - it('should return "Downgrade possible" if there is no mostRecentProcess', () => { - const testUpgradeStatus = { - upgradability: 'SENSOR_VERSION_HIGHER', - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Downgrade possible', - type: 'download', - actionText: 'Downgrade sensor', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade available" if there is no mostRecentProcess', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade available', - type: 'download', - actionText: 'Upgrade sensor', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade available" if upgradeStatus -> upgradability is AUTO_UPGRADE_POSSIBLE but mostRecentProgress is not active and is COMPLETE', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade available', - type: 'download', - actionText: 'Upgrade sensor', - }; - expect(received).toEqual(expected); - }); - - it('should print the error (and "Retry Upgrade") if upgradeStatus -> upgradability is AUTO_UPGRADE_POSSIBLE but mostRecentProgress is not active and failed', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: false, - progress: { - upgradeState: 'PRE_FLIGHT_CHECKS_FAILED', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Pre-flight checks failed', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade initializing" if upgradeStatus -> upgradability is AUTO_UPGRADE_POSSIBLE and upgradeState is UPGRADE_INITIALIZING', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_INITIALIZING', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade initializing', - type: 'progress', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrader launching" if upgradeState is UPGRADER_LAUNCHING', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADER_LAUNCHING', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Upgrader launching', type: 'progress' }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrader launched" if upgradeState is UPGRADER_LAUNCHED', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADER_LAUNCHED', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Upgrader launched', type: 'progress' }; - expect(received).toEqual(expected); - }); - - it('should return "Pre-flight checks complete" if upgradeState is PRE_FLIGHT_CHECKS_COMPLETE', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'PRE_FLIGHT_CHECKS_COMPLETE', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Pre-flight checks complete', type: 'progress' }; - expect(received).toEqual(expected); - }); - - it('should return "Pre-flight checks failed." if upgradeState is PRE_FLIGHT_CHECKS_FAILED', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'PRE_FLIGHT_CHECKS_FAILED', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Pre-flight checks failed', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade Operations Done" if upgradeState is UPGRADE_OPERATIONS_DONE', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_OPERATIONS_DONE', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Upgrade operations done', type: 'progress' }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade Operations Complete" if upgradeState is UPGRADE_COMPLETE', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_COMPLETE', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { displayValue: 'Upgrade complete', type: 'current' }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade failed. Rolled back." if upgradeState is UPGRADE_ERROR_ROLLED_BACK', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_ERROR_ROLLED_BACK', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade failed. Rolled back.', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade failed. Rollback failed." if upgradeState is UPGRADE_ERROR_ROLLBACK_FAILED', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_ERROR_ROLLBACK_FAILED', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade failed. Rollback failed.', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade timed out." if upgradeState is UPGRADE_TIMED_OUT', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_TIMED_OUT', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade timed out.', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Upgrade error unknown." if upgradeState is UPGRADE_ERROR_UNKNOWN', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'UPGRADE_ERROR_UNKNOWN', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Upgrade error unknown', - type: 'failure', - actionText: 'Retry upgrade', - }; - expect(received).toEqual(expected); - }); - - it('should return "Unknown upgrade state. Contact Support." if upgradeState does not match known progress', () => { - const testUpgradeStatus = { - upgradability: 'AUTO_UPGRADE_POSSIBLE', - mostRecentProcess: { - active: true, - progress: { - upgradeState: 'SNAFU', - }, - }, - }; - - const received = findUpgradeState(testUpgradeStatus); - - const expected = { - displayValue: 'Unknown upgrade state. Contact Support.', - type: 'intervention', - }; - expect(received).toEqual(expected); - }); - }); - describe('get credential expiration status', () => { it('should return HEALTHY when more than a month before expiration', () => { const status = getCredentialExpirationStatus( diff --git a/ui/apps/platform/src/Containers/Clusters/cluster.helpers.tsx b/ui/apps/platform/src/Containers/Clusters/cluster.helpers.tsx index 351aa82177115..6fa25a579b65e 100644 --- a/ui/apps/platform/src/Containers/Clusters/cluster.helpers.tsx +++ b/ui/apps/platform/src/Containers/Clusters/cluster.helpers.tsx @@ -1,12 +1,9 @@ import { differenceInDays, differenceInMinutes } from 'date-fns'; -import get from 'lodash/get'; -import { DownloadCloud } from 'react-feather'; import { BanIcon, CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, - InProgressIcon, InfoCircleIcon, MinusCircleIcon, ResourcesEmptyIcon, @@ -14,7 +11,6 @@ import { } from '@patternfly/react-icons'; import type { - Cluster, ClusterHealthStatusLabel, ClusterProviderMetadata, SensorVersionCompatibility, @@ -187,20 +183,6 @@ export const delayedScannerStatusStyle = { fgColor: '', }; -export const sensorUpgradeStyles = { - current: styleHealthy, - progress: { - Icon: InProgressIcon, - fgColor: 'pf-v6-u-icon-color-primary', - }, - download: { - Icon: DownloadCloud, - fgColor: 'pf-v6-u-text-color-link', - }, - intervention: styleDegraded, - failure: styleUnhealthy, -}; - export const sensorCompatibilityMap = { SENSOR_VERSION_COMPATIBILITY_MATCHED: { displayValue: 'Matched', @@ -243,97 +225,6 @@ export const sensorCompatibilityMap = { export type SensorCompatibilityInfo = (typeof sensorCompatibilityMap)[keyof typeof sensorCompatibilityMap]; -type UpgradeState = { - displayValue: string; - type: string; - actionText?: string; -}; -type UpgradeStates = Record; - -// @TODO: add optional button text and func -const upgradeStates: UpgradeStates = { - UP_TO_DATE: { - displayValue: 'Up to date with Central', - type: 'current', - }, - MANUAL_UPGRADE_REQUIRED: { - displayValue: 'Sensor is not running the same version as Central', - type: 'intervention', - }, - UPGRADE_AVAILABLE: { - displayValue: 'Upgrade available', - type: 'download', - actionText: 'Upgrade sensor', - }, - DOWNGRADE_POSSIBLE: { - displayValue: 'Downgrade possible', - type: 'download', - actionText: 'Downgrade sensor', - }, - UPGRADE_INITIALIZING: { - displayValue: 'Upgrade initializing', - type: 'progress', - }, - UPGRADER_LAUNCHING: { - displayValue: 'Upgrader launching', - type: 'progress', - }, - UPGRADER_LAUNCHED: { - displayValue: 'Upgrader launched', - type: 'progress', - }, - PRE_FLIGHT_CHECKS_COMPLETE: { - displayValue: 'Pre-flight checks complete', - type: 'progress', - }, - UPGRADE_OPERATIONS_DONE: { - displayValue: 'Upgrade operations done', - type: 'progress', - }, - UPGRADE_COMPLETE: { - displayValue: 'Upgrade complete', - type: 'current', - }, - UPGRADE_INITIALIZATION_ERROR: { - displayValue: 'Upgrade initialization error', - type: 'failure', - actionText: 'Retry upgrade', - }, - PRE_FLIGHT_CHECKS_FAILED: { - displayValue: 'Pre-flight checks failed', - type: 'failure', - actionText: 'Retry upgrade', - }, - UPGRADE_ERROR_ROLLING_BACK: { - displayValue: 'Upgrade failed. Rolling back…', - type: 'failure', - }, - UPGRADE_ERROR_ROLLED_BACK: { - displayValue: 'Upgrade failed. Rolled back.', - type: 'failure', - actionText: 'Retry upgrade', - }, - UPGRADE_ERROR_ROLLBACK_FAILED: { - displayValue: 'Upgrade failed. Rollback failed.', - type: 'failure', - actionText: 'Retry upgrade', - }, - UPGRADE_TIMED_OUT: { - displayValue: 'Upgrade timed out.', - type: 'failure', - actionText: 'Retry upgrade', - }, - UPGRADE_ERROR_UNKNOWN: { - displayValue: 'Upgrade error unknown', - type: 'failure', - actionText: 'Retry upgrade', - }, - unknown: { - displayValue: 'Unknown upgrade state. Contact Support.', - type: 'intervention', - }, -}; - export function formatKubernetesVersion(orchestratorMetadata: { version: string }) { return orchestratorMetadata?.version || 'Not available'; } @@ -430,160 +321,6 @@ export const isCertificateExpiringSoon = ( export const isDelayedSensorHealthStatus = (sensorHealthStatus) => sensorHealthStatus === 'UNHEALTHY' || sensorHealthStatus === 'DEGRADED'; -export function formatUpgradeMessage(upgradeStateObject, detail) { - if (upgradeStateObject.type === 'current') { - return null; - } - const message = { - message: - upgradeStateObject.displayValue || upgradeStateObject.actionText || 'Unknown status', - type: '', - detail, - }; - switch (upgradeStateObject.type) { - case 'failure': { - message.type = 'error'; - break; - } - case 'intervention': { - message.type = 'warn'; - break; - } - default: { - message.type = 'info'; - } - } - return message; -} - -// This function looks at a cluster upgrade status, and figures out whether the most recent -// upgrade has any information that is of relevance to the user. -function hasRelevantInformationFromMostRecentUpgrade(upgradeStatus) { - // No information from the most recent upgrade -- probably means no upgrade has been done before. - // Not interesting. - if (get(upgradeStatus, 'mostRecentProcess', null) === null) { - return false; - } - const isActive = get(upgradeStatus, 'mostRecentProcess.active', false); - // The upgrade is currently active. Definitely show the user information about it. - if (isActive) { - return true; - } - - // The upgrade is not active. This means that this is the most recently completed upgrade. - // If it was COMPLETE, the information is not interesting to the user. - // Else, we show the user the information. - return ( - get(upgradeStatus, 'mostRecentProcess.progress.upgradeState', undefined) !== - 'UPGRADE_COMPLETE' - ); -} - -export function getUpgradeStatusDetail(upgradeStatus: string): string { - return get(upgradeStatus, 'mostRecentProcess.progress.upgradeStatusDetail', '') as string; -} - -export type UpgradeStatus = { - mostRecentProcess: { - type: string; - progress?: { - upgradeState: string; - }; - initiatedAt?: string; - active?: boolean; - upgradability?: string; - }; -}; - -/** - * If the most recent upgrade was a cert rotation, return the initiation time. - * Else, return null. - */ -export function initiationOfCertRotationIfApplicable(upgradeStatus: UpgradeStatus) { - const mostRecentProcess = upgradeStatus?.mostRecentProcess; - if (mostRecentProcess?.type !== 'CERT_ROTATION') { - return null; - } - if (mostRecentProcess?.progress?.upgradeState !== 'UPGRADE_COMPLETE') { - return null; - } - return mostRecentProcess.initiatedAt; -} - -export function findUpgradeState( - upgradeStatus: UpgradeStatus | null | undefined -): UpgradeState | null { - const upgradability = get(upgradeStatus, 'upgradability', null); - if (!upgradability || upgradability === 'UNSET') { - return null; - } - - switch (upgradability) { - case 'UP_TO_DATE': { - if (!upgradeStatus?.mostRecentProcess?.active) { - return upgradeStates.UP_TO_DATE; - } - - // Display active progress while using automatic upgrade to re-issue certificates. - const upgradeState: string = get( - upgradeStatus, - 'mostRecentProcess.progress.upgradeState', - 'unknown' - ); - - return upgradeStates[upgradeState] || upgradeStates.unknown; - } - case 'MANUAL_UPGRADE_REQUIRED': { - return upgradeStates[upgradability]; - } - // Auto upgrades are possible even in the case of SENSOR_VERSION_HIGHER (it's not technically an upgrade, - // and not really something we ever expect, but eh.) If the backend detects this to be the case, it will not - // trigger an upgrade unless asked to by the user. - case 'SENSOR_VERSION_HIGHER': { - if (!hasRelevantInformationFromMostRecentUpgrade(upgradeStatus)) { - return upgradeStates.DOWNGRADE_POSSIBLE; - } - - const upgradeState = get( - upgradeStatus, - 'mostRecentProcess.progress.upgradeState', - 'unknown' - ); - - return upgradeStates[upgradeState] || upgradeStates.unknown; - } - case 'AUTO_UPGRADE_POSSIBLE': { - if (!hasRelevantInformationFromMostRecentUpgrade(upgradeStatus)) { - return upgradeStates.UPGRADE_AVAILABLE; - } - - const upgradeState = get( - upgradeStatus, - 'mostRecentProcess.progress.upgradeState', - 'unknown' - ); - - return upgradeStates[upgradeState] || upgradeStates.unknown; - } - default: { - return upgradeStates.unknown; - } - } -} - -export function isUpToDateStateObject(upgradeStateObject) { - return upgradeStateObject?.type === 'current'; -} - -export function getUpgradeableClusters(clusters: Cluster[] = []): Cluster[] { - return clusters.filter((cluster) => { - const upgradeStatus: UpgradeStatus | null = get(cluster, 'status.upgradeStatus', null); - const upgradeStateObject = findUpgradeState(upgradeStatus); - - return upgradeStateObject?.actionText; // if property exists, you can try or retry an upgrade - }); -} - export function buildStatusMessage( healthStatus: ClusterHealthStatusLabel, lastContact: string | null | undefined, @@ -628,6 +365,4 @@ export default { clusterTablePollingInterval, clusterDetailPollingInterval, newClusterDefault, - findUpgradeState, - isUpToDateStateObject, }; diff --git a/ui/apps/platform/src/Containers/Clusters/searchFilterConfig.ts b/ui/apps/platform/src/Containers/Clusters/searchFilterConfig.ts index 8ade59f3a9b36..707c2549ee461 100644 --- a/ui/apps/platform/src/Containers/Clusters/searchFilterConfig.ts +++ b/ui/apps/platform/src/Containers/Clusters/searchFilterConfig.ts @@ -79,7 +79,6 @@ const sensorCompatibilityStatusAttribute: CompoundSearchFilterAttribute = { searchTerm: 'Sensor Version Compatibility', inputType: 'select', inputProps: { options: sensorCompatibilityStatusOptions }, - featureFlagDependency: ['ROX_SENSOR_COMPATIBILITY_STATUS'], }; const lastContactAttributes: CompoundSearchFilterAttribute = { diff --git a/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealth.utils.ts b/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealth.utils.ts index f34244482922f..9c505fc548df3 100644 --- a/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealth.utils.ts +++ b/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealth.utils.ts @@ -1,7 +1,4 @@ -import { - findUpgradeState, - getCredentialExpirationStatus, -} from 'Containers/Clusters/cluster.helpers'; +import { getCredentialExpirationStatus } from 'Containers/Clusters/cluster.helpers'; import type { CertExpiryStatus } from 'Containers/Clusters/clusterTypes'; // TODO types/cluster.proto.ts import type { Cluster } from 'types/cluster.proto'; @@ -50,37 +47,6 @@ export function getCertificateExpirationCounts( return counts; } -export function getSensorUpgradeCounts(clusters: Cluster[]): ClusterStatusCounts { - const counts = getClusterStatusCountsObject(); - - clusters.forEach((cluster) => { - if (cluster && cluster.healthStatus) { - switch (cluster.healthStatus.overallHealthStatus) { - case 'UNAVAILABLE': - case 'UNINITIALIZED': { - counts[cluster.healthStatus.overallHealthStatus] += 1; - break; - } - default: { - const { upgradeStatus } = cluster.status ?? {}; - const upgradeState = findUpgradeState(upgradeStatus); - - const key = - upgradeState?.type === 'current' - ? 'HEALTHY' - : upgradeState?.type === 'failure' - ? 'UNHEALTHY' - : 'DEGRADED'; - - counts[key] += 1; - } - } - } - }); - - return counts; -} - export function getClusterStatusCounts(clusters: Cluster[]): ClusterStatusCounts { const counts = getClusterStatusCountsObject(); diff --git a/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealthCards.tsx b/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealthCards.tsx index 874fdc2b7912f..8326d97f94035 100644 --- a/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealthCards.tsx +++ b/ui/apps/platform/src/Containers/SystemHealth/ClustersHealth/ClustersHealthCards.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import { GridItem } from '@patternfly/react-core'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import { fetchClusters } from 'services/ClustersService'; import type { Cluster } from 'types/cluster.proto'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; @@ -10,7 +9,6 @@ import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; import ClusterStatusCard from './ClusterStatusCard'; import CredentialExpirationCard from './CredentialExpirationCard'; import SensorCompatibilityCard from './SensorCompatibilityCard'; -import SensorUpgradeCard from './SensorUpgradeCard'; type ClustersHealthCardsProps = { pollingCount: number; @@ -23,9 +21,6 @@ const ClustersHealthCards = ({ pollingCount }: ClustersHealthCardsProps): ReactE const [currentDatetime, setCurrentDatetime] = useState(null); - const { isFeatureFlagEnabled } = useFeatureFlags(); - const showCompatibilityStatus = isFeatureFlagEnabled('ROX_SENSOR_COMPATIBILITY_STATUS'); - useEffect(() => { setIsFetching(true); fetchClusters() @@ -59,19 +54,11 @@ const ClustersHealthCards = ({ pollingCount }: ClustersHealthCardsProps): ReactE />
- {showCompatibilityStatus ? ( - - ) : ( - - )} + - - {errorMessageFetching ? ( - - - - ) : counts !== null && - (counts.HEALTHY === 0 || counts.UNHEALTHY !== 0 || counts.DEGRADED !== 0) ? ( - - - - - - - - - - - - - - -
Clusters
-
- ) : null} - - ); -} - -export default SensorUpgradeCard; diff --git a/ui/apps/platform/src/services/ClustersService.ts b/ui/apps/platform/src/services/ClustersService.ts index 9aa7e844f39f5..65d6d58889f85 100644 --- a/ui/apps/platform/src/services/ClustersService.ts +++ b/ui/apps/platform/src/services/ClustersService.ts @@ -15,8 +15,6 @@ const clustersUrl = '/v1/clusters'; const clusterDefaultsUrl = '/v1/cluster-defaults'; const clusterInitUrl = '/v1/cluster-init'; const upgradesUrl = '/v1/sensorupgrades'; -const autoUpgradeConfigUrl = `${upgradesUrl}/config`; -const manualUpgradeUrl = `${upgradesUrl}/cluster`; export type ClusterLabels = Record; @@ -82,42 +80,6 @@ export function fetchClusterWithRetentionInformation(id: string): Promise { - return axios.get<{ config: AutoUpgradeConfig }>(autoUpgradeConfigUrl).then((response) => { - return response?.data?.config ?? {}; - }); -} - -/** - * Saves the cluster autoupgrade config. - */ -export function saveAutoUpgradeConfig(config: AutoUpgradeConfig): Promise { - const wrappedObject = { config: { enableAutoUpgrade: config.enableAutoUpgrade } }; - return axios.post(autoUpgradeConfigUrl, wrappedObject).then((response) => response.data); -} - -/** - * Manually start a sensor upgrade given the cluster ID. - */ -export function upgradeCluster(id: string): Promise { - return axios.post(`${manualUpgradeUrl}/${id}`); -} - /** * Start a cluster cert rotation. */ @@ -125,13 +87,6 @@ export function rotateClusterCerts(id: string): Promise { return axios.post(`${upgradesUrl}/rotateclustercerts/${id}`); } -/** - * Manually start a sensor upgrade for an array of clusters. - */ -export function upgradeClusters(ids: string[] = []): Promise { - return Promise.all(ids.map((id) => upgradeCluster(id))); -} - /** * Deletes cluster given the cluster ID. Returns an empty object. */ diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index 5993d5156364c..8cc4f0cd42f32 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -16,7 +16,6 @@ export type FeatureFlagEnvVar = | 'ROX_POLICY_CRITERIA_MODAL' | 'ROX_SCANNER_V4' | 'ROX_SENSITIVE_FILE_ACTIVITY' - | 'ROX_SENSOR_COMPATIBILITY_STATUS' | 'ROX_UI_SECRETS_PAGE_MIGRATION' | 'ROX_VIRTUAL_MACHINES' | 'ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL' From b897a8d1b95fe5da6c22b1a4147e2b404030e6cc Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Tue, 25 Aug 2026 13:05:07 +0000 Subject: [PATCH 062/638] ROX-36509: lazy-init children slice in process filter levels (#22250) Signed-off-by: Tomasz Janiszewski Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 1 + pkg/env/process_filter.go | 1 + pkg/process/filter/filter.go | 70 ++++++--- pkg/process/filter/filter_bench_test.go | 181 +++++++++++++++++++----- pkg/process/filter/filter_test.go | 8 ++ 5 files changed, 209 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d6937293fb5..2c0f6627e31bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-34804: The machine access configuration for `config-controller` now validates the audience (`aud` claim) of the service account token. The expected audience is `central.stackrox.io`. When users have added their own role bindings to this machine access configuration, the audience check is not enforced by default to keep backwards compatibility. It is recommended to set the expected audience to `central.stackrox.io` after ensuring that all exchange tokens are being created with this audience claim. - ROX-34535: Fixes an issue where if ScannerV2 is disabled or unavailable on initial startup the central deployment leaks GRPC connections until the scanner becomes available. +- ROX-36509: Improved Central memory efficiency by optimizing process filter data structures in high-cardinality scenarios. The `ROX_PROCESS_FILTER_FAN_OUT_LEVELS` environment variable now accepts values up to 255; higher values are automatically clamped with a warning. ## [4.11.0] diff --git a/pkg/env/process_filter.go b/pkg/env/process_filter.go index 2629d025ec4d7..02dca0d77d4ab 100644 --- a/pkg/env/process_filter.go +++ b/pkg/env/process_filter.go @@ -25,6 +25,7 @@ var ( // An empty array "[]" means only unique processes are tracked without argument tracking. // This setting can be overridden by ROX_PROCESS_FILTER_MODE presets. // Default: [8,6,4,2] + // TODO(janisz): change maximum element Value to 255 to avoid implicit clamping in filter.go ProcessFilterFanOutLevels = RegisterIntegerArraySetting( "ROX_PROCESS_FILTER_FAN_OUT_LEVELS", []int{8, 6, 4, 2}, diff --git a/pkg/process/filter/filter.go b/pkg/process/filter/filter.go index 6b24970b5d95b..d07e3581dede1 100644 --- a/pkg/process/filter/filter.go +++ b/pkg/process/filter/filter.go @@ -1,6 +1,7 @@ package filter import ( + "fmt" "hash" "strings" "unsafe" @@ -11,6 +12,7 @@ import ( "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/stringutils" "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/utils" ) // BinaryHash represents a 64-bit hash for memory-efficient key storage. @@ -95,23 +97,41 @@ type Filter interface { DeleteByPod(pod *storage.Pod) } +type child struct { + hash BinaryHash + node *level +} + type level struct { hits int - children map[BinaryHash]*level + children []child } func newLevel() *level { - return &level{ - children: make(map[BinaryHash]*level), + return &level{} +} + +func (l *level) findChild(h BinaryHash) *level { + for _, c := range l.children { + if c.hash == h { + return c.node + } } + return nil } +// containerNode is the per-container root of the filter tree, keyed by process +// executable path. The process layer is unbounded (Sensor sets maxUniqueProcesses +// to math.MaxInt), so a map keeps lookup and insertion O(1); argument levels below +// each process are bounded by fanOut and use slices (see level). +type containerNode = map[BinaryHash]*level + type filterImpl struct { - maxExactPathMatches int // maximum number of exact path (same pod + container and same process and args) matches to tolerate - maxUniqueProcesses int // maximum number of unique process exec file paths - maxFanOut []int // maximum fan out starting at the process level + maxExactPathMatches int // maximum number of exact path (same pod + container and same process and args) matches to tolerate + maxUniqueProcesses int // maximum number of unique process exec file paths + maxFanOut []uint8 // maximum fan out starting at the process level - containersInDeployment map[string]map[string]*level + containersInDeployment map[string]map[string]containerNode rootLock sync.Mutex // Hash instance for computing BinaryHash keys @@ -138,14 +158,14 @@ func (f *filterImpl) siftNoLock(level *level, args []string, levelNum int) bool // 2. Using BinaryHash as map key is reducing memory requirements for the filter argHash := hashString(f.h, truncated) - nextLevel := level.children[argHash] + nextLevel := level.findChild(argHash) if nextLevel == nil { // If this level has already hit its max fan out then return false - if len(level.children) >= f.maxFanOut[levelNum] { + if len(level.children) >= int(f.maxFanOut[levelNum]) { return false } nextLevel = newLevel() - level.children[argHash] = nextLevel + level.children = append(level.children, child{hash: argHash, node: nextLevel}) } return f.siftNoLock(nextLevel, args[1:], levelNum+1) @@ -153,26 +173,40 @@ func (f *filterImpl) siftNoLock(level *level, args []string, levelNum int) bool // NewFilter returns an empty filter to start loading processes into func NewFilter(maxExactPathMatches, maxUniqueProcesses int, fanOut []int) Filter { + maxFanOut := make([]uint8, len(fanOut)) + for i, v := range fanOut { + if v < 1 { + // Guard the boundary: uint8(negative) wraps to a large value (e.g. -1 -> 255). + // Mirror the env layer's MinimumElementValue(1) so invalid values cannot become max fan out. + utils.Should(fmt.Errorf("fanOut[%d]=%d is below minimum, clamping to 1", i, v)) + v = 1 + } + if v > 255 { + utils.Should(fmt.Errorf("ROX_PROCESS_FILTER_FAN_OUT_LEVELS[%d]=%d exceeds uint8 max, clamping to 255", i, v)) + v = 255 + } + maxFanOut[i] = uint8(v) + } return &filterImpl{ maxExactPathMatches: maxExactPathMatches, maxUniqueProcesses: maxUniqueProcesses, - maxFanOut: fanOut, + maxFanOut: maxFanOut, - containersInDeployment: make(map[string]map[string]*level), + containersInDeployment: make(map[string]map[string]containerNode), h: xxhash.New(), } } -func (f *filterImpl) getOrAddRootLevelNoLock(indicator *storage.ProcessIndicator) *level { +func (f *filterImpl) getOrAddRootLevelNoLock(indicator *storage.ProcessIndicator) containerNode { containerMap := f.containersInDeployment[indicator.GetDeploymentId()] if containerMap == nil { - containerMap = make(map[string]*level) + containerMap = make(map[string]containerNode) f.containersInDeployment[indicator.GetDeploymentId()] = containerMap } rootLevel := containerMap[indicator.GetSignal().GetContainerId()] if rootLevel == nil { - rootLevel = newLevel() + rootLevel = make(containerNode) containerMap[indicator.GetSignal().GetContainerId()] = rootLevel } @@ -192,13 +226,13 @@ func (f *filterImpl) Add(indicator *storage.ProcessIndicator) bool { execFilePathHash := hashString(f.h, execFilePath) // Handle the process level independently as we will never reject a new process - processLevel := rootLevel.children[execFilePathHash] + processLevel := rootLevel[execFilePathHash] if processLevel == nil { - if len(rootLevel.children) >= f.maxUniqueProcesses { + if len(rootLevel) >= f.maxUniqueProcesses { return false } processLevel = newLevel() - rootLevel.children[execFilePathHash] = processLevel + rootLevel[execFilePathHash] = processLevel } return f.siftNoLock(processLevel, strings.Fields(indicator.GetSignal().GetArgs()), 0) diff --git a/pkg/process/filter/filter_bench_test.go b/pkg/process/filter/filter_bench_test.go index fd98f09ab560e..518a8bca24f81 100644 --- a/pkg/process/filter/filter_bench_test.go +++ b/pkg/process/filter/filter_bench_test.go @@ -2,51 +2,108 @@ package filter import ( "fmt" - "runtime" + "math" "testing" "github.com/stackrox/rox/generated/storage" ) -// BenchmarkAdd measures performance of adding process indicators func BenchmarkAdd(b *testing.B) { - filter := NewFilter(100, 1000, []int{100, 100, 100}) - - // Create test data - indicators := make([]*storage.ProcessIndicator, 1000) - for i := range indicators { - indicators[i] = &storage.ProcessIndicator{ - DeploymentId: fmt.Sprintf("dep%d", i%10), - ContainerName: "container", - Signal: &storage.ProcessSignal{ - ContainerId: fmt.Sprintf("id%d", i%10), - ExecFilePath: fmt.Sprintf("/usr/bin/process%d", i%100), - Args: fmt.Sprintf("arg1 arg2 arg3 iteration%d", i), - }, - } + cases := []struct { + name string + fanOut []int + maxExact int + maxUnique int + numIndicators int + numDeployments int + numProcesses int + }{ + { + name: "default_fanout", + fanOut: []int{8, 6, 4, 2}, + maxExact: 5, + maxUnique: 5000, + numIndicators: 1000, + numDeployments: 10, + numProcesses: 100, + }, + { + name: "high_fanout", + fanOut: []int{100, 100, 100}, + maxExact: 100, + maxUnique: 1000, + numIndicators: 1000, + numDeployments: 10, + numProcesses: 100, + }, } - for i := 0; b.Loop(); i++ { - filter.Add(indicators[i%len(indicators)]) + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + filter := NewFilter(tc.maxExact, tc.maxUnique, tc.fanOut) + + indicators := make([]*storage.ProcessIndicator, tc.numIndicators) + for i := range indicators { + indicators[i] = &storage.ProcessIndicator{ + DeploymentId: fmt.Sprintf("dep%d", i%tc.numDeployments), + ContainerName: "container", + Signal: &storage.ProcessSignal{ + ContainerId: fmt.Sprintf("id%d", i%tc.numDeployments), + ExecFilePath: fmt.Sprintf("/usr/bin/process%d", i%tc.numProcesses), + Args: fmt.Sprintf("arg1 arg2 arg3 iteration%d", i), + }, + } + } + + for i := 0; b.Loop(); i++ { + filter.Add(indicators[i%len(indicators)]) + } + }) } } -// BenchmarkAddMemory measures memory allocations -func BenchmarkAddMemory(b *testing.B) { - filter := NewFilter(100, 1000, []int{100, 100, 100}) - - pi := &storage.ProcessIndicator{ - DeploymentId: "deployment", - ContainerName: "container", - Signal: &storage.ProcessSignal{ - ContainerId: "containerid", - ExecFilePath: "/usr/bin/process", - Args: "arg1 arg2 arg3", - }, +func BenchmarkLookupInFullLevel(b *testing.B) { + cases := []struct { + name string + fanOut []int + numArgs int + }{ + {name: "fanout_8", fanOut: []int{8}, numArgs: 8}, + {name: "fanout_20", fanOut: []int{20}, numArgs: 20}, + {name: "fanout_100", fanOut: []int{100}, numArgs: 100}, + {name: "fanout_255", fanOut: []int{255}, numArgs: 255}, } - for b.Loop() { - filter.Add(pi) + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + filter := NewFilter(tc.numArgs+1, 5000, tc.fanOut) + + for i := range tc.numArgs { + filter.Add(&storage.ProcessIndicator{ + DeploymentId: "dep", + ContainerName: "container", + Signal: &storage.ProcessSignal{ + ContainerId: "id", + ExecFilePath: "/usr/bin/proc", + Args: fmt.Sprintf("arg%d", i), + }, + }) + } + + last := &storage.ProcessIndicator{ + DeploymentId: "dep", + ContainerName: "container", + Signal: &storage.ProcessSignal{ + ContainerId: "id", + ExecFilePath: "/usr/bin/proc", + Args: fmt.Sprintf("arg%d", tc.numArgs-1), + }, + } + + for b.Loop() { + filter.Add(last) + } + }) } } @@ -80,8 +137,64 @@ func BenchmarkBuildIndicatorFilterMemory(b *testing.B) { } } } + } +} + +// BenchmarkHighCardinalityProcesses measures a single container accumulating many +// distinct executable paths. Sensor runs with an unbounded maxUniqueProcesses, so +// the process level must stay near O(1) per insert rather than degrading to O(n^2). +// The "maxed" case additionally pushes the configurable parameters to their limits +// (fanOut 255 across 10 levels, maxExact 1000) with several argument variations per +// process to also stress the argument-level tree. +func BenchmarkHighCardinalityProcesses(b *testing.B) { + cases := []struct { + name string + numProcesses int + argsPerProc int + fanOut []int + maxExact int + }{ + { + name: "10k_processes_default_fanout", + numProcesses: 10000, + argsPerProc: 1, + fanOut: []int{8, 6, 4, 2}, + maxExact: 5, + }, + { + name: "10k_processes_maxed_params", + numProcesses: 10000, + argsPerProc: 20, + fanOut: []int{255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + maxExact: 1000, + }, + } - // Force GC to measure actual memory retained - runtime.GC() + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + indicators := make([]*storage.ProcessIndicator, 0, tc.numProcesses*tc.argsPerProc) + for p := range tc.numProcesses { + for a := range tc.argsPerProc { + indicators = append(indicators, &storage.ProcessIndicator{ + DeploymentId: "dep", + ContainerName: "container", + Signal: &storage.ProcessSignal{ + ContainerId: "id", + ExecFilePath: fmt.Sprintf("/usr/bin/process%d", p), + Args: fmt.Sprintf("--flag%d value subcommand run", a), + }, + }) + } + } + + b.ReportAllocs() + b.ResetTimer() + for b.Loop() { + filter := NewFilter(tc.maxExact, math.MaxInt, tc.fanOut) + for _, pi := range indicators { + filter.Add(pi) + } + } + }) } } diff --git a/pkg/process/filter/filter_test.go b/pkg/process/filter/filter_test.go index 3100b065e3c82..df6bf3d8a2bec 100644 --- a/pkg/process/filter/filter_test.go +++ b/pkg/process/filter/filter_test.go @@ -21,6 +21,14 @@ func TestBasicFilterFunctions(t *testing.T) { assert.True(t, filter.Add(pi)) } +func TestNewFilterNegativeFanOut(t *testing.T) { + // A negative fanOut must be caught at the NewFilter boundary rather than + // silently wrapping through uint8 to 255. utils.Should panics on non-release builds. + assert.Panics(t, func() { + NewFilter(2, 5, []int{-1}) + }) +} + func TestBasicFilter(t *testing.T) { cases := []struct { name string From bebd81efeecf22360707f6f6c63429793c48daf1 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:22:17 +0200 Subject: [PATCH 063/638] ROX-34788: Use Quadlet in e2e tests (#22345) --- .../virtualmachines/roxagent/cmd/sdnotify.go | 20 ++ .../roxagent/cmd/sdnotify_test.go | 46 ++++ .../virtualmachines/roxagent/cmd/serve.go | 64 +++-- .../roxagent/quadlet/README.md | 32 ++- .../roxagent/quadlet/roxagent.container | 4 + tests/e2e/vm-scanning-lib.sh | 27 ++- tests/vm_scanning_suite_test.go | 30 +-- tests/vm_scanning_test.go | 10 +- tests/vmhelpers/roxagent.go | 220 ++++++------------ tests/vmhelpers/roxagent_quadlet.go | 214 +++++++++++++++++ tests/vmhelpers/roxagent_quadlet_test.go | 136 +++++++++++ tests/vmhelpers/roxagent_test.go | 44 ---- tests/vmhelpers/vm_scan_config.go | 44 +++- tests/vmhelpers/vm_scan_config_test.go | 84 ++++++- 14 files changed, 723 insertions(+), 252 deletions(-) create mode 100644 compliance/virtualmachines/roxagent/cmd/sdnotify.go create mode 100644 compliance/virtualmachines/roxagent/cmd/sdnotify_test.go create mode 100644 tests/vmhelpers/roxagent_quadlet.go create mode 100644 tests/vmhelpers/roxagent_quadlet_test.go delete mode 100644 tests/vmhelpers/roxagent_test.go diff --git a/compliance/virtualmachines/roxagent/cmd/sdnotify.go b/compliance/virtualmachines/roxagent/cmd/sdnotify.go new file mode 100644 index 0000000000000..9ad3a4df5bfc2 --- /dev/null +++ b/compliance/virtualmachines/roxagent/cmd/sdnotify.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + + "github.com/coreos/go-systemd/v22/daemon" +) + +// notifySystemdReady sends READY=1 on NOTIFY_SOCKET when set (Type=notify / +// Quadlet Notify=true). No-op when unset so bare `roxagent serve` still works. +func notifySystemdReady() error { + sent, err := daemon.SdNotify(false, daemon.SdNotifyReady) + if err != nil { + return fmt.Errorf("sd_notify READY=1: %w", err) + } + if sent { + log.Info("Signaled systemd readiness (READY=1)") + } + return nil +} diff --git a/compliance/virtualmachines/roxagent/cmd/sdnotify_test.go b/compliance/virtualmachines/roxagent/cmd/sdnotify_test.go new file mode 100644 index 0000000000000..f3cae0a4c2a23 --- /dev/null +++ b/compliance/virtualmachines/roxagent/cmd/sdnotify_test.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestNotifySystemdReady_NoSocketIsNoop covers running without systemd +// (NOTIFY_SOCKET unset): readiness notify must not error. +func TestNotifySystemdReady_NoSocketIsNoop(t *testing.T) { + t.Setenv("NOTIFY_SOCKET", "") + require.NoError(t, notifySystemdReady()) +} + +// TestNotifySystemdReady_WritesReady covers the sd_notify protocol against a +// fake NOTIFY_SOCKET: READY=1 must be written as a unixgram datagram. +func TestNotifySystemdReady_WritesReady(t *testing.T) { + // Prefer a short path under /tmp: macOS AF_UNIX sun_path is small, and + // t.TempDir() paths are often too long to bind. + f, err := os.CreateTemp("", "rox-sdn-*.sock") + require.NoError(t, err) + sockPath := f.Name() + require.NoError(t, f.Close()) + require.NoError(t, os.Remove(sockPath)) + t.Cleanup(func() { _ = os.Remove(sockPath) }) + + addr := &net.UnixAddr{Name: sockPath, Net: "unixgram"} + ln, err := net.ListenUnixgram("unixgram", addr) + require.NoError(t, err) + t.Cleanup(func() { _ = ln.Close() }) + + t.Setenv("NOTIFY_SOCKET", sockPath) + + require.NoError(t, notifySystemdReady()) + + _ = ln.SetReadDeadline(time.Now().Add(2 * time.Second)) + buf := make([]byte, 64) + n, _, err := ln.ReadFromUnix(buf) + require.NoError(t, err) + assert.Equal(t, "READY=1", string(buf[:n])) +} diff --git a/compliance/virtualmachines/roxagent/cmd/serve.go b/compliance/virtualmachines/roxagent/cmd/serve.go index c84c4f115f2af..8fd852b87f07d 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve.go +++ b/compliance/virtualmachines/roxagent/cmd/serve.go @@ -108,27 +108,12 @@ func runServe(ctx context.Context, cfg serveConfig) error { return err } - // The mapping file must exist locally before the first scan can run: - // scan() never fetches it itself, so this initial fetch is mandatory. - // If it fails, startup fails rather than running a scan against no data. - // Start(ctx, true) blocks until that fetch completes, then keeps - // refreshing the file in the background. - mappingDownloader := newMappingDownloader(cfg.repoCPEURL, mappingCachePath, - logMappingDownloadResult(cfg.repoCPEURL, mappingCachePath)) - if err := mappingDownloader.Start(ctx, true); err != nil { - return fmt.Errorf("initial repository-to-CPE mapping fetch: %w", err) - } + // serveCtx lets early-return paths (mapping/scan failure after listen) + // stop Serve instead of leaking the accept loop until process exit. + serveCtx, cancelServe := context.WithCancel(ctx) + defer cancelServe() cache := &vsockserver.ReportCache{} - vmRescanner := newRescanner(cache, cfg.hostPath, mappingCachePath, cfg.rescanInterval) - - report, err := scan(ctx, cfg.hostPath, mappingCachePath) - if err != nil { - return fmt.Errorf("initial scan: %w", err) - } - cache.SetReport(report, discoverFacts(cfg.hostPath)) - log.Infof("Initial scan complete, report cached. Num packages: %d", len(report.GetContents().GetPackages())) - handler := vsockserver.NewHandler(cache, agentVersion) // TLS is mandatory: sensor always dials with TLS, so a plaintext agent is @@ -155,14 +140,47 @@ func runServe(ctx context.Context, cfg serveConfig) error { log.Infof("Listening on VSOCK port %d (pull mode)", cfg.port) var wg sync.WaitGroup - wg.Go(func() { srv.Serve(ctx, ln) }) - wg.Go(func() { vmRescanner.Run(ctx) }) + wg.Go(func() { srv.Serve(serveCtx, ln) }) + + // Report systemd readiness once VSOCK is up. + if err := notifySystemdReady(); err != nil { + // Exiting, as systemd would kill this service anyway after TimeoutStartSec. + cancelServe() + wg.Wait() + return err + } + + // The mapping file must exist locally before the first scan can run: + // scan() never fetches it itself, so this initial fetch is mandatory. + // If it fails, startup fails rather than running a scan against no data. + // Start(serveCtx, true) blocks until that fetch completes, then keeps + // refreshing the file in the background. + mappingDownloader := newMappingDownloader(cfg.repoCPEURL, mappingCachePath, + logMappingDownloadResult(cfg.repoCPEURL, mappingCachePath)) + if err := mappingDownloader.Start(serveCtx, true); err != nil { + cancelServe() + wg.Wait() + return fmt.Errorf("initial repository-to-CPE mapping fetch: %w", err) + } + + report, err := scan(serveCtx, cfg.hostPath, mappingCachePath) + if err != nil { + cancelServe() + wg.Wait() + mappingDownloader.Stop() + return fmt.Errorf("initial scan: %w", err) + } + cache.SetReport(report, discoverFacts(cfg.hostPath)) + log.Infof("Initial scan complete, report cached. Num packages: %d", len(report.GetContents().GetPackages())) + + vmRescanner := newRescanner(cache, cfg.hostPath, mappingCachePath, cfg.rescanInterval) + wg.Go(func() { vmRescanner.Run(serveCtx) }) - <-ctx.Done() + <-serveCtx.Done() // Wait for Serve's graceful drain (in-flight connections) and the // rescan loop to finish before returning, so the process doesn't exit // mid-drain or mid-scan. mappingDownloader.Stop waits for its own - // background refresh loop the same way, once ctx is already done. + // background refresh loop the same way, once serveCtx is already done. wg.Wait() mappingDownloader.Stop() return nil diff --git a/compliance/virtualmachines/roxagent/quadlet/README.md b/compliance/virtualmachines/roxagent/quadlet/README.md index e7244020f54c7..9f12e3469a8b4 100644 --- a/compliance/virtualmachines/roxagent/quadlet/README.md +++ b/compliance/virtualmachines/roxagent/quadlet/README.md @@ -6,6 +6,8 @@ Run roxagent on RHEL VMs with Podman Quadlet: a systemd-managed container that s [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) turns `roxagent.container` into a systemd unit that runs `roxagent serve`. The agent scans installed packages, caches the report, and listens on a VSOCK port. Sensor dials in when it needs a report. The agent rescans on its own schedule (default: every 4 hours). +`Notify=true` makes Podman pass systemd's notify socket into the container, so `systemctl status roxagent` becomes `active` only after the agent has bound the VSOCK listener (not merely when the container process starts). `TimeoutStartSec=600` covers a cold pull of the main image, which can exceed systemd's 90s default. + ### Components | File | Description | @@ -18,7 +20,7 @@ Run roxagent on RHEL VMs with Podman Quadlet: a systemd-managed container that s * RHEL 8, 9, or 10 VM running on KubeVirt with VSOCK enabled * Podman installed (`dnf install -y podman`) * StackRox deployed with VM scanning enabled (`ROX_VIRTUAL_MACHINES=true`) -* Network access to pull the StackRox main image +* Network access to pull the StackRox main image. For a private registry, see [Private Registries](#private-registries). ## Installation @@ -113,6 +115,30 @@ Exec=serve --host-path /host --port 2048 The port must match the StackRox Sensor configuration. +### Private Registries + +Quadlet pulls the image as a systemd service. That pull does not use `/etc/containers/auth.json` unless you pass the path into Podman. + +RHEL 8 Quadlet (Podman 4.9) rejects `AuthFile=` in the `[Container]` section (`unsupported key 'AuthFile'`), so set a systemd environment variable instead. + +If the StackRox main image is in a private registry: + +1. Put registry credentials in `/etc/containers/auth.json`. Create that file with `sudo podman login` or by copying a Docker config JSON. Each registry entry needs an `auth` field (`base64(username:password)`); RHEL 8 Podman ignores username/password-only files. +2. Add this line to the `[Service]` section of `roxagent.container`: + +```ini +Environment=REGISTRY_AUTH_FILE=/etc/containers/auth.json +``` + +On Podman 5 and later you can set `AuthFile=/etc/containers/auth.json` in `[Container]` instead. + +3. Reload systemd and restart the unit: + +```bash +sudo systemctl daemon-reload +sudo systemctl restart roxagent.service +``` + ## Troubleshooting ### No Packages Found @@ -140,6 +166,10 @@ ls -la /dev/vsock lsmod | grep vsock ``` +### Image Pull Unauthorized + +If `journalctl -u roxagent.service` reports `unauthorized` while pulling the main image, Quadlet is not using your registry credentials. Set `REGISTRY_AUTH_FILE` as described in [Private Registries](#private-registries). If `quadlet --dryrun` reports `unsupported key 'AuthFile'`, use the `[Service]` `Environment=` form, not `AuthFile=`. + ### Container Fails to Start Check Quadlet generation: diff --git a/compliance/virtualmachines/roxagent/quadlet/roxagent.container b/compliance/virtualmachines/roxagent/quadlet/roxagent.container index 566ecd248c5a9..5492417eabb27 100644 --- a/compliance/virtualmachines/roxagent/quadlet/roxagent.container +++ b/compliance/virtualmachines/roxagent/quadlet/roxagent.container @@ -8,6 +8,8 @@ Image=quay.io/stackrox-io/main:latest Pull=newer Exec=serve --host-path /host Network=host +# Pass NOTIFY_SOCKET into the container so roxagent can READY=1 after VSOCK listen. +Notify=true # Privileged for vsock access # Use PodmanArgs for entrypoint (RHEL 8 Quadlet doesn't support Entrypoint key) @@ -34,6 +36,8 @@ Volume=/usr/lib/sysimage/libdnf5:/host/usr/lib/sysimage/libdnf5:ro [Service] Restart=on-failure RestartSec=10s +# First pull of the main image can take minutes; systemd's 90s default would kill Type=notify start. +TimeoutStartSec=600 [Install] WantedBy=multi-user.target diff --git a/tests/e2e/vm-scanning-lib.sh b/tests/e2e/vm-scanning-lib.sh index b46dde96a9d8f..2b8bb13c81ff0 100755 --- a/tests/e2e/vm-scanning-lib.sh +++ b/tests/e2e/vm-scanning-lib.sh @@ -13,16 +13,37 @@ ensure_vm_scanning_cluster_prereqs() { require_environment "KUBECONFIG" require_environment "VM_IMAGES" - # Build a docker config JSON for pulling private container-disk images - # (e.g. quay.io/rhacs-eng/vm-images/*) inside the VM test namespace. + # Quadlet pulls the cluster main image. Construct MAIN_IMAGE the same way + # deploy.sh does when the caller only exported MAIN_IMAGE_TAG. + if [[ -z "${ROXAGENT_IMAGE:-}" && -z "${MAIN_IMAGE:-}" ]]; then + if [[ -z "${MAIN_IMAGE_TAG:-}" ]]; then + MAIN_IMAGE_TAG="$(make --quiet --no-print-directory -C "$_VM_SCANNING_LIB_ROOT" tag)" + export MAIN_IMAGE_TAG + fi + if [[ -z "${MAIN_IMAGE_REPO:-}" ]]; then + MAIN_IMAGE_REPO="$(make --quiet --no-print-directory -C "$_VM_SCANNING_LIB_ROOT" default-image-registry)/main" + export MAIN_IMAGE_REPO + fi + export MAIN_IMAGE="${MAIN_IMAGE_REPO}:${MAIN_IMAGE_TAG}" + info "MAIN_IMAGE set to ${MAIN_IMAGE} for Quadlet roxagent" + fi + + # Kubernetes imagePullSecret (username/password dockerconfig) and a separate + # containers-auth file for guest Podman. RHEL 8 Podman requires the auth field. if [[ -n "${QUAY_RHACS_ENG_RO_USERNAME:-}" && -n "${QUAY_RHACS_ENG_RO_PASSWORD:-}" ]]; then - local vm_pull_secret + local vm_pull_secret vm_podman_auth quay_auth vm_pull_secret="$(mktemp)" + vm_podman_auth="$(mktemp)" cat > "$vm_pull_secret" < "$vm_podman_auth" + chmod 600 "$vm_podman_auth" export VM_IMAGE_PULL_SECRET_PATH="$vm_pull_secret" + export VM_PODMAN_AUTH_FILE="$vm_podman_auth" info "VM image pull secret written to ${vm_pull_secret}" + info "VM Podman auth file written to ${vm_podman_auth}" else info "QUAY_RHACS_ENG_RO_USERNAME/PASSWORD not set; VM images must be publicly accessible" fi diff --git a/tests/vm_scanning_suite_test.go b/tests/vm_scanning_suite_test.go index c4f4470cbb49f..c37da24f99308 100644 --- a/tests/vm_scanning_suite_test.go +++ b/tests/vm_scanning_suite_test.go @@ -90,6 +90,8 @@ type VMHandle struct { ID string // NodeName is the Kubernetes node hosting the VirtualMachineInstance (populated after VMI is Running). NodeName string + // SkipReason, when set, skips this VM's subtests so other VMs still run. + SkipReason string } // VMScanningSuite exercises OpenShift VM scanning end-to-end (KubeVirt guests, roxagent, Central). @@ -592,7 +594,7 @@ func (s *VMScanningSuite) prepareGuests() { func (s *VMScanningSuite) prepareGuestWithRecovery(vm *VMHandle) error { const maxRecoveries = 2 for recoveryAttempt := 0; recoveryAttempt <= maxRecoveries; recoveryAttempt++ { - err := s.prepareGuest(*vm) + err := s.prepareGuest(vm) if err == nil { return nil } @@ -711,8 +713,8 @@ func (s *VMScanningSuite) waitForScannerV4Initialized() error { return nil } -// ensureRoxagentServing starts pull-mode `roxagent serve` on the guest (if needed) -// and waits until its VSOCK listener is ready. Sensor scrapes the report afterward. +// ensureRoxagentServing starts Quadlet roxagent.service on the guest if needed +// and waits until it is active (VSOCK listener ready). Sensor scrapes afterward. func (s *VMScanningSuite) ensureRoxagentServing(ctx context.Context, vm *VMHandle) error { if vm == nil { return errors.New("ensureRoxagentServing: nil VM handle") @@ -722,7 +724,7 @@ func (s *VMScanningSuite) ensureRoxagentServing(ctx context.Context, vm *VMHandl return fmt.Errorf("Scanner V4 matcher did not initialize within timeout: %w", err) } virt := s.virtctlForVM(*vm) - return vmhelpers.EnsureRoxagentServing(ctx, virt, vm.Namespace, vm.Name, s.cfg.Repo2CPEURL) + return vmhelpers.EnsureRoxagentServing(ctx, virt, vm.Namespace, vm.Name) } // waitForScan polls Central in order until scan data is visible. @@ -773,8 +775,8 @@ func (s *VMScanningSuite) resourceDeleteTimeout() time.Duration { return defaultVMDeleteTimeout } -func (s *VMScanningSuite) prepareGuest(vm VMHandle) error { - virt := s.virtctlForVM(vm) +func (s *VMScanningSuite) prepareGuest(vm *VMHandle) error { + virt := s.virtctlForVM(*vm) stepNum := 0 runStep := func(stepName, errContext string, timeout time.Duration, fn func(stepCtx context.Context) error) error { stepNum++ @@ -806,15 +808,15 @@ func (s *VMScanningSuite) prepareGuest(vm VMHandle) error { }); err != nil { return err } - if err := runStep("Copy roxagent binary", "CopyRoxagentBinary", stepTimeout, func(stepCtx context.Context) error { - return vmhelpers.CopyRoxagentBinary(stepCtx, virt, vm.Namespace, vm.Name, s.cfg.RoxagentBinaryPath) - }); err != nil { - return err - } - // Runs `roxagent --help` to confirm the binary is present, executable, and in $PATH. - if err := runStep("Verify roxagent installed", "VerifyRoxagentInstalled", stepTimeout, func(stepCtx context.Context) error { - return vmhelpers.VerifyRoxagentInstalled(stepCtx, virt, vm.Namespace, vm.Name) + if err := runStep("Install roxagent Quadlet", "InstallRoxagentQuadlet", max(stepTimeout, 15*time.Minute), func(stepCtx context.Context) error { + return vmhelpers.InstallRoxagentQuadlet(stepCtx, virt, vm.Namespace, vm.Name, s.cfg.RoxagentImage, s.cfg.Repo2CPEURL, s.cfg.PodmanAuthFilePath) }); err != nil { + if errors.Is(err, vmhelpers.ErrPodmanNotFound) { + vm.SkipReason = err.Error() + s.logf("[guest prep] Quadlet install skipped on %s/%s; VM subtest will be skipped: %v", + vm.Namespace, vm.Name, err) + return nil + } return err } s.logf("[guest prep] COMPLETED for %s/%s in %d step(s)", vm.Namespace, vm.Name, stepNum) diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index 5cbb78bfec3b0..9e991c5054172 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -14,6 +14,12 @@ import ( func (s *VMScanningSuite) TestScanPipeline() { for i := range s.vms { vm := &s.vms[i] + if vm.SkipReason != "" { + s.T().Run(vm.Name, func(t *testing.T) { + t.Skip(vm.SkipReason) + }) + continue + } virt := s.virtctlForVM(*vm) if err := vmhelpers.EnsureVsockReady(s.ctx, virt, vm.Namespace, vm.Name, "scan pipeline"); err != nil { @@ -26,8 +32,8 @@ func (s *VMScanningSuite) TestScanPipeline() { roxagentOK := false t.Run("EnsureRoxagentServing", func(t *testing.T) { - t.Logf("ensuring pull-mode agent: sudo %s serve --port 818 --host-path / --rescan-interval 5m --repo-cpe-url %s", - vmhelpers.DefaultRoxagentInstallPath, s.cfg.Repo2CPEURL) + t.Logf("ensuring Quadlet roxagent.service is active (image=%s rescan=%s repo-cpe-url=%s)", + s.cfg.RoxagentImage, vmhelpers.E2ERescanInterval, s.cfg.Repo2CPEURL) err := s.ensureRoxagentServing(s.ctx, vm) require.NoError(t, err) roxagentOK = true diff --git a/tests/vmhelpers/roxagent.go b/tests/vmhelpers/roxagent.go index 48f9e327f0f25..43b011a395885 100644 --- a/tests/vmhelpers/roxagent.go +++ b/tests/vmhelpers/roxagent.go @@ -8,12 +8,7 @@ import ( "time" ) -// Guest paths for staging and installing the roxagent binary during VM tests. const ( - // DefaultRoxagentInstallPath is the path used when copying and running roxagent on the guest. - DefaultRoxagentInstallPath = "/usr/local/bin/roxagent" - roxagentStagingPath = "/tmp/roxagent" - roxagentServePort = "818" // E2ERescanInterval is passed to serve --rescan-interval. It is the // minimum allowed interval and keeps the package-freshness e2e within a // reasonable wall-clock budget without reactive watching or restarts. @@ -21,165 +16,105 @@ const ( // E2EScraperPollInterval is the Sensor pull-mode scrape cadence used by // the VM e2e suite - floored at 1m. E2EScraperPollInterval = time.Minute - // Transient systemd unit started by EnsureRoxagentServing (systemd-run --unit=...). - roxagentE2EUnit = "roxagent-e2e.service" - roxagentE2EUnitName = "roxagent-e2e" - roxagentListenReadyMarker = "Listening on VSOCK port" + // roxagentUnit is the Quadlet-generated systemd unit customers run. + roxagentUnit = "roxagent.service" roxagentListenPollInterval = 5 * time.Second + guestPodmanAuthPath = "/etc/containers/auth.json" + guestPodmanAuthStagingPath = "/tmp/roxagent-podman-auth.json" ) +// ErrPodmanNotFound means the guest has no podman binary. SetupSuite records +// SkipReason and continues; TestScanPipeline skips that VM's subtests. +var ErrPodmanNotFound = errors.New("podman not found on guest") + // ErrTerminalVSOCKUnavailable is returned when vsock is permanently unavailable on the guest (no retry). var ErrTerminalVSOCKUnavailable = errors.New("terminal vsock unavailable") -// isVsockUnavailableOutput detects terminal vsock device errors in roxagent combined output. +// isVsockUnavailableOutput detects terminal vsock device errors in roxagent or +// Podman combined output. Both the vsock token and an unavailable-device marker +// must appear on the same line so an unrelated ENOENT in a journal dump is ignored. func isVsockUnavailableOutput(output string) bool { - lower := strings.ToLower(strings.TrimSpace(output)) - if !strings.Contains(lower, "vsock") { - return false - } - return strings.Contains(lower, "no such device") || - strings.Contains(lower, "no such file or directory") -} - -// CopyRoxagentBinary copies a local roxagent binary into the guest install path. -func CopyRoxagentBinary(ctx context.Context, virt Virtctl, namespace, vm, hostBinaryPath string) error { - return retryOnSSHTransport(ctx, virt.Logf, "copy roxagent binary", func(ctx context.Context) error { - stderr, err := virt.SCPTo(ctx, namespace, vm, hostBinaryPath, roxagentStagingPath) - if err != nil { - return fmt.Errorf("virtctl scp roxagent: %w: %s", err, strings.TrimSpace(stderr)) + for line := range strings.SplitSeq(strings.ToLower(output), "\n") { + if !strings.Contains(line, "vsock") { + continue } - _, stderr, err = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "install roxagent binary", - transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "install", "-m", "0755", roxagentStagingPath, DefaultRoxagentInstallPath) - if err != nil { - return fmt.Errorf("install roxagent binary on guest: %w: %s", err, strings.TrimSpace(stderr)) - } - return nil - }) -} - -// VerifyRoxagentInstalled runs `roxagent --help` on the guest to confirm the binary is -// present, executable, and resolvable in $PATH — all in a single SSH round-trip. -func VerifyRoxagentInstalled(ctx context.Context, virt Virtctl, namespace, vm string) error { - return retryOnSSHTransport(ctx, virt.Logf, "verify roxagent installed", func(ctx context.Context) error { - _, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "verify roxagent installed", - transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, DefaultRoxagentInstallPath, "--help") - if err != nil { - return fmt.Errorf("roxagent --help: %w: %s", err, strings.TrimSpace(stderr)) + if strings.Contains(line, "no such device") || + strings.Contains(line, "no such file or directory") || + (strings.Contains(line, "/dev/vsock") && strings.Contains(line, "not found")) { + return true } - return nil - }) + } + return false } -// EnsureRoxagentServing starts `roxagent serve` on the guest via a transient systemd -// unit if it is not already active, then waits until the VSOCK listener is up. -// Sensor pulls reports from this long-running process; the test does not push -// index reports. -// -// repo2cpeURL is passed as --repo-cpe-url (serve does not read ROXAGENT_REPO2CPE_URL). -func EnsureRoxagentServing(ctx context.Context, virt Virtctl, namespace, vm, repo2cpeURL string) error { - invocationID, err := startRoxagentServeIfNeeded(ctx, virt, namespace, vm, repo2cpeURL) - if err != nil { +// EnsureRoxagentServing starts Quadlet `roxagent.service` if it is not already +// active, then waits until ActiveState is active (READY=1 after VSOCK listen). +func EnsureRoxagentServing(ctx context.Context, virt Virtctl, namespace, vm string) error { + if err := startRoxagentServeIfNeeded(ctx, virt, namespace, vm); err != nil { return err } - return waitRoxagentListening(ctx, virt, namespace, vm, invocationID) + return waitRoxagentActive(ctx, virt, namespace, vm) } -// startRoxagentServeIfNeeded starts the e2e unit if it isn't already active/activating, -// and returns its current InvocationID so waitRoxagentListening can scope its journal -// query to exactly this activation. -func startRoxagentServeIfNeeded(ctx context.Context, virt Virtctl, namespace, vm, repo2cpeURL string) (string, error) { - var invocationID string - err := retryOnSSHTransport(ctx, virt.Logf, "start roxagent serve", func(ctx context.Context) error { +func startRoxagentServeIfNeeded(ctx context.Context, virt Virtctl, namespace, vm string) error { + return retryOnSSHTransport(ctx, virt.Logf, "start roxagent serve", func(ctx context.Context) error { state, err := roxagentServeState(ctx, virt, namespace, vm) if err != nil { return err } if state == "active" || state == "activating" { - virt.Logf("roxagent serve: %s already %s", roxagentE2EUnit, state) - invocationID, err = roxagentInvocationID(ctx, virt, namespace, vm) - return err + virt.Logf("roxagent serve: %s already %s", roxagentUnit, state) + return nil } - // systemd-run refuses to reuse a unit name that still exists (failed or - // stopped). Clear leftovers from a prior attempt, then start fresh. - _, _, _ = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "stop leftover roxagent-e2e unit", - transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "systemctl", "stop", roxagentE2EUnit) - _, _, _ = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "reset-failed roxagent-e2e unit", - transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "systemctl", "reset-failed", roxagentE2EUnit) + if state == "failed" { + _, _, _ = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "reset-failed roxagent.service", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "sudo", "systemctl", "reset-failed", roxagentUnit) + } _, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "systemd-run roxagent serve", + description: "systemctl start roxagent.service", transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "systemd-run", - "--unit="+roxagentE2EUnitName, - "--property=Type=simple", - DefaultRoxagentInstallPath, "serve", - "--port", roxagentServePort, - "--host-path", "/", - "--rescan-interval", E2ERescanInterval.String(), - "--repo-cpe-url", repo2cpeURL, - ) + }, "sudo", "systemctl", "start", roxagentUnit) if err != nil { - logs := fetchRoxagentServeJournal(ctx, virt, namespace, vm) - combined := strings.TrimSpace(stderr + "\n" + logs) - if isVsockUnavailableOutput(combined) { - return fmt.Errorf("%w: systemd-run roxagent serve: %w (output: %s)", - ErrTerminalVSOCKUnavailable, err, combined) - } - return fmt.Errorf("systemd-run roxagent serve: %w (stderr: %s; journal: %s)", - err, strings.TrimSpace(stderr), logs) + return wrapRoxagentUnitCmdError(ctx, virt, namespace, vm, "systemctl start "+roxagentUnit, err, stderr) } - virt.Logf("roxagent serve: systemd-run accepted %s", roxagentE2EUnit) - invocationID, err = roxagentInvocationID(ctx, virt, namespace, vm) - return err + virt.Logf("roxagent serve: started %s", roxagentUnit) + return nil }) - return invocationID, err } -// waitRoxagentListening polls until the unit identified by invocationID is both active -// and reports the VSOCK listen marker in its journal. -func waitRoxagentListening(ctx context.Context, virt Virtctl, namespace, vm, invocationID string) error { +// waitRoxagentActive polls until the Quadlet unit's ActiveState is active. +// Type=notify keeps the unit in activating until READY=1 after VSOCK listen. +func waitRoxagentActive(ctx context.Context, virt Virtctl, namespace, vm string) error { for { state, err := roxagentServeState(ctx, virt, namespace, vm) if err != nil { return err } switch state { - case "active", "activating": - // keep waiting for the listen log line + case "active": + virt.Logf("roxagent serve is active (VSOCK listener ready)") + return nil + case "activating": + virt.Logf("roxagent serve still starting (unit=%s, waiting for Type=notify READY)", state) default: logs := fetchRoxagentServeJournal(ctx, virt, namespace, vm) if isVsockUnavailableOutput(logs) { - return fmt.Errorf("%w: roxagent serve unit %q (journal: %s)", - ErrTerminalVSOCKUnavailable, state, logs) + return fmt.Errorf("%w: %s ActiveState=%q (journal: %s)", + ErrTerminalVSOCKUnavailable, roxagentUnit, state, logs) } - return fmt.Errorf("roxagent serve unit %q (journal: %s)", state, logs) - } - - listening, err := roxagentServeListening(ctx, virt, namespace, vm, invocationID) - if err != nil { - return err - } - if listening { - virt.Logf("roxagent serve is listening on VSOCK") - return nil + return fmt.Errorf("%s ActiveState=%q (journal: %s)", roxagentUnit, state, logs) } - virt.Logf("roxagent serve still starting (unit=%s, waiting for %q)", state, roxagentListenReadyMarker) timer := time.NewTimer(roxagentListenPollInterval) select { case <-ctx.Done(): timer.Stop() - return fmt.Errorf("timed out waiting for roxagent serve to listen: %w", ctx.Err()) + return fmt.Errorf("timed out waiting for roxagent serve to become active: %w", ctx.Err()) case <-timer.C: } } @@ -190,8 +125,8 @@ func waitRoxagentListening(ctx context.Context, virt Virtctl, namespace, vm, inv // for any unit systemd knows about — loaded or not, active or not — so a non-zero exit here // reliably means a genuine remote-command problem, not an expected state value. That makes it // the preferred way to read any systemd unit property from the guest; add new lookups (like -// roxagentServeState and roxagentInvocationID below) on top of it rather than parsing the -// output or exit code of state-specific subcommands such as `is-active`. +// roxagentServeState below) on top of it rather than parsing the output or exit code of +// state-specific subcommands such as `is-active`. func systemctlShowProperty(ctx context.Context, virt Virtctl, namespace, vm, unit, property string) (string, error) { stdout, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ description: fmt.Sprintf("systemctl show -p %s %s", property, unit), @@ -204,21 +139,21 @@ func systemctlShowProperty(ctx context.Context, virt Virtctl, namespace, vm, uni return strings.TrimSpace(stdout), nil } -// RoxagentServeInvocationID returns the e2e unit's current systemd InvocationID. +// RoxagentServeInvocationID returns the Quadlet unit's current systemd InvocationID. // That UUID is unique per activation, so comparing it before and after a wait // detects restarts. func RoxagentServeInvocationID(ctx context.Context, virt Virtctl, namespace, vm string) (string, error) { - id, err := roxagentInvocationID(ctx, virt, namespace, vm) + id, err := systemctlShowProperty(ctx, virt, namespace, vm, roxagentUnit, "InvocationID") if err != nil { return "", err } if id == "" { - return "", errors.New("roxagent-e2e InvocationID is empty") + return "", errors.New("roxagent.service InvocationID is empty") } return id, nil } -// RoxagentServeDidNotRestart returns nil when the e2e unit still has beforeID +// RoxagentServeDidNotRestart returns nil when the Quadlet unit still has beforeID // as its InvocationID. A different ID means systemd started a new activation // (stop/start, crash recovery, or an explicit restart). func RoxagentServeDidNotRestart(ctx context.Context, virt Virtctl, namespace, vm, beforeID string) error { @@ -230,15 +165,15 @@ func RoxagentServeDidNotRestart(ctx context.Context, virt Virtctl, namespace, vm return err } if afterID != beforeID { - return fmt.Errorf("roxagent-e2e restarted during the wait: InvocationID changed from %s to %s", + return fmt.Errorf("roxagent.service restarted during the wait: InvocationID changed from %s to %s", beforeID, afterID) } return nil } -// roxagentServeState returns the e2e unit's ActiveState (active/inactive/failed/activating/…). +// roxagentServeState returns the Quadlet unit's ActiveState (active/inactive/failed/activating/…). func roxagentServeState(ctx context.Context, virt Virtctl, namespace, vm string) (string, error) { - state, err := systemctlShowProperty(ctx, virt, namespace, vm, roxagentE2EUnit, "ActiveState") + state, err := systemctlShowProperty(ctx, virt, namespace, vm, roxagentUnit, "ActiveState") if err != nil { return "", err } @@ -248,37 +183,22 @@ func roxagentServeState(ctx context.Context, virt Virtctl, namespace, vm string) return state, nil } -// roxagentInvocationID returns the e2e unit's current InvocationID, a per-activation UUID. -// Filtering journalctl on it (instead of `-u -b`) scopes a query to exactly this run, -// so a stale marker line from an earlier activation of the same reused transient unit name -// (stopped/failed, then restarted by startRoxagentServeIfNeeded) can't produce a false positive. -func roxagentInvocationID(ctx context.Context, virt Virtctl, namespace, vm string) (string, error) { - return systemctlShowProperty(ctx, virt, namespace, vm, roxagentE2EUnit, "InvocationID") -} - -// roxagentServeListening reports whether the journal for the activation identified by -// invocationID contains the VSOCK listen marker. A plain dump exits 0 regardless of whether -// the marker is present, so a non-zero exit here always means a genuine SSH or remote-command -// problem rather than "not listening yet". The dump is unbounded (no `-n` tail) so a chatty -// agent cannot scroll the marker out of a fixed window between polls. -func roxagentServeListening(ctx context.Context, virt Virtctl, namespace, vm, invocationID string) (bool, error) { - stdout, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "journalctl roxagent listening check", - transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "journalctl", "_SYSTEMD_INVOCATION_ID="+invocationID, "--no-pager", "-o", "cat") - if err != nil { - return false, fmt.Errorf("journalctl _SYSTEMD_INVOCATION_ID=%s: %w (stdout: %s; stderr: %s)", - invocationID, err, strings.TrimSpace(stdout), strings.TrimSpace(stderr)) +// wrapRoxagentUnitCmdError appends journal context and maps terminal vsock failures. +func wrapRoxagentUnitCmdError(ctx context.Context, virt Virtctl, namespace, vm, op string, err error, stderr string) error { + logs := fetchRoxagentServeJournal(ctx, virt, namespace, vm) + combined := strings.TrimSpace(stderr + "\n" + logs) + if isVsockUnavailableOutput(combined) { + return fmt.Errorf("%w: %s: %w (output: %s)", ErrTerminalVSOCKUnavailable, op, err, combined) } - return strings.Contains(stdout, roxagentListenReadyMarker), nil + return fmt.Errorf("%s: %w (stderr: %s; journal: %s)", op, err, strings.TrimSpace(stderr), logs) } // fetchRoxagentServeJournal returns a short recent journal dump for error messages. func fetchRoxagentServeJournal(ctx context.Context, virt Virtctl, namespace, vm string) string { stdout, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ - description: "journalctl roxagent-e2e", + description: "journalctl roxagent.service", transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, - }, "sudo", "journalctl", "-u", roxagentE2EUnit, "-b", "--no-pager", "-o", "cat", "-n", "200") + }, "sudo", "journalctl", "-u", roxagentUnit, "-b", "--no-pager", "-o", "cat", "-n", "200") if err != nil { return strings.TrimSpace(stdout + "\n" + stderr) } diff --git a/tests/vmhelpers/roxagent_quadlet.go b/tests/vmhelpers/roxagent_quadlet.go new file mode 100644 index 0000000000000..10e2123c869ae --- /dev/null +++ b/tests/vmhelpers/roxagent_quadlet.go @@ -0,0 +1,214 @@ +package vmhelpers + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" +) + +const ( + quadletInstallScriptName = "install.sh" + quadletContainerFileName = "roxagent.container" + guestQuadletStageTemplate = "/tmp/roxagent-quadlet.XXXXXX" +) + +// InstallRoxagentQuadlet copies the customer installer plus a staged +// roxagent.container to the guest and runs install.sh --stage-dir so systemd +// ends up with roxagent.service. +func InstallRoxagentQuadlet(ctx context.Context, virt Virtctl, namespace, vm, image, repo2cpeURL, podmanAuthPath string) error { + if err := requireGuestPodman(ctx, virt, namespace, vm); err != nil { + return err + } + if err := installGuestPodmanAuth(ctx, virt, namespace, vm, podmanAuthPath); err != nil { + return err + } + + hostStage, err := stageQuadletInstall(image, repo2cpeURL, podmanAuthPath) + if err != nil { + return err + } + defer func() { _ = os.RemoveAll(hostStage) }() + + return retryOnSSHTransport(ctx, virt.Logf, "install roxagent quadlet", func(ctx context.Context) error { + guestStage, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "mktemp quadlet stage dir", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "mktemp", "-d", guestQuadletStageTemplate) + if err != nil { + return fmt.Errorf("mktemp quadlet stage dir: %w: %s", err, strings.TrimSpace(stderr)) + } + guestStage = strings.TrimSpace(guestStage) + if guestStage == "" { + return errors.New("mktemp quadlet stage dir: empty path") + } + + for _, name := range []string{quadletInstallScriptName, quadletContainerFileName} { + if scpErr := scpToGuest(ctx, virt, namespace, vm, filepath.Join(hostStage, name), guestStage+"/"+name); scpErr != nil { + return scpErr + } + } + + prevState := "inactive" + if s, stateErr := roxagentServeState(ctx, virt, namespace, vm); stateErr == nil { + prevState = s + } + if prevState == "failed" { + _, _, _ = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "reset-failed roxagent.service", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "sudo", "systemctl", "reset-failed", roxagentUnit) + } + + _, stderr, err = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "install.sh --stage-dir", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "bash", guestStage+"/"+quadletInstallScriptName, "--stage-dir", guestStage) + if err != nil { + return wrapRoxagentUnitCmdError(ctx, virt, namespace, vm, "install.sh --stage-dir", err, stderr) + } + + // install.sh start is a no-op when the unit is already running, so + // restart only on re-runs to apply a new Image=/Exec= overlay. + if prevState != "active" && prevState != "activating" { + return nil + } + _, stderr, err = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "systemctl restart roxagent.service", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "sudo", "systemctl", "restart", roxagentUnit) + if err != nil { + return wrapRoxagentUnitCmdError(ctx, virt, namespace, vm, "systemctl restart "+roxagentUnit, err, stderr) + } + return nil + }) +} + +func requireGuestPodman(ctx context.Context, virt Virtctl, namespace, vm string) error { + return retryOnSSHTransport(ctx, virt.Logf, "check guest podman", func(ctx context.Context) error { + _, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "podman --version", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "podman", "--version") + if err != nil { + return fmt.Errorf("%w on guest %s/%s (VM image must ship it; unactivated guests cannot dnf install): %v: %s", + ErrPodmanNotFound, namespace, vm, err, strings.TrimSpace(stderr)) + } + return nil + }) +} + +func installGuestPodmanAuth(ctx context.Context, virt Virtctl, namespace, vm, podmanAuthPath string) error { + if strings.TrimSpace(podmanAuthPath) == "" { + return nil + } + if _, err := os.Stat(podmanAuthPath); err != nil { + return fmt.Errorf("podman auth file %q: %w", podmanAuthPath, err) + } + return retryOnSSHTransport(ctx, virt.Logf, "install guest podman auth", func(ctx context.Context) error { + if err := scpToGuest(ctx, virt, namespace, vm, podmanAuthPath, guestPodmanAuthStagingPath); err != nil { + return err + } + defer func() { + _, _, _ = runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "remove staged podman auth file", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "rm", "-f", guestPodmanAuthStagingPath) + }() + _, stderr, err := runSSHCommandWithFramework(ctx, virt, namespace, vm, sshCommandRunOptions{ + description: "install /etc/containers/auth.json", + transportRetryAttempts: rhsmPrecheckSSHRetryThreshold, + }, "sudo", "install", "-D", "-m", "0600", guestPodmanAuthStagingPath, guestPodmanAuthPath) + if err != nil { + return fmt.Errorf("install %s: %w: %s", guestPodmanAuthPath, err, strings.TrimSpace(stderr)) + } + return nil + }) +} + +func scpToGuest(ctx context.Context, virt Virtctl, namespace, vm, src, dst string) error { + stderr, err := virt.SCPTo(ctx, namespace, vm, src, dst) + if err != nil { + return fmt.Errorf("virtctl scp %s -> %s: %w: %s", src, dst, err, strings.TrimSpace(stderr)) + } + return nil +} + +func stageQuadletInstall(image, repo2cpeURL, podmanAuthPath string) (string, error) { + srcDir := filepath.Join(repoRoot(), "compliance", "virtualmachines", "roxagent", "quadlet") + srcContainer, err := os.ReadFile(filepath.Join(srcDir, quadletContainerFileName)) + if err != nil { + return "", fmt.Errorf("read %s: %w", quadletContainerFileName, err) + } + authFile := "" + if strings.TrimSpace(podmanAuthPath) != "" { + authFile = guestPodmanAuthPath + } + overlayed, err := overlayQuadletContainer(srcContainer, image, repo2cpeURL, authFile) + if err != nil { + return "", err + } + installSrc, err := os.ReadFile(filepath.Join(srcDir, quadletInstallScriptName)) + if err != nil { + return "", fmt.Errorf("read %s: %w", quadletInstallScriptName, err) + } + + dir, err := os.MkdirTemp("", "roxagent-quadlet-e2e-") + if err != nil { + return "", fmt.Errorf("mktemp host quadlet stage: %w", err) + } + if err := os.WriteFile(filepath.Join(dir, quadletContainerFileName), overlayed, 0o644); err != nil { + _ = os.RemoveAll(dir) + return "", err + } + if err := os.WriteFile(filepath.Join(dir, quadletInstallScriptName), installSrc, 0o755); err != nil { + _ = os.RemoveAll(dir) + return "", err + } + return dir, nil +} + +// overlayQuadletContainer rewrites Image= and Exec= for the cluster image and +// a 5m rescan. A non-empty authFile sets REGISTRY_AUTH_FILE on [Service]: +// RHEL 8 Quadlet rejects AuthFile= as an unsupported [Container] key. +func overlayQuadletContainer(src []byte, image, repo2cpeURL, authFile string) ([]byte, error) { + if strings.TrimSpace(image) == "" { + return nil, errors.New("overlayQuadletContainer: image is empty") + } + if strings.TrimSpace(repo2cpeURL) == "" { + return nil, errors.New("overlayQuadletContainer: repo2cpeURL is empty") + } + + sawImage, sawExec := false, false + srcLines := strings.Split(string(src), "\n") + out := make([]string, 0, len(srcLines)+1) + for _, line := range srcLines { + switch { + case strings.HasPrefix(line, "Image="): + out = append(out, "Image="+image) + sawImage = true + case strings.HasPrefix(line, "AuthFile="): + continue + case line == "[Service]": + out = append(out, "[Service]") + if authFile != "" { + out = append(out, "Environment=REGISTRY_AUTH_FILE="+authFile) + } + case strings.HasPrefix(line, "Exec="): + out = append(out, "Exec=serve --host-path /host --rescan-interval "+ + E2ERescanInterval.String()+" --repo-cpe-url "+repo2cpeURL) + sawExec = true + default: + out = append(out, line) + } + } + if !sawImage { + return nil, errors.New("overlayQuadletContainer: no Image= line") + } + if !sawExec { + return nil, errors.New("overlayQuadletContainer: no Exec= line") + } + return []byte(strings.Join(out, "\n")), nil +} diff --git a/tests/vmhelpers/roxagent_quadlet_test.go b/tests/vmhelpers/roxagent_quadlet_test.go new file mode 100644 index 0000000000000..cee1931789e4d --- /dev/null +++ b/tests/vmhelpers/roxagent_quadlet_test.go @@ -0,0 +1,136 @@ +//go:build test && !test_e2e && !test_e2e_vm + +package vmhelpers + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOverlayQuadletContainer(t *testing.T) { + t.Parallel() + + src, err := os.ReadFile(filepath.Join(repoRoot(), "compliance", "virtualmachines", "roxagent", "quadlet", "roxagent.container")) + require.NoError(t, err) + + const ( + image = "quay.io/rhacs-eng/main:e2e-test" + url = "https://example.test/repository-to-cpe.json" + ) + + got, err := overlayQuadletContainer(src, image, url, "") + require.NoError(t, err) + out := string(got) + require.Contains(t, out, "Image="+image) + require.NotContains(t, out, "Image=quay.io/stackrox-io/main:latest") + require.NotContains(t, out, "AuthFile=") + require.NotContains(t, out, "REGISTRY_AUTH_FILE") + require.Contains(t, out, "Exec=serve --host-path /host --rescan-interval "+E2ERescanInterval.String()+" --repo-cpe-url "+url) + require.Contains(t, out, "Notify=true") + require.Contains(t, out, "TimeoutStartSec=600") +} + +func TestOverlayQuadletContainer_RegistryAuthFile(t *testing.T) { + t.Parallel() + + src, err := os.ReadFile(filepath.Join(repoRoot(), "compliance", "virtualmachines", "roxagent", "quadlet", "roxagent.container")) + require.NoError(t, err) + + got, err := overlayQuadletContainer(src, "quay.io/rhacs-eng/main:e2e-test", "https://example.test/repository-to-cpe.json", guestPodmanAuthPath) + require.NoError(t, err) + out := string(got) + require.NotContains(t, out, "AuthFile=") + container, rest, found := strings.Cut(out, "[Service]") + require.True(t, found) + require.NotContains(t, container, "REGISTRY_AUTH_FILE") + require.Contains(t, rest, "Environment=REGISTRY_AUTH_FILE="+guestPodmanAuthPath) +} + +func TestOverlayQuadletContainer_Errors(t *testing.T) { + t.Parallel() + + src := []byte("[Container]\nImage=example:latest\nExec=serve\n") + tests := map[string]struct { + src []byte + image string + repo2cpeURL string + errSubstring string + }{ + "should reject empty image": { + src: src, + repo2cpeURL: "https://example.test/mapping.json", + errSubstring: "image is empty", + }, + "should reject empty repo2cpe URL": { + src: src, + image: "example:latest", + errSubstring: "repo2cpeURL is empty", + }, + "should reject missing Image=": { + src: []byte("[Container]\nExec=serve\n"), + image: "example:latest", + repo2cpeURL: "https://example.test/mapping.json", + errSubstring: "no Image= line", + }, + "should reject missing Exec=": { + src: []byte("[Container]\nImage=example:latest\n"), + image: "example:latest", + repo2cpeURL: "https://example.test/mapping.json", + errSubstring: "no Exec= line", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + _, err := overlayQuadletContainer(tc.src, tc.image, tc.repo2cpeURL, "") + require.ErrorContains(t, err, tc.errSubstring) + }) + } +} + +func TestIsVsockUnavailableOutput(t *testing.T) { + t.Parallel() + tests := map[string]struct { + output string + want bool + }{ + "vsock no such device": { + output: "dial vsock host(2):818: connect: no such device", + want: true, + }, + "dev vsock missing": { + output: "open /dev/vsock: no such file or directory", + want: true, + }, + "listen vsock missing": { + output: "listening on VSOCK port 818: no such device", + want: true, + }, + "podman device not found": { + output: "error adding device /dev/vsock: not found", + want: true, + }, + "non-vsock no such device": { + output: "open /dev/does-not-exist: no such device", + want: false, + }, + "other vsock error is retryable": { + output: "dial vsock host(2):818: connection refused", + want: false, + }, + "unrelated enoent in journal dump": { + output: "listening on VSOCK port 818\nopen /etc/foo: no such file or directory", + want: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, isVsockUnavailableOutput(tc.output)) + }) + } +} diff --git a/tests/vmhelpers/roxagent_test.go b/tests/vmhelpers/roxagent_test.go deleted file mode 100644 index f981429845384..0000000000000 --- a/tests/vmhelpers/roxagent_test.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build test && !test_e2e && !test_e2e_vm - -package vmhelpers - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsVsockUnavailableOutput(t *testing.T) { - t.Parallel() - tests := map[string]struct { - output string - want bool - }{ - "vsock no such device": { - output: "dial vsock host(2):818: connect: no such device", - want: true, - }, - "dev vsock missing": { - output: "open /dev/vsock: no such file or directory", - want: true, - }, - "listen vsock missing": { - output: "listening on VSOCK port 818: no such device", - want: true, - }, - "non-vsock no such device": { - output: "open /dev/does-not-exist: no such device", - want: false, - }, - "other vsock error is retryable": { - output: "dial vsock host(2):818: connection refused", - want: false, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - require.Equal(t, tc.want, isVsockUnavailableOutput(tc.output)) - }) - } -} diff --git a/tests/vmhelpers/vm_scan_config.go b/tests/vmhelpers/vm_scan_config.go index 82d09d7f9bc6f..2655f6681646d 100644 --- a/tests/vmhelpers/vm_scan_config.go +++ b/tests/vmhelpers/vm_scan_config.go @@ -51,7 +51,7 @@ type VMScanConfig struct { Images []string // container-disk images (from VM_IMAGES, comma-separated) GuestUsers []string // per-image SSH users (from VM_USERS, comma-separated; shorter lists are padded with defaultGuestUser) VirtctlPath string - RoxagentBinaryPath string + RoxagentImage string Repo2CPEURL string SSHPrivateKey string // PEM-encoded private key content (not a file path) SSHPublicKey string // OpenSSH authorized_keys line (not a file path) @@ -60,7 +60,8 @@ type VMScanConfig struct { ScanPollInterval time.Duration DeleteTimeout time.Duration SkipCleanup bool - ImagePullSecretPath string // Path to docker config JSON for private registries + ImagePullSecretPath string // Path to docker config JSON for the namespace imagePullSecret + PodmanAuthFilePath string // Path to containers-auth JSON for guest Quadlet pulls } // LoadVMScanConfig reads the VM scanning configuration from the environment. @@ -91,7 +92,7 @@ func LoadVMScanConfig() (*VMScanConfig, error) { if cfg.VirtctlPath, err = DiscoverVirtctlPath(); err != nil { return nil, err } - if cfg.RoxagentBinaryPath, err = discoverRoxagentBinaryPath(); err != nil { + if cfg.RoxagentImage, err = discoverRoxagentImage(); err != nil { return nil, err } @@ -123,6 +124,7 @@ func LoadVMScanConfig() (*VMScanConfig, error) { cfg.DeleteTimeout = defaultDeleteTimeout cfg.SkipCleanup = vmScanSkipCleanup.BooleanSetting() cfg.ImagePullSecretPath = strings.TrimSpace(os.Getenv("VM_IMAGE_PULL_SECRET_PATH")) + cfg.PodmanAuthFilePath = strings.TrimSpace(os.Getenv("VM_PODMAN_AUTH_FILE")) return cfg, nil } @@ -164,18 +166,36 @@ func DiscoverVirtctlPath() (string, error) { return p, nil } -// discoverRoxagentBinaryPath returns the ROXAGENT_BINARY_PATH env var if set, -// otherwise probes the standard build output path relative to the repository root. -func discoverRoxagentBinaryPath() (string, error) { - if v := strings.TrimSpace(os.Getenv("ROXAGENT_BINARY_PATH")); v != "" { +// discoverRoxagentImage returns ROXAGENT_IMAGE, else MAIN_IMAGE, else +// :MAIN_IMAGE_TAG. Repo is MAIN_IMAGE_REPO, else DEFAULT_IMAGE_REGISTRY/main, +// else the branding default, so CI can pass only MAIN_IMAGE_TAG. +func discoverRoxagentImage() (string, error) { + if v := strings.TrimSpace(os.Getenv("ROXAGENT_IMAGE")); v != "" { return v, nil } - root := repoRoot() - candidate := filepath.Join(root, "bin", "linux_amd64", "roxagent") - if _, err := os.Stat(candidate); err == nil { - return candidate, nil + if v := strings.TrimSpace(os.Getenv("MAIN_IMAGE")); v != "" { + return v, nil + } + tag := strings.TrimSpace(os.Getenv("MAIN_IMAGE_TAG")) + if tag == "" { + return "", errors.New("ROXAGENT_IMAGE or MAIN_IMAGE is required (or MAIN_IMAGE_TAG)") + } + return defaultMainImageRepo() + ":" + tag, nil +} + +// defaultMainImageRepo matches make/env.mk: RHACS_BRANDING uses quay.io/rhacs-eng, +// otherwise quay.io/stackrox-io. +func defaultMainImageRepo() string { + if v := strings.TrimSpace(os.Getenv("MAIN_IMAGE_REPO")); v != "" { + return v + } + if v := strings.TrimSpace(os.Getenv("DEFAULT_IMAGE_REGISTRY")); v != "" { + return v + "/main" + } + if os.Getenv("ROX_PRODUCT_BRANDING") == "RHACS_BRANDING" { + return "quay.io/rhacs-eng/main" } - return "", fmt.Errorf("ROXAGENT_BINARY_PATH not set and %s does not exist; run 'make roxagent_linux-amd64'", candidate) + return "quay.io/stackrox-io/main" } // repoRoot returns the repository root by walking up from this source file. diff --git a/tests/vmhelpers/vm_scan_config_test.go b/tests/vmhelpers/vm_scan_config_test.go index 2aac47fc226b6..32cbee8d8c83e 100644 --- a/tests/vmhelpers/vm_scan_config_test.go +++ b/tests/vmhelpers/vm_scan_config_test.go @@ -32,7 +32,8 @@ func TestLoadVMScanConfig_Defaults(t *testing.T) { t.Setenv("VM_IMAGES", "registry.example.com/rhel9:latest,registry.example.com/rhel10:latest") t.Setenv("VM_USERS", "") t.Setenv("VIRTCTL_PATH", mustFindExecutable(t, "true")) - t.Setenv("ROXAGENT_BINARY_PATH", "/bin/true") + t.Setenv("ROXAGENT_IMAGE", "") + t.Setenv("MAIN_IMAGE", "quay.io/example/main:test") t.Setenv("VM_SCAN_NAMESPACE_PREFIX", "") cfg, err := LoadVMScanConfig() require.NoError(t, err) @@ -42,6 +43,7 @@ func TestLoadVMScanConfig_Defaults(t *testing.T) { require.Equal(t, 20*time.Minute, cfg.ScanTimeout) require.Equal(t, 10*time.Second, cfg.ScanPollInterval) require.Equal(t, 5*time.Minute, cfg.DeleteTimeout) + require.Equal(t, "quay.io/example/main:test", cfg.RoxagentImage) specs := cfg.VMSpecs() require.Len(t, specs, 2) @@ -49,11 +51,23 @@ func TestLoadVMScanConfig_Defaults(t *testing.T) { require.Equal(t, "vm-1", specs[1].Name) } +func TestLoadVMScanConfig_AuthPaths(t *testing.T) { + t.Setenv("VM_IMAGES", "registry.example.com/rhel9:latest") + t.Setenv("VIRTCTL_PATH", mustFindExecutable(t, "true")) + t.Setenv("MAIN_IMAGE", "quay.io/example/main:test") + t.Setenv("VM_IMAGE_PULL_SECRET_PATH", "/tmp/k8s-pull-secret.json") + t.Setenv("VM_PODMAN_AUTH_FILE", "/tmp/podman-auth.json") + cfg, err := LoadVMScanConfig() + require.NoError(t, err) + require.Equal(t, "/tmp/k8s-pull-secret.json", cfg.ImagePullSecretPath) + require.Equal(t, "/tmp/podman-auth.json", cfg.PodmanAuthFilePath) +} + func TestLoadVMScanConfig_PartialUsers(t *testing.T) { t.Setenv("VM_IMAGES", "img-a,img-b,img-c") t.Setenv("VM_USERS", "alice") t.Setenv("VIRTCTL_PATH", mustFindExecutable(t, "true")) - t.Setenv("ROXAGENT_BINARY_PATH", "/bin/true") + t.Setenv("MAIN_IMAGE", "quay.io/example/main:test") cfg, err := LoadVMScanConfig() require.NoError(t, err) require.Equal(t, []string{"alice"}, cfg.GuestUsers, "only explicit users; VMSpecs() pads with default") @@ -62,7 +76,7 @@ func TestLoadVMScanConfig_PartialUsers(t *testing.T) { func TestLoadVMScanConfig_InvalidSSHKeyContent(t *testing.T) { t.Setenv("VM_IMAGES", "registry.example.com/rhel9:latest") t.Setenv("VIRTCTL_PATH", mustFindExecutable(t, "true")) - t.Setenv("ROXAGENT_BINARY_PATH", "/bin/true") + t.Setenv("MAIN_IMAGE", "quay.io/example/main:test") tests := map[string]string{ "should reject a file path": "/home/user/.ssh/id_ed25519", @@ -116,6 +130,70 @@ func TestGenerateEphemeralSSHKeypair(t *testing.T) { require.Contains(t, pub, "ssh-ed25519 ") // notsecret } +func TestDiscoverRoxagentImage(t *testing.T) { + tests := map[string]struct { + roxagentImage string + mainImage string + repo string + tag string + branding string + registry string + want string + errSubstring string + }{ + "should prefer ROXAGENT_IMAGE over MAIN_IMAGE": { + roxagentImage: "quay.io/custom/main:dev", + mainImage: "quay.io/example/main:ignored", + want: "quay.io/custom/main:dev", + }, + "should use MAIN_IMAGE": { + mainImage: "quay.io/example/main:test", + want: "quay.io/example/main:test", + }, + "should synthesize MAIN_IMAGE_REPO and MAIN_IMAGE_TAG": { + repo: "quay.io/rhacs-eng/main", + tag: "4.12.x-123", + want: "quay.io/rhacs-eng/main:4.12.x-123", + }, + "should default rhacs repo from MAIN_IMAGE_TAG": { + tag: "4.12.x-784-g58e138e64c", + branding: "RHACS_BRANDING", + want: "quay.io/rhacs-eng/main:4.12.x-784-g58e138e64c", + }, + "should default opensource repo from MAIN_IMAGE_TAG": { + tag: "4.12.x-1", + want: "quay.io/stackrox-io/main:4.12.x-1", + }, + "should prefer DEFAULT_IMAGE_REGISTRY over branding": { + tag: "4.12.x-1", + branding: "RHACS_BRANDING", + registry: "quay.io/example", + want: "quay.io/example/main:4.12.x-1", + }, + "should reject missing image configuration": { + errSubstring: "ROXAGENT_IMAGE or MAIN_IMAGE", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Setenv("ROXAGENT_IMAGE", tc.roxagentImage) + t.Setenv("MAIN_IMAGE", tc.mainImage) + t.Setenv("MAIN_IMAGE_REPO", tc.repo) + t.Setenv("MAIN_IMAGE_TAG", tc.tag) + t.Setenv("ROX_PRODUCT_BRANDING", tc.branding) + t.Setenv("DEFAULT_IMAGE_REGISTRY", tc.registry) + got, err := discoverRoxagentImage() + if tc.errSubstring != "" { + require.ErrorContains(t, err, tc.errSubstring) + require.Empty(t, got) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + func TestRepoRootFrom_VMHelpersFile(t *testing.T) { t.Parallel() From 3864713363476b7e322632d820024fdcae2690c3 Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:22:50 +0200 Subject: [PATCH 064/638] ROX-33973: Convert ACM UserPermissions data to SimpleAccessScope objs (#22242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann Co-authored-by: Claude Opus 4.6 (1M context) --- .../access_scope_builder.go | 63 +++ .../access_scope_builder_test.go | 362 ++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 pkg/sac/externalrolebroker/access_scope_builder.go create mode 100644 pkg/sac/externalrolebroker/access_scope_builder_test.go diff --git a/pkg/sac/externalrolebroker/access_scope_builder.go b/pkg/sac/externalrolebroker/access_scope_builder.go new file mode 100644 index 0000000000000..288c4af19658b --- /dev/null +++ b/pkg/sac/externalrolebroker/access_scope_builder.go @@ -0,0 +1,63 @@ +package externalrolebroker + +import ( + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/uuid" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" +) + +// ConvertBindingsToSimpleAccessScope converts ClusterBinding entries from +// an ACM UserPermission object to a *storage.SimpleAccessScope +// +// The function creates: +// - Entries in IncludedClusters for bindings with cluster scope +// - Entries in IncludedNamespaces for bindings with namespace scope +// +// Note: In ACM, the Cluster field in ClusterBinding contains the managed +// cluster name, which corresponds to a namespace name on the hub cluster. +// This is mapped to ClusterName in the SimpleAccessScope structure. +// +// The returned SimpleAccessScope has a generated ID and an empty name/description. +// Duplicate namespaces within the same cluster are deduplicated. +func ConvertBindingsToSimpleAccessScope( + bindings []clusterviewv1alpha1.ClusterBinding, +) *storage.SimpleAccessScope { + clusterSet := set.NewStringSet() + namespaceSet := set.NewStringSet() + var namespaces []*storage.SimpleAccessScope_Rules_Namespace + + for _, binding := range bindings { + switch binding.Scope { + case clusterviewv1alpha1.BindingScopeCluster: + // Cluster-scoped binding: add to IncludedClusters + clusterSet.Add(binding.Cluster) + + case clusterviewv1alpha1.BindingScopeNamespace: + // Namespace-scoped binding: create entries in IncludedNamespaces + for _, ns := range binding.Namespaces { + // Convert wildcard namespace to full cluster access + if ns == "*" { + clusterSet.Add(binding.Cluster) + break + } + + key := binding.Cluster + ":" + ns + if namespaceSet.Add(key) { + namespaces = append(namespaces, &storage.SimpleAccessScope_Rules_Namespace{ + ClusterName: binding.Cluster, + NamespaceName: ns, + }) + } + } + } + } + + return &storage.SimpleAccessScope{ + Id: uuid.NewV4().String(), + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: clusterSet.AsSlice(), + IncludedNamespaces: namespaces, + }, + } +} diff --git a/pkg/sac/externalrolebroker/access_scope_builder_test.go b/pkg/sac/externalrolebroker/access_scope_builder_test.go new file mode 100644 index 0000000000000..3b94203c29654 --- /dev/null +++ b/pkg/sac/externalrolebroker/access_scope_builder_test.go @@ -0,0 +1,362 @@ +package externalrolebroker + +import ( + "testing" + + "github.com/stackrox/rox/pkg/set" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConvertBindingsToSimpleAccessScope(t *testing.T) { + tests := map[string]struct { + bindings []clusterviewv1alpha1.ClusterBinding + expectedClusters []string + expectedNamespaceCount int + checkNamespaces func(t *testing.T, namespaces []string) + }{ + "empty bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{}, + expectedClusters: []string{}, + expectedNamespaceCount: 0, + }, + "single cluster-scoped binding": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"cluster-a"}, + expectedNamespaceCount: 0, + }, + "multiple cluster-scoped bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster-b", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster-c", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"cluster-a", "cluster-b", "cluster-c"}, + expectedNamespaceCount: 0, + }, + "duplicate cluster-scoped bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"cluster-a"}, + expectedNamespaceCount: 0, + }, + "duplicate namespace-scoped bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system"}, + }, + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "monitoring"}, + }, + }, + expectedClusters: []string{}, + expectedNamespaceCount: 3, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-a:default") + assert.Contains(t, namespaces, "cluster-a:kube-system") + assert.Contains(t, namespaces, "cluster-a:monitoring") + }, + }, + "single namespace-scoped binding": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default"}, + }, + }, + expectedClusters: []string{}, + expectedNamespaceCount: 1, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-a:default") + }, + }, + "namespace-scoped binding with multiple namespaces": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system", "app-namespace"}, + }, + }, + expectedClusters: []string{}, + expectedNamespaceCount: 3, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-a:default") + assert.Contains(t, namespaces, "cluster-a:kube-system") + assert.Contains(t, namespaces, "cluster-a:app-namespace") + }, + }, + "multiple namespace-scoped bindings on different clusters": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "app-ns"}, + }, + { + Cluster: "cluster-b", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"production", "staging"}, + }, + }, + expectedClusters: []string{}, + expectedNamespaceCount: 4, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-a:default") + assert.Contains(t, namespaces, "cluster-a:app-ns") + assert.Contains(t, namespaces, "cluster-b:production") + assert.Contains(t, namespaces, "cluster-b:staging") + }, + }, + "mixed cluster and namespace scoped bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster-b", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system"}, + }, + { + Cluster: "cluster-c", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"cluster-a", "cluster-c"}, + expectedNamespaceCount: 2, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-b:default") + assert.Contains(t, namespaces, "cluster-b:kube-system") + }, + }, + "namespace-scoped binding with wildcard namespace (should be converted to full cluster access)": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"cluster-a"}, + expectedNamespaceCount: 0, + }, + "namespace-scoped binding with mix of regular and wildcard namespaces": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "*", "kube-system"}, + }, + }, + expectedClusters: []string{"cluster-a"}, + expectedNamespaceCount: 1, // Only default and kube-system, wildcard is skipped + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "cluster-a:default") + assert.NotContains(t, namespaces, "cluster-a:kube-system") + assert.NotContains(t, namespaces, "cluster-a:*") + }, + }, + "complex scenario with multiple bindings": { + bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "prod-cluster", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "dev-cluster", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"team-a", "team-b"}, + }, + { + Cluster: "staging-cluster", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"preview"}, + }, + { + Cluster: "test-cluster", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + expectedClusters: []string{"prod-cluster", "test-cluster"}, + expectedNamespaceCount: 3, + checkNamespaces: func(t *testing.T, namespaces []string) { + assert.Contains(t, namespaces, "dev-cluster:team-a") + assert.Contains(t, namespaces, "dev-cluster:team-b") + assert.Contains(t, namespaces, "staging-cluster:preview") + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + scope := ConvertBindingsToSimpleAccessScope(tc.bindings) + + require.NotNil(t, scope) + assert.NotEmpty(t, scope.GetId(), "SimpleAccessScope should have a generated ID") + require.NotNil(t, scope.GetRules()) + + // Verify included clusters + actualClusters := set.NewStringSet(scope.GetRules().GetIncludedClusters()...) + expectedClusters := set.NewStringSet(tc.expectedClusters...) + assert.Equal(t, expectedClusters, actualClusters, + "Expected clusters %v, got %v", tc.expectedClusters, scope.GetRules().GetIncludedClusters()) + + // Verify namespace count + assert.Len(t, scope.GetRules().GetIncludedNamespaces(), tc.expectedNamespaceCount, + "Expected %d namespaces, got %d", tc.expectedNamespaceCount, len(scope.GetRules().GetIncludedNamespaces())) + + // Additional namespace checks if provided + if tc.checkNamespaces != nil && len(scope.GetRules().GetIncludedNamespaces()) > 0 { + // Convert to cluster:namespace format for easier checking + namespaceStrs := make([]string, len(scope.GetRules().GetIncludedNamespaces())) + for i, ns := range scope.GetRules().GetIncludedNamespaces() { + namespaceStrs[i] = ns.GetClusterName() + ":" + ns.GetNamespaceName() + } + tc.checkNamespaces(t, namespaceStrs) + } + }) + } +} + +func TestConvertBindingsToSimpleAccessScope_NamespaceStructure(t *testing.T) { + bindings := []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "my-cluster", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system"}, + }, + } + + scope := ConvertBindingsToSimpleAccessScope(bindings) + + require.NotNil(t, scope) + require.NotNil(t, scope.GetRules()) + require.Len(t, scope.GetRules().GetIncludedNamespaces(), 2) + + // Verify the structure of namespace entries + for _, ns := range scope.GetRules().GetIncludedNamespaces() { + assert.Equal(t, "my-cluster", ns.GetClusterName(), "ClusterName should be set") + assert.NotEmpty(t, ns.GetNamespaceName(), "NamespaceName should be set") + assert.Contains(t, []string{"default", "kube-system"}, ns.GetNamespaceName()) + assert.Empty(t, ns.GetClusterId(), "ClusterId should not be set (we only use ClusterName)") + } +} + +func TestConvertBindingsToSimpleAccessScope_EmptyNamespacesList(t *testing.T) { + bindings := []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{}, // Empty namespace list + }, + } + + scope := ConvertBindingsToSimpleAccessScope(bindings) + + require.NotNil(t, scope) + require.NotNil(t, scope.GetRules()) + assert.Empty(t, scope.GetRules().GetIncludedNamespaces(), "Should have no namespace entries for empty namespace list") +} + +func TestConvertBindingsToSimpleAccessScope_OnlyClusterScoped(t *testing.T) { + bindings := []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-1", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster-2", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + } + + scope := ConvertBindingsToSimpleAccessScope(bindings) + + require.NotNil(t, scope) + require.NotNil(t, scope.GetRules()) + + // Should have clusters but no namespaces + assert.Len(t, scope.GetRules().GetIncludedClusters(), 2) + assert.Empty(t, scope.GetRules().GetIncludedNamespaces()) + + clusterSet := set.NewStringSet(scope.GetRules().GetIncludedClusters()...) + assert.True(t, clusterSet.Contains("cluster-1")) + assert.True(t, clusterSet.Contains("cluster-2")) +} + +func TestConvertBindingsToSimpleAccessScope_OnlyNamespaceScoped(t *testing.T) { + bindings := []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-1", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"ns-1", "ns-2"}, + }, + { + Cluster: "cluster-2", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"ns-3"}, + }, + } + + scope := ConvertBindingsToSimpleAccessScope(bindings) + + require.NotNil(t, scope) + require.NotNil(t, scope.GetRules()) + + // Should have namespaces but no cluster-wide access + assert.Empty(t, scope.GetRules().GetIncludedClusters()) + assert.Len(t, scope.GetRules().GetIncludedNamespaces(), 3) + + // Verify all namespaces are present + nsMap := make(map[string]bool) + for _, ns := range scope.GetRules().GetIncludedNamespaces() { + key := ns.GetClusterName() + ":" + ns.GetNamespaceName() + nsMap[key] = true + } + + assert.True(t, nsMap["cluster-1:ns-1"]) + assert.True(t, nsMap["cluster-1:ns-2"]) + assert.True(t, nsMap["cluster-2:ns-3"]) +} From dbf86ff0a2efd79aaee02cb834c9069ae17528c3 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Tue, 25 Aug 2026 16:11:30 +0000 Subject: [PATCH 065/638] ROX-36543: remove release test (#22449) Signed-off-by: Tomasz Janiszewski --- pkg/process/filter/filter_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/process/filter/filter_test.go b/pkg/process/filter/filter_test.go index df6bf3d8a2bec..3100b065e3c82 100644 --- a/pkg/process/filter/filter_test.go +++ b/pkg/process/filter/filter_test.go @@ -21,14 +21,6 @@ func TestBasicFilterFunctions(t *testing.T) { assert.True(t, filter.Add(pi)) } -func TestNewFilterNegativeFanOut(t *testing.T) { - // A negative fanOut must be caught at the NewFilter boundary rather than - // silently wrapping through uint8 to 255. utils.Should panics on non-release builds. - assert.Panics(t, func() { - NewFilter(2, 5, []int{-1}) - }) -} - func TestBasicFilter(t *testing.T) { cases := []struct { name string From c924eebb26ae1192b08c9c09e31aa117fdc5e97f Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:24:07 +0200 Subject: [PATCH 066/638] ROX-35685: add roxagent repo-to-CPE mapping updaters (#22141) Co-authored-by: Cursor --- .../roxagent/vsockserver/mapping.go | 31 ++ .../roxagent/vsockserver/sensor_updater.go | 221 +++++++++++++ .../vsockserver/sensor_updater_test.go | 312 ++++++++++++++++++ .../roxagent/vsockserver/url_updater.go | 174 ++++++++++ .../roxagent/vsockserver/url_updater_test.go | 223 +++++++++++++ pkg/filedownloader/filedownloader.go | 7 +- pkg/filedownloader/filedownloader_test.go | 4 +- 7 files changed, 968 insertions(+), 4 deletions(-) create mode 100644 compliance/virtualmachines/roxagent/vsockserver/mapping.go create mode 100644 compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go create mode 100644 compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go create mode 100644 compliance/virtualmachines/roxagent/vsockserver/url_updater.go create mode 100644 compliance/virtualmachines/roxagent/vsockserver/url_updater_test.go diff --git a/compliance/virtualmachines/roxagent/vsockserver/mapping.go b/compliance/virtualmachines/roxagent/vsockserver/mapping.go new file mode 100644 index 0000000000000..24226356d349d --- /dev/null +++ b/compliance/virtualmachines/roxagent/vsockserver/mapping.go @@ -0,0 +1,31 @@ +package vsockserver + +import ( + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" +) + +// MappingProvider supplies the current repository-to-CPE mapping regardless +// of which updater (Sensor-backed or URL-backed) owns it in this process. +type MappingProvider interface { + Ready() bool + Hash() string + UpdatePath() pb.RepoCPEMappingUpdatePath + Bytes() ([]byte, error) + // Path returns a file matching Bytes(). It may write that file if a + // background persist has not landed yet. + Path() (string, error) +} + +// MappingUpdater accepts new mapping content pushed in over VSOCK. Only the +// Sensor-backed updater implements this; a URL-backed agent's Handler keeps +// its updater reference nil and rejects SyncRepoCPEMapping instead. +type MappingUpdater interface { + Update(content []byte) (updated bool, err error) +} + +// ScanBusyGate is implemented by the Sensor updater so rescanner/Handler +// can defer active applies across an in-flight scan + GetReport send. +type ScanBusyGate interface { + MarkScanBusy() + MarkScanIdleAndApplyPending() +} diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go new file mode 100644 index 0000000000000..9e81e9afa5a2f --- /dev/null +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go @@ -0,0 +1,221 @@ +package vsockserver + +import ( + "bytes" + "errors" + "os" + + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/filedownloader" + "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" +) + +var ( + _ MappingProvider = (*SensorUpdater)(nil) + _ MappingUpdater = (*SensorUpdater)(nil) + _ ScanBusyGate = (*SensorUpdater)(nil) +) + +var errSensorMappingNotReady = errors.New("no repo-to-CPE mapping available yet") + +// SensorUpdater applies repo-to-CPE mappings pushed in over VSOCK via +// SyncRepoCPEMapping, deferring the apply while busy is set so an +// in-flight scan's GetReport response never sees a mid-flight change. +type SensorUpdater struct { + active []byte + activeHash string + cachePath string + pending []byte + busy bool + onChange func() + mu sync.Mutex + // persistMu serializes cachePath writes. persistActive is the only + // acquirer; it takes persistMu, then mu, never the reverse. + persistMu sync.Mutex +} + +// NewSensorUpdater seeds active from cachePath, else bundledPath, else +// stays empty until the first Update. Construct onChange's dependents +// (e.g. the rescanner) first, since it may fire during this call. +func NewSensorUpdater(cachePath, bundledPath string, onChange func()) *SensorUpdater { + u := &SensorUpdater{cachePath: cachePath, onChange: onChange} + if u.bootstrapFrom(cachePath) || u.bootstrapFrom(bundledPath) { + if onChange != nil { + onChange() + } + } + return u +} + +// bootstrapFrom seeds active/activeHash from path if it decodes as a valid +// mapping. A no-op for an empty path, so the optional bundled seed can be +// skipped by passing "". Reports whether it seeded active. +func (u *SensorUpdater) bootstrapFrom(path string) bool { + if path == "" { + return false + } + content, err := os.ReadFile(path) + if err != nil { + // First boot always misses cache; do not treat that as an error. + if !errors.Is(err, os.ErrNotExist) { + log.Warnf("Reading repo-to-CPE mapping at %q: %v", path, err) + } + return false + } + if err := cpemapping.ValidateMapping(content); err != nil { + log.Warnf("Ignoring invalid repo-to-CPE mapping at %q: %v", path, err) + return false + } + u.active = content + u.activeHash = cpemapping.HashMapping(content) + return true +} + +// Ready reports whether a validated mapping is currently active. +func (u *SensorUpdater) Ready() bool { + u.mu.Lock() + defer u.mu.Unlock() + return len(u.active) > 0 +} + +// Hash returns the active mapping's content hash, or "" if not Ready. +func (u *SensorUpdater) Hash() string { + u.mu.Lock() + defer u.mu.Unlock() + return u.activeHash +} + +// UpdatePath reports this updater's mapping source for ResponseMeta. +func (u *SensorUpdater) UpdatePath() pb.RepoCPEMappingUpdatePath { + return pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR +} + +// Bytes returns a copy of the active mapping content. +func (u *SensorUpdater) Bytes() ([]byte, error) { + u.mu.Lock() + defer u.mu.Unlock() + if len(u.active) == 0 { + return nil, errSensorMappingNotReady + } + out := make([]byte, len(u.active)) + copy(out, u.active) + return out, nil +} + +// Path returns cachePath after (re-)writing it with the active mapping, so +// callers always get a file whose content matches Bytes() even if the +// fire-and-forget persistence from the last apply has not landed yet. +func (u *SensorUpdater) Path() (string, error) { + return u.persistActive() +} + +// Update applies content to active immediately, or stages it as pending +// for MarkScanIdleAndApplyPending to promote later if a scan is in +// flight (busy), so it never mutates active out from under that scan. +func (u *SensorUpdater) Update(content []byte) (updated bool, err error) { + if err := cpemapping.ValidateMapping(content); err != nil { + return false, err + } + // MappingUpdater retains content in active/pending and in the async + // cache write; clone so a caller reusing the buffer cannot desync + // those from activeHash. + content = bytes.Clone(content) + hash := cpemapping.HashMapping(content) + + updated, deferred := concurrency.WithLock2(&u.mu, func() (bool, bool) { + if hash == u.activeHash { + // The newest push matches active, so it supersedes whatever + // an earlier, now-stale push staged as pending. + u.pending = nil + return false, false + } + if bytes.Equal(content, u.pending) { + return false, false + } + if u.busy { + u.pending = content + return true, true + } + u.applyLocked(content, hash) + return true, false + }) + if !updated { + return false, nil + } + if deferred { + log.Infof("SyncRepoCPEMapping: deferring apply of mapping (hash=%s) until the in-flight scan finishes", hash) + return true, nil + } + u.persistAndNotify() + return true, nil +} + +// MarkScanBusy marks a VM-disk scan in progress, so a concurrent Update +// stages new content as pending instead of mutating active underneath it. +func (u *SensorUpdater) MarkScanBusy() { + u.mu.Lock() + defer u.mu.Unlock() + u.busy = true +} + +// MarkScanIdleAndApplyPending clears busy and, if Update staged content +// while busy, promotes it to active now that the scan (and its GetReport +// response) has finished. +func (u *SensorUpdater) MarkScanIdleAndApplyPending() { + pending := concurrency.WithLock1(&u.mu, func() []byte { + u.busy = false + pending := u.pending + if pending == nil { + return nil + } + u.pending = nil + u.applyLocked(pending, cpemapping.HashMapping(pending)) + return pending + }) + if pending != nil { + u.persistAndNotify() + } +} + +// applyLocked sets active/activeHash to content/hash. Caller must hold mu. +func (u *SensorUpdater) applyLocked(content []byte, hash string) { + u.active = content + u.activeHash = hash +} + +// persistAndNotify fires onChange now and persists cachePath in the +// background. Path is the write barrier if the file is needed before +// that goroutine finishes. +func (u *SensorUpdater) persistAndNotify() { + go func() { + if _, err := u.persistActive(); err != nil { + log.Warnf("Persisting repo-to-CPE mapping cache to %q: %v", u.cachePath, err) + } + }() + if u.onChange != nil { + u.onChange() + } +} + +// persistActive writes a copy of the current active mapping to cachePath. +// It takes persistMu, then mu, so a late persist cannot overwrite the +// file with a superseded mapping. +func (u *SensorUpdater) persistActive() (string, error) { + return concurrency.WithLock2(&u.persistMu, func() (string, error) { + active, cachePath := concurrency.WithLock2(&u.mu, func() ([]byte, string) { + if len(u.active) == 0 { + return nil, u.cachePath + } + return bytes.Clone(u.active), u.cachePath + }) + if len(active) == 0 { + return "", errSensorMappingNotReady + } + if err := filedownloader.AtomicWriteFile(cachePath, active); err != nil { + return "", err + } + return cachePath, nil + }) +} diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go new file mode 100644 index 0000000000000..aa77bcae032c2 --- /dev/null +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go @@ -0,0 +1,312 @@ +package vsockserver + +import ( + "os" + "path/filepath" + "testing" + "time" + + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const validMappingJSON = `{"data":{"rhel-8-server-rpms":{"cpes":["cpe:/o:redhat:enterprise_linux:8"]}}}` +const otherValidMappingJSON = `{"data":{"rhel-9-server-rpms":{"cpes":["cpe:/o:redhat:enterprise_linux:9"]}}}` +const invalidMappingJSON = `{not json` + +// waitTimeout/waitTick bound assert.Eventually polling for the +// fire-and-forget cache-persistence goroutine shared by both updaters' tests. +const ( + waitTimeout = 2 * time.Second + waitTick = 10 * time.Millisecond +) + +// onChangeCounter is a test double for a MappingProvider's onChange +// callback that records how many times it fired. +type onChangeCounter struct { + count int +} + +func (c *onChangeCounter) fn() { c.count++ } + +func writeFile(t *testing.T, path, content string) { + t.Helper() + require.NoError(t, os.WriteFile(path, []byte(content), 0600)) +} + +// waitForCacheContent polls cachePath for the persist goroutine an applied +// Update starts in the background, so tests can drain it deterministically +// instead of letting it race the tempdir's end-of-test cleanup. +func waitForCacheContent(t *testing.T, cachePath, want string) { + t.Helper() + assert.Eventually(t, func() bool { + b, err := os.ReadFile(cachePath) + return err == nil && string(b) == want + }, waitTimeout, waitTick, "expected %q to be persisted to %s", want, cachePath) +} + +func TestNewSensorUpdater_EmptyStart_NotReady(t *testing.T) { + dir := t.TempDir() + counter := &onChangeCounter{} + + u := NewSensorUpdater(filepath.Join(dir, "cache.json"), "", counter.fn) + + assert.False(t, u.Ready()) + assert.Empty(t, u.Hash()) + assert.Equal(t, 0, counter.count) + + _, err := u.Bytes() + assert.Error(t, err) + _, err = u.Path() + assert.Error(t, err) +} + +func TestNewSensorUpdater_BootstrapFromCache(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + counter := &onChangeCounter{} + + u := NewSensorUpdater(cachePath, "", counter.fn) + + require.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b)) + assert.Equal(t, 1, counter.count, "onChange should fire once for a valid bootstrap") +} + +func TestNewSensorUpdater_BootstrapFromBundled_WhenNoCache(t *testing.T) { + dir := t.TempDir() + bundledPath := filepath.Join(dir, "bundled.json") + writeFile(t, bundledPath, validMappingJSON) + counter := &onChangeCounter{} + + u := NewSensorUpdater(filepath.Join(dir, "cache.json"), bundledPath, counter.fn) + + require.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + assert.Equal(t, 1, counter.count) +} + +func TestNewSensorUpdater_CacheTakesPrecedenceOverBundled(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + bundledPath := filepath.Join(dir, "bundled.json") + writeFile(t, cachePath, validMappingJSON) + writeFile(t, bundledPath, otherValidMappingJSON) + + u := NewSensorUpdater(cachePath, bundledPath, func() {}) + + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b), "a valid cache must win over a valid bundled file") +} + +func TestNewSensorUpdater_InvalidCacheFallsBackToBundled(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + bundledPath := filepath.Join(dir, "bundled.json") + writeFile(t, cachePath, invalidMappingJSON) + writeFile(t, bundledPath, validMappingJSON) + + u := NewSensorUpdater(cachePath, bundledPath, func() {}) + + require.True(t, u.Ready()) + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b)) +} + +func TestNewSensorUpdater_InvalidCacheNoBundled_NotReady(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, invalidMappingJSON) + counter := &onChangeCounter{} + + u := NewSensorUpdater(cachePath, "", counter.fn) + + assert.False(t, u.Ready()) + assert.Equal(t, 0, counter.count) +} + +func TestSensorUpdater_Update_OwnsContent(t *testing.T) { + cachePath := filepath.Join(t.TempDir(), "cache.json") + u := NewSensorUpdater(cachePath, "", func() {}) + content := []byte(validMappingJSON) + + updated, err := u.Update(content) + require.NoError(t, err) + require.True(t, updated) + content[0] ^= 0xff + + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b), "mutating the caller's buffer must not change the active mapping") + waitForCacheContent(t, cachePath, validMappingJSON) +} + +func TestSensorUpdater_Path_WritesCurrentActive(t *testing.T) { + cachePath := filepath.Join(t.TempDir(), "cache.json") + u := NewSensorUpdater(cachePath, "", func() {}) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + _, err = u.Update([]byte(otherValidMappingJSON)) + require.NoError(t, err) + + path, err := u.Path() + require.NoError(t, err) + onDisk, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, otherValidMappingJSON, string(onDisk), + "Path must persist the current active mapping, not an in-flight older write") + waitForCacheContent(t, cachePath, otherValidMappingJSON) +} + +func TestSensorUpdater_Update_AppliesWhenIdle(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + counter := &onChangeCounter{} + u := NewSensorUpdater(cachePath, "", counter.fn) + + updated, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + assert.True(t, updated) + assert.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + assert.Equal(t, 1, counter.count) + waitForCacheContent(t, cachePath, validMappingJSON) +} + +func TestSensorUpdater_Update_NoOpWhenUnchanged(t *testing.T) { + cachePath := filepath.Join(t.TempDir(), "cache.json") + u := NewSensorUpdater(cachePath, "", func() {}) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + waitForCacheContent(t, cachePath, validMappingJSON) + + updated, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + assert.False(t, updated, "re-applying identical content must be a no-op") +} + +func TestSensorUpdater_Update_RejectsInvalidKeepsLastGood(t *testing.T) { + cachePath := filepath.Join(t.TempDir(), "cache.json") + u := NewSensorUpdater(cachePath, "", func() {}) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + wantHash := u.Hash() + waitForCacheContent(t, cachePath, validMappingJSON) + + updated, err := u.Update([]byte(invalidMappingJSON)) + assert.Error(t, err) + assert.False(t, updated) + assert.Equal(t, wantHash, u.Hash(), "invalid content must not replace the last-good mapping") +} + +func TestSensorUpdater_Update_OversizeRejected(t *testing.T) { + u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + oversized := make([]byte, cpemapping.MaxMappingBytes+1) + + updated, err := u.Update(oversized) + assert.Error(t, err) + assert.False(t, updated) + assert.False(t, u.Ready()) +} + +func TestSensorUpdater_UpdateWhileBusy_DefersUntilIdle(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + counter := &onChangeCounter{} + u := NewSensorUpdater(cachePath, "", counter.fn) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + require.Equal(t, 1, counter.count) + idleHash := u.Hash() + idleBytes, err := u.Bytes() + require.NoError(t, err) + + u.MarkScanBusy() + + updated, err := u.Update([]byte(otherValidMappingJSON)) + require.NoError(t, err) + assert.True(t, updated, "a genuinely new candidate is reported as updated even while deferred") + + // While busy, active content (and therefore Hash/Bytes) must not change: + // an in-flight scan may still be reading these values. + assert.Equal(t, idleHash, u.Hash()) + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, idleBytes, b) + assert.Equal(t, 1, counter.count, "onChange must not fire for a deferred apply") + + u.MarkScanIdleAndApplyPending() + + assert.Equal(t, cpemapping.HashMapping([]byte(otherValidMappingJSON)), u.Hash()) + b, err = u.Bytes() + require.NoError(t, err) + assert.Equal(t, otherValidMappingJSON, string(b)) + assert.Equal(t, 2, counter.count, "onChange must fire once the pending mapping is promoted") + waitForCacheContent(t, cachePath, otherValidMappingJSON) +} + +func TestSensorUpdater_UpdateWhileBusy_SameAsPending_NoOp(t *testing.T) { + u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + u.MarkScanBusy() + + updated, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + assert.True(t, updated) + + updated, err = u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + assert.False(t, updated, "re-staging identical pending content must be a no-op") +} + +// TestSensorUpdater_UpdateWhileBusy_RevertToActive_ClearsStalePending covers +// a revert push landing after a newer one was already staged: Sensor pushes +// are ordered, so the newest push (matching active) must win, not the +// earlier, now-stale pending content. +func TestSensorUpdater_UpdateWhileBusy_RevertToActive_ClearsStalePending(t *testing.T) { + u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + u.MarkScanBusy() + + updated, err := u.Update([]byte(otherValidMappingJSON)) + require.NoError(t, err) + require.True(t, updated) + + updated, err = u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + assert.False(t, updated, "reverting to the already-active content is not itself an update") + + u.MarkScanIdleAndApplyPending() + + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash(), + "the newest push (matching active) must win over the stale pending mapping") +} + +func TestSensorUpdater_MarkScanIdleAndApplyPending_NoPending_NoOp(t *testing.T) { + cachePath := filepath.Join(t.TempDir(), "cache.json") + counter := &onChangeCounter{} + u := NewSensorUpdater(cachePath, "", counter.fn) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + require.Equal(t, 1, counter.count) + waitForCacheContent(t, cachePath, validMappingJSON) + + u.MarkScanBusy() + u.MarkScanIdleAndApplyPending() + + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + assert.Equal(t, 1, counter.count, "onChange must not fire when there is nothing pending to apply") +} + +func TestSensorUpdater_UpdatePath_IsSensor(t *testing.T) { + u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, u.UpdatePath()) +} diff --git a/compliance/virtualmachines/roxagent/vsockserver/url_updater.go b/compliance/virtualmachines/roxagent/vsockserver/url_updater.go new file mode 100644 index 0000000000000..23b19dc8a7628 --- /dev/null +++ b/compliance/virtualmachines/roxagent/vsockserver/url_updater.go @@ -0,0 +1,174 @@ +package vsockserver + +import ( + "context" + "errors" + "os" + "time" + + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/filedownloader" + "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" +) + +// Retry/refresh policy for the periodic mapping download, mirroring +// cmd.newMappingDownloader: WaitMin=5s with the default WaitMax=30s covers +// four HTTP attempts (5+10+20=35s) within urlMappingClientTimeout. +const ( + urlMappingFetchRetryMax = 3 + urlMappingFetchRetryWaitMin = 5 * time.Second + urlMappingClientTimeout = 2 * time.Minute + urlMappingRefreshInterval = time.Hour + // urlMappingStagingSuffix names the path the downloader writes to. + // onDownloadComplete only promotes a staged download to cachePath + // once it passes validation, so a bad fetch can never corrupt the + // persisted last-good mapping. + urlMappingStagingSuffix = ".download" +) + +var _ MappingProvider = (*URLUpdater)(nil) + +var errURLMappingNotReady = errors.New("no repo-to-CPE mapping available yet") + +// URLUpdater is the MappingProvider for an agent configured with a +// mapping URL; this mapping source never accepts a Sensor-pushed Update. +type URLUpdater struct { + downloader *filedownloader.Downloader + cachePath string + stagingPath string + active []byte + activeHash string + onChange func() + mu sync.Mutex +} + +// NewURLUpdater seeds active from cachePath (last-good from a prior +// successful download), else stays empty until the first fetch. Bundled +// image content is not used: a never-working URL would look configured. +func NewURLUpdater(url, cachePath string, onChange func()) *URLUpdater { + u := &URLUpdater{cachePath: cachePath, stagingPath: cachePath + urlMappingStagingSuffix, onChange: onChange} + if u.bootstrap() && onChange != nil { + onChange() + } + u.downloader = filedownloader.New(url, u.stagingPath, urlMappingRefreshInterval, + filedownloader.WithRequestTimeout(urlMappingClientTimeout), + filedownloader.WithRetryMax(urlMappingFetchRetryMax), + filedownloader.WithRetryWaitMin(urlMappingFetchRetryWaitMin), + filedownloader.WithOnComplete(u.onDownloadComplete), + ) + return u +} + +// bootstrap seeds active/activeHash from cachePath if it decodes as a +// valid mapping. Reports whether it seeded active. +func (u *URLUpdater) bootstrap() bool { + content, err := os.ReadFile(u.cachePath) + if err != nil { + // First boot always misses cache; do not treat that as an error. + if !errors.Is(err, os.ErrNotExist) { + log.Warnf("Reading repo-to-CPE mapping cache at %q: %v", u.cachePath, err) + } + return false + } + if err := cpemapping.ValidateMapping(content); err != nil { + log.Warnf("Ignoring invalid repo-to-CPE mapping cache at %q: %v", u.cachePath, err) + return false + } + u.active = content + u.activeHash = cpemapping.HashMapping(content) + return true +} + +// Start begins the periodic download loop in the background; agent +// startup never blocks on a successful fetch. +func (u *URLUpdater) Start(ctx context.Context) error { + return u.downloader.Start(ctx, false) +} + +// Stop signals the download loop to stop and blocks until it exits. +func (u *URLUpdater) Stop() { + u.downloader.Stop() +} + +// Ready reports whether a validated mapping is currently active. +func (u *URLUpdater) Ready() bool { + u.mu.Lock() + defer u.mu.Unlock() + return len(u.active) > 0 +} + +// Hash returns the active mapping's content hash, or "" if not Ready. +func (u *URLUpdater) Hash() string { + u.mu.Lock() + defer u.mu.Unlock() + return u.activeHash +} + +// UpdatePath reports this updater's mapping source for ResponseMeta. +func (u *URLUpdater) UpdatePath() pb.RepoCPEMappingUpdatePath { + return pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL +} + +// Bytes returns a copy of the active mapping content. +func (u *URLUpdater) Bytes() ([]byte, error) { + u.mu.Lock() + defer u.mu.Unlock() + if len(u.active) == 0 { + return nil, errURLMappingNotReady + } + out := make([]byte, len(u.active)) + copy(out, u.active) + return out, nil +} + +// Path returns cachePath. onDownloadComplete only sets active after a +// validated download has been persisted there, so the file matches Bytes(). +func (u *URLUpdater) Path() (string, error) { + ready := concurrency.WithLock1(&u.mu, func() bool { + return len(u.active) > 0 + }) + if !ready { + return "", errURLMappingNotReady + } + return u.cachePath, nil +} + +// onDownloadComplete applies a staged download only after it validates +// and persists to cachePath, so Bytes() and Path() stay aligned and a +// failed persist is retried on the next identical fetch. +func (u *URLUpdater) onDownloadComplete(err error, _ time.Duration) { + if err != nil { + log.Warnf("Error downloading repo-to-CPE mapping: %v", err) + return + } + content, err := os.ReadFile(u.stagingPath) + if err != nil { + log.Warnf("Reading downloaded repo-to-CPE mapping at %q: %v", u.stagingPath, err) + return + } + if err := cpemapping.ValidateMapping(content); err != nil { + log.Warnf("Downloaded repo-to-CPE mapping failed validation, keeping last-good: %v", err) + return + } + hash := cpemapping.HashMapping(content) + + alreadyActive := concurrency.WithLock1(&u.mu, func() bool { + return hash == u.activeHash + }) + if alreadyActive { + return + } + if err := filedownloader.AtomicWriteFile(u.cachePath, content); err != nil { + log.Warnf("Persisting repo-to-CPE mapping cache to %q: %v", u.cachePath, err) + return + } + concurrency.WithLock(&u.mu, func() { + u.active = content + u.activeHash = hash + }) + if u.onChange != nil { + u.onChange() + } +} diff --git a/compliance/virtualmachines/roxagent/vsockserver/url_updater_test.go b/compliance/virtualmachines/roxagent/vsockserver/url_updater_test.go new file mode 100644 index 0000000000000..cabcff82cfbbc --- /dev/null +++ b/compliance/virtualmachines/roxagent/vsockserver/url_updater_test.go @@ -0,0 +1,223 @@ +package vsockserver + +import ( + "errors" + "os" + "path/filepath" + "testing" + + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// dummyMappingURL is never dialed in these tests: NewURLUpdater's +// constructor only builds the filedownloader.Downloader, and the actual +// fetch is exercised indirectly via onDownloadComplete. +const dummyMappingURL = "http://127.0.0.1:0/mapping.json" + +func TestNewURLUpdater_BootstrapFromCache(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + counter := &onChangeCounter{} + + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + + require.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b)) + path, err := u.Path() + require.NoError(t, err) + assert.Equal(t, cachePath, path) + assert.Equal(t, 1, counter.count, "onChange should fire once for a valid bootstrap") +} + +func TestNewURLUpdater_NotReadyWithoutCache(t *testing.T) { + dir := t.TempDir() + counter := &onChangeCounter{} + + u := NewURLUpdater(dummyMappingURL, filepath.Join(dir, "cache.json"), counter.fn) + + assert.False(t, u.Ready()) + assert.Empty(t, u.Hash()) + assert.Equal(t, 0, counter.count) + _, err := u.Bytes() + assert.Error(t, err) + _, err = u.Path() + assert.Error(t, err) +} + +func TestNewURLUpdater_InvalidCacheStaysNotReady_NoBundledFallback(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, invalidMappingJSON) + counter := &onChangeCounter{} + + // Cache is last-good from a successful URL fetch. An invalid cache + // must leave the updater not Ready, not seed from anything else. + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + + assert.False(t, u.Ready()) + assert.Equal(t, 0, counter.count) +} + +func TestURLUpdater_UpdatePath_IsURL(t *testing.T) { + u := NewURLUpdater(dummyMappingURL, filepath.Join(t.TempDir(), "cache.json"), func() {}) + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, u.UpdatePath()) +} + +func TestURLUpdater_FailedDownload_StaysNotReady(t *testing.T) { + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, filepath.Join(t.TempDir(), "cache.json"), counter.fn) + + u.onDownloadComplete(errors.New("connection refused"), 0) + + assert.False(t, u.Ready()) + assert.Equal(t, 0, counter.count) +} + +func TestURLUpdater_FailedDownload_KeepsLastGood(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + require.True(t, u.Ready()) + require.Equal(t, 1, counter.count) + + u.onDownloadComplete(errors.New("connection refused"), 0) + + assert.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + assert.Equal(t, 1, counter.count, "onChange must not re-fire when a failed refresh keeps the same content") +} + +func TestURLUpdater_SuccessfulDownload_AppliesNewContent(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + require.False(t, u.Ready()) + + // Simulate what filedownloader's atomic write already did before + // invoking onComplete: the new content is on disk at stagingPath. + writeFile(t, u.stagingPath, validMappingJSON) + u.onDownloadComplete(nil, 0) + + require.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + b, err := u.Bytes() + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(b)) + assert.Equal(t, 1, counter.count) + + onDisk, err := os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(onDisk), "a validated download must be promoted to cachePath") +} + +// TestURLUpdater_PersistFailure_DoesNotActivate covers a validated +// download whose cache promotion fails: active stays unchanged so Path() +// still matches Bytes(), and a later identical fetch retries the persist. +func TestURLUpdater_PersistFailure_DoesNotActivate(t *testing.T) { + dir := t.TempDir() + cacheDir := filepath.Join(dir, "cache") + require.NoError(t, os.Mkdir(cacheDir, 0700)) + cachePath := filepath.Join(cacheDir, "cache.json") + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + writeFile(t, u.stagingPath, validMappingJSON) + + require.NoError(t, os.Chmod(cacheDir, 0500)) + t.Cleanup(func() { _ = os.Chmod(cacheDir, 0700) }) + + u.onDownloadComplete(nil, 0) + + assert.False(t, u.Ready(), "a failed persist must not expose the new mapping as active") + assert.Equal(t, 0, counter.count) + _, err := os.Stat(cachePath) + assert.Error(t, err, "cachePath must not exist after a failed persist") + + require.NoError(t, os.Chmod(cacheDir, 0700)) + u.onDownloadComplete(nil, 0) + + require.True(t, u.Ready(), "the next identical fetch must retry persist and then activate") + assert.Equal(t, 1, counter.count) + onDisk, err := os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(onDisk)) +} + +func TestURLUpdater_PersistFailure_KeepsLastGoodThenRetries(t *testing.T) { + dir := t.TempDir() + cacheDir := filepath.Join(dir, "cache") + require.NoError(t, os.Mkdir(cacheDir, 0700)) + cachePath := filepath.Join(cacheDir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + require.Equal(t, 1, counter.count) + writeFile(t, u.stagingPath, otherValidMappingJSON) + + require.NoError(t, os.Chmod(cacheDir, 0500)) + t.Cleanup(func() { _ = os.Chmod(cacheDir, 0700) }) + + u.onDownloadComplete(nil, 0) + + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash(), + "a failed persist must keep serving the last-good mapping") + assert.Equal(t, 1, counter.count) + onDisk, err := os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(onDisk)) + + require.NoError(t, os.Chmod(cacheDir, 0700)) + u.onDownloadComplete(nil, 0) + + assert.Equal(t, cpemapping.HashMapping([]byte(otherValidMappingJSON)), u.Hash()) + assert.Equal(t, 2, counter.count) + onDisk, err = os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, otherValidMappingJSON, string(onDisk)) +} + +func TestURLUpdater_SuccessfulDownload_InvalidContentKeepsLastGood(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + counter := &onChangeCounter{} + u := NewURLUpdater(dummyMappingURL, cachePath, counter.fn) + require.Equal(t, 1, counter.count) + + writeFile(t, u.stagingPath, invalidMappingJSON) + u.onDownloadComplete(nil, 0) + + assert.True(t, u.Ready()) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) + assert.Equal(t, 1, counter.count, "onChange must not fire when the refreshed content fails validation") +} + +// TestURLUpdater_SuccessfulDownload_InvalidContentDoesNotCorruptCache covers +// what TestURLUpdater_SuccessfulDownload_InvalidContentKeepsLastGood only +// checks in memory: an invalid refresh must leave the persisted cachePath +// itself untouched, since a restart bootstraps from that file, not memory. +func TestURLUpdater_SuccessfulDownload_InvalidContentDoesNotCorruptCache(t *testing.T) { + dir := t.TempDir() + cachePath := filepath.Join(dir, "cache.json") + writeFile(t, cachePath, validMappingJSON) + u := NewURLUpdater(dummyMappingURL, cachePath, func() {}) + + writeFile(t, u.stagingPath, invalidMappingJSON) + u.onDownloadComplete(nil, 0) + + onDisk, err := os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, validMappingJSON, string(onDisk), "an invalid refresh must not overwrite the persisted last-good cache") + + restarted := NewURLUpdater(dummyMappingURL, cachePath, func() {}) + assert.True(t, restarted.Ready(), "a restart must still bootstrap from the untouched cache") +} diff --git a/pkg/filedownloader/filedownloader.go b/pkg/filedownloader/filedownloader.go index 5bce79ea4a66d..1343e736df5de 100644 --- a/pkg/filedownloader/filedownloader.go +++ b/pkg/filedownloader/filedownloader.go @@ -224,7 +224,7 @@ func (d *Downloader) doDownload(ctx context.Context) error { return fmt.Errorf("response body exceeds maximum size of %d bytes", d.maxSize) } - if err := atomicWriteFile(d.filePath, body); err != nil { + if err := AtomicWriteFile(d.filePath, body); err != nil { return fmt.Errorf("writing file: %w", err) } @@ -232,7 +232,10 @@ func (d *Downloader) doDownload(ctx context.Context) error { return nil } -func atomicWriteFile(path string, data []byte) error { +// AtomicWriteFile writes data to path via a temp file plus rename, so +// concurrent readers never observe a partially written file. Creates +// path's parent directory (mode 0700) if it does not already exist. +func AtomicWriteFile(path string, data []byte) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0700); err != nil { return fmt.Errorf("creating directory %q: %w", dir, err) diff --git a/pkg/filedownloader/filedownloader_test.go b/pkg/filedownloader/filedownloader_test.go index ce6a673f3e283..3bff6e5547e6f 100644 --- a/pkg/filedownloader/filedownloader_test.go +++ b/pkg/filedownloader/filedownloader_test.go @@ -268,7 +268,7 @@ func TestAtomicWriteFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "output.json") - require.NoError(t, atomicWriteFile(path, []byte("hello"))) + require.NoError(t, AtomicWriteFile(path, []byte("hello"))) data, err := os.ReadFile(path) require.NoError(t, err) @@ -283,7 +283,7 @@ func TestAtomicWriteFile_CreatesParentDirectory(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "nested", "output.json") - require.NoError(t, atomicWriteFile(path, []byte("hello"))) + require.NoError(t, AtomicWriteFile(path, []byte("hello"))) data, err := os.ReadFile(path) require.NoError(t, err) From 67f4ab5cdb68bcd5e31b300f8225fd7d2d41ff5b Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:21:55 +0200 Subject: [PATCH 067/638] fix(ci): Reduce false `Image_Availability` failures (#22407) --- scripts/ci/lib.sh | 36 ++++++++++++++++++++---------------- scripts/ci/test_state.sh | 1 - tests/e2e/lib.sh | 12 ------------ 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index b9d082c1f112c..af27a420983ee 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -597,21 +597,37 @@ poll_for_system_test_images() { local tag local image + local success="true" while read -r image tag do while ! check_rhacs_eng_image_exists "$image" "$tag" do info "$image does not exist" if (( $(date '+%s') - start_time > time_limit )); then - check_build_workflows "$(get_commit_sha)" - die "ERROR: Timed out waiting for images after ${time_limit} seconds" + success="false" + break 2 fi sleep 60 done done < "$image_list" - info "All images exist." - touch "${STATE_IMAGES_AVAILABLE}" + local images_available=("Image_Availability" "Were the required images built successfully by GitHub Actions?") + if [[ "$success" == "true" ]]; then + save_junit_success "${images_available[@]}" + info "All images exist." + touch "${STATE_IMAGES_AVAILABLE}" + else + local commit_sha="$(get_commit_sha)" + local build_details="Build results are unknown" + local build_results + if build_results="$(check-workflow-run --workflow=build.yaml --head-SHA="${commit_sha}")"; then + build_details="GitHub Actions workflow status for build.yaml: +$build_results" + fi + info "${build_details}" + save_junit_failure "${images_available[@]}" "${build_details}" + die "ERROR: Timed out waiting for images after ${time_limit} seconds" + fi } # Image prefetch is broken into two sets: @@ -1128,18 +1144,6 @@ check_rhacs_eng_image_exists() { [[ "$(jq -r '.tags | first | .name' <<<"$check")" == "$tag" ]] } -check_build_workflows() { - local commit_sha="$1" - - { - echo - info "GitHub Actions workflow status for build.yaml:" - check-workflow-run \ - --workflow=build.yaml \ - --head-SHA="${commit_sha}" - } | tee "${STATE_BUILD_RESULTS}" || true -} - check_scanner_version() { if ! is_release_version "$(make --quiet --no-print-directory scanner-tag)"; then echo "::error::Scanner tag does not look like a release tag. Please update SCANNER_VERSION file before releasing." diff --git a/scripts/ci/test_state.sh b/scripts/ci/test_state.sh index 1b204d59a3675..2acb244b556f0 100644 --- a/scripts/ci/test_state.sh +++ b/scripts/ci/test_state.sh @@ -3,7 +3,6 @@ # State files used to track test progress across script invocations. export STATE_IMAGES_AVAILABLE="${SHARED_DIR:-/tmp}/stackrox_ci_state_images_available" -export STATE_BUILD_RESULTS="${SHARED_DIR:-/tmp}/stackrox_ci_state_build_results" export STATE_DEPLOYED="${SHARED_DIR:-/tmp}/stackrox_ci_state_deployed" # For the upgrade test diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index b006da77ee25a..fd8a36d57a27e 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -1859,24 +1859,12 @@ db_backup_and_restore_test() { handle_e2e_progress_failures() { info "Checking for progress events" - local images_available=("Image_Availability" "Were the required images built successfully by GitHub Actions?") local stackrox_deployed=("Stackrox_Deployment" "Was Stackrox deployed to the cluster?") local check_deployment=false if [[ -f "${STATE_IMAGES_AVAILABLE}" ]]; then - save_junit_success "${images_available[@]}" check_deployment=true - else - local build_results="build results are unknown" - if [[ -f "${STATE_BUILD_RESULTS}" ]]; then - build_results="$(cat "${STATE_BUILD_RESULTS}")" - fi - read -r -d '' build_details <<- _EO_DETAILS_ || true -Check the build workflow runs on GitHub: -${build_results} -_EO_DETAILS_ - save_junit_failure "${images_available[@]}" "${build_details}" fi case "$CI_JOB_NAME" in From b7dfbc929e28fa4ef4f533d207453d145e8031d1 Mon Sep 17 00:00:00 2001 From: David Vail Date: Tue, 25 Aug 2026 14:23:30 -0400 Subject: [PATCH 068/638] fix(ui): apply UI_CUSTOM_PROXIES in dev server proxy config (#22451) --- ui/apps/platform/src/setupProxy.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/apps/platform/src/setupProxy.js b/ui/apps/platform/src/setupProxy.js index c6b399099ad75..3d73bde1e9f25 100644 --- a/ui/apps/platform/src/setupProxy.js +++ b/ui/apps/platform/src/setupProxy.js @@ -46,7 +46,11 @@ export function viteProxy() { const proxy = proxyWithTarget(process.env.UI_START_TARGET || 'https://localhost:8000'); + // Custom proxies come first so their (often more specific, possibly regex) + // paths take precedence over the generic catch-alls below. Vite matches proxy + // contexts in insertion order, and treats keys beginning with `^` as RegExp. return { + ...customProxies, '/v1': proxy, '/v2': proxy, '/api': proxy, From 2d77dd32fc991c66775eebe8352aff0c1a290439 Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Tue, 25 Aug 2026 20:47:57 +0200 Subject: [PATCH 069/638] ROX-33973: introduce client for ACM UserPermissions API (#22243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann Co-authored-by: Claude Opus 4.6 (1M context) --- pkg/sac/externalrolebroker/acm/acm_client.go | 53 ++++ .../externalrolebroker/acm/acm_client_test.go | 237 ++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 pkg/sac/externalrolebroker/acm/acm_client.go create mode 100644 pkg/sac/externalrolebroker/acm/acm_client_test.go diff --git a/pkg/sac/externalrolebroker/acm/acm_client.go b/pkg/sac/externalrolebroker/acm/acm_client.go new file mode 100644 index 0000000000000..4fadb1b6d93e7 --- /dev/null +++ b/pkg/sac/externalrolebroker/acm/acm_client.go @@ -0,0 +1,53 @@ +package acm + +import ( + "context" + + "github.com/pkg/errors" + "github.com/stackrox/rox/pkg/retryablehttp" + clusterviewclient "github.com/stolostron/cluster-lifecycle-api/client/clusterview/clientset/versioned/typed/clusterview/v1alpha1" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" +) + +// Client provides access to the ACM clusterview aggregate API. +type Client struct { + clusterviewClient clusterviewclient.ClusterviewV1alpha1Interface +} + +// NewClientForConfig creates a new client for the ACM clusterview API using the provided config. +// The config's transport is wrapped with retry logic for transient errors. +func NewClientForConfig(config *rest.Config) (*Client, error) { + cfg := rest.CopyConfig(config) + retryablehttp.ConfigureRESTConfig(cfg) + clusterviewClient, err := clusterviewclient.NewForConfig(cfg) + if err != nil { + return nil, errors.Wrap(err, "failed to create clusterview client") + } + return &Client{ + clusterviewClient: clusterviewClient, + }, nil +} + +// ListUserPermissions retrieves the list of user permissions from the ACM clusterview API. +// This calls the aggregate API at /apis/clusterview.open-cluster-management.io/v1alpha1/userpermissions. +func (c *Client) ListUserPermissions( + ctx context.Context, + opts metav1.ListOptions, +) (*clusterviewv1alpha1.UserPermissionList, error) { + list, err := c.clusterviewClient.UserPermissions().List(ctx, opts) + if err != nil { + return nil, errors.Wrap(err, "failed to list user permissions") + } + return list, nil +} + +// GetUserPermission retrieves a specific user permission by name from the ACM clusterview API. +func (c *Client) GetUserPermission(ctx context.Context, name string, opts metav1.GetOptions) (*clusterviewv1alpha1.UserPermission, error) { + permission, err := c.clusterviewClient.UserPermissions().Get(ctx, name, opts) + if err != nil { + return nil, errors.Wrapf(err, "failed to get user permission %q", name) + } + return permission, nil +} diff --git a/pkg/sac/externalrolebroker/acm/acm_client_test.go b/pkg/sac/externalrolebroker/acm/acm_client_test.go new file mode 100644 index 0000000000000..3e5704b025cde --- /dev/null +++ b/pkg/sac/externalrolebroker/acm/acm_client_test.go @@ -0,0 +1,237 @@ +package acm + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" +) + +func TestNewClientForConfig(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) + })) + defer server.Close() + + client, err := NewClientForConfig(&rest.Config{Host: server.URL}) + require.NoError(t, err) + require.NotNil(t, client) + assert.NotNil(t, client.clusterviewClient) +} + +func TestClient_WithFakeServer(t *testing.T) { + testPermissions := &clusterviewv1alpha1.UserPermissionList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "clusterview.open-cluster-management.io/v1alpha1", + Kind: "UserPermissionList", + }, + ListMeta: metav1.ListMeta{}, + Items: []clusterviewv1alpha1.UserPermission{ + { + TypeMeta: metav1.TypeMeta{ + APIVersion: "clusterview.open-cluster-management.io/v1alpha1", + Kind: "UserPermission", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "managedcluster:admin", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster1", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "cluster2", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system"}, + }, + }, + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"*"}, + Verbs: []string{"*"}, + }, + }, + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{ + APIVersion: "clusterview.open-cluster-management.io/v1alpha1", + Kind: "UserPermission", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "managedcluster:view", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster1", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods", "services"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + }, + }, + }, + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expectedBasePath := "/apis/clusterview.open-cluster-management.io/v1alpha1/userpermissions" + + switch { + case r.Method == http.MethodGet && r.URL.Path == expectedBasePath: + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(testPermissions) + require.NoError(t, err) + + case r.Method == http.MethodGet && r.URL.Path == expectedBasePath+"/managedcluster:admin": + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(&testPermissions.Items[0]) + require.NoError(t, err) + + case r.Method == http.MethodGet && r.URL.Path == expectedBasePath+"/managedcluster:view": + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(&testPermissions.Items[1]) + require.NoError(t, err) + + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + client, err := NewClientForConfig(&rest.Config{Host: server.URL}) + require.NoError(t, err) + require.NotNil(t, client) + + t.Run("ListUserPermissions", func(t *testing.T) { + list, err := client.ListUserPermissions(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, list) + + assert.Len(t, list.Items, 2) + + assert.Equal(t, "managedcluster:admin", list.Items[0].Name) + assert.Len(t, list.Items[0].Status.Bindings, 2) + assert.Equal(t, "cluster1", list.Items[0].Status.Bindings[0].Cluster) + assert.Equal(t, clusterviewv1alpha1.BindingScopeCluster, list.Items[0].Status.Bindings[0].Scope) + assert.Equal(t, []string{"*"}, list.Items[0].Status.Bindings[0].Namespaces) + + assert.Equal(t, "cluster2", list.Items[0].Status.Bindings[1].Cluster) + assert.Equal(t, clusterviewv1alpha1.BindingScopeNamespace, list.Items[0].Status.Bindings[1].Scope) + assert.Equal(t, []string{"default", "kube-system"}, list.Items[0].Status.Bindings[1].Namespaces) + + assert.Len(t, list.Items[0].Status.ClusterRoleDefinition.Rules, 1) + assert.Equal(t, []string{"*"}, list.Items[0].Status.ClusterRoleDefinition.Rules[0].APIGroups) + assert.Equal(t, []string{"*"}, list.Items[0].Status.ClusterRoleDefinition.Rules[0].Resources) + assert.Equal(t, []string{"*"}, list.Items[0].Status.ClusterRoleDefinition.Rules[0].Verbs) + + assert.Equal(t, "managedcluster:view", list.Items[1].Name) + assert.Len(t, list.Items[1].Status.Bindings, 1) + assert.Equal(t, "cluster1", list.Items[1].Status.Bindings[0].Cluster) + }) + + t.Run("GetUserPermission_Admin", func(t *testing.T) { + permission, err := client.GetUserPermission(context.Background(), "managedcluster:admin", metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, permission) + + assert.Equal(t, "managedcluster:admin", permission.Name) + assert.Len(t, permission.Status.Bindings, 2) + + assert.Equal(t, "cluster1", permission.Status.Bindings[0].Cluster) + assert.Equal(t, clusterviewv1alpha1.BindingScopeCluster, permission.Status.Bindings[0].Scope) + assert.Equal(t, []string{"*"}, permission.Status.Bindings[0].Namespaces) + + assert.Equal(t, "cluster2", permission.Status.Bindings[1].Cluster) + assert.Equal(t, clusterviewv1alpha1.BindingScopeNamespace, permission.Status.Bindings[1].Scope) + assert.Equal(t, []string{"default", "kube-system"}, permission.Status.Bindings[1].Namespaces) + + assert.Len(t, permission.Status.ClusterRoleDefinition.Rules, 1) + rule := permission.Status.ClusterRoleDefinition.Rules[0] + assert.Equal(t, []string{"*"}, rule.APIGroups) + assert.Equal(t, []string{"*"}, rule.Resources) + assert.Equal(t, []string{"*"}, rule.Verbs) + }) + + t.Run("GetUserPermission_View", func(t *testing.T) { + permission, err := client.GetUserPermission(context.Background(), "managedcluster:view", metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, permission) + + assert.Equal(t, "managedcluster:view", permission.Name) + assert.Len(t, permission.Status.Bindings, 1) + + assert.Equal(t, "cluster1", permission.Status.Bindings[0].Cluster) + assert.Equal(t, clusterviewv1alpha1.BindingScopeCluster, permission.Status.Bindings[0].Scope) + assert.Equal(t, []string{"*"}, permission.Status.Bindings[0].Namespaces) + + assert.Len(t, permission.Status.ClusterRoleDefinition.Rules, 1) + rule := permission.Status.ClusterRoleDefinition.Rules[0] + assert.Equal(t, []string{""}, rule.APIGroups) + assert.Equal(t, []string{"pods", "services"}, rule.Resources) + assert.Equal(t, []string{"get", "list", "watch"}, rule.Verbs) + }) +} + +func TestClient_WithFakeServer_EmptyList(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + emptyList := &clusterviewv1alpha1.UserPermissionList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "clusterview.open-cluster-management.io/v1alpha1", + Kind: "UserPermissionList", + }, + ListMeta: metav1.ListMeta{}, + Items: []clusterviewv1alpha1.UserPermission{}, + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(emptyList) + require.NoError(t, err) + })) + defer server.Close() + + client, err := NewClientForConfig(&rest.Config{Host: server.URL}) + require.NoError(t, err) + + list, err := client.ListUserPermissions(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, list) + assert.Len(t, list.Items, 0) +} + +func TestClient_WithFakeServer_NotFound(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) + })) + defer server.Close() + + client, err := NewClientForConfig(&rest.Config{Host: server.URL}) + require.NoError(t, err) + + _, err = client.GetUserPermission(context.Background(), "nonexistent", metav1.GetOptions{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to get user permission") +} From 3fc20809cba3b3a6c2f635a51281ea3846850b46 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Tue, 25 Aug 2026 21:01:14 +0200 Subject: [PATCH 070/638] chore(tests): Bump roxie version. (#22435) --- ROXIE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROXIE_VERSION b/ROXIE_VERSION index cb498ab2c89f4..76914ddc02f84 100644 --- a/ROXIE_VERSION +++ b/ROXIE_VERSION @@ -1 +1 @@ -0.4.8 +0.4.9 From 96307cf7fb8a5df7f1623367b93f7e6c90108403 Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Tue, 25 Aug 2026 16:02:04 -0300 Subject: [PATCH 071/638] ROX-36136: bundler change detection and per-source timestamps (#22408) --- .github/workflows/scanner-updater-bundle.yaml | 91 ++++++++++++- .github/workflows/scanner-updater-export.yaml | 15 ++- .../workflows/scanner-updater-schedule.yaml | 5 +- .../scanner-updater-check-freshness.sh | 123 ++++++++++-------- .../scripts/scanner-updater-config.sh | 87 +++++++++++++ .../scripts/scanner-updater-matrix.sh | 36 +++-- 6 files changed, 267 insertions(+), 90 deletions(-) create mode 100755 .github/workflows/scripts/scanner-updater-config.sh diff --git a/.github/workflows/scanner-updater-bundle.yaml b/.github/workflows/scanner-updater-bundle.yaml index ff607c47241c4..2d71a99b2d92f 100644 --- a/.github/workflows/scanner-updater-bundle.yaml +++ b/.github/workflows/scanner-updater-bundle.yaml @@ -11,6 +11,12 @@ on: schedule: - cron: "47 * * * *" workflow_dispatch: + inputs: + force: + description: "Force rebuild even if no sources are newer than the bundle" + required: false + type: boolean + default: false permissions: contents: read @@ -70,21 +76,95 @@ jobs: - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 - - name: Download sources + - name: Check for changes + id: check-changes + env: + FORCE: ${{ inputs.force == true }} run: | set -euo pipefail - bucket=$(yq '.storage.bucket' "$SOURCE_CONFIG") - prefix=$(yq '.storage.prefix' "$SOURCE_CONFIG") + sc=./.github/workflows/scripts/scanner-updater-config.sh + bucket=$("$sc" bucket) + prefix=$("$sc" prefix) echo "GCS_BUCKET=$bucket" >> "$GITHUB_ENV" echo "GCS_PREFIX=$prefix" >> "$GITHUB_ENV" + if [[ "$FORCE" == "true" ]]; then + echo "Force rebuild requested" + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + bundle_obj=$("$sc" bundle-object "$STREAM") + + # Get the bundle's creation timestamp. If the bundle does not + # exist or the query fails, assume changes and proceed. + if ! bundle_ts=$(gcloud storage objects describe "$bundle_obj" \ + --format="value(creation_time)" 2>/dev/null) || [ -z "$bundle_ts" ]; then + echo "Bundle not found or query failed; assuming changes" + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + bundle_epoch=$(date -d "$bundle_ts" +%s) + echo "Bundle created: $bundle_ts (epoch: $bundle_epoch)" + + # Check if any source has been updated after the bundle. + changed=false + while IFS= read -r source; do + object="gs://${bucket}/${prefix}/${STREAM}/sources/${source}.json.zst" + newer=$(GCS_OBJECT="$object" \ + ./.github/workflows/scripts/scanner-updater-check-freshness.sh --newer-than "$bundle_epoch") || { + echo "Source '${source}': check failed; assuming changes" + changed=true; break + } + if [[ "$newer" == "true" ]]; then + echo "Source '${source}' is newer than bundle" + changed=true; break + fi + done < <("$sc" sources "$STREAM") + + echo "changed=${changed}" >> "$GITHUB_OUTPUT" + if [ "$changed" = "false" ]; then + echo "No sources newer than bundle; skipping" + fi + + - name: Download sources + if: steps.check-changes.outputs.changed == 'true' + run: | + set -euo pipefail + + sc=./.github/workflows/scripts/scanner-updater-config.sh mkdir -p bundles - gcloud storage cp "gs://${bucket}/${prefix}/${STREAM}/sources/*.json.zst" bundles/ + + while IFS= read -r source; do + object="gs://${GCS_BUCKET}/${GCS_PREFIX}/${STREAM}/sources/${source}.json.zst" + dest="bundles/${source}.json.zst" + + # Retry if the source is overwritten between describe and cp. + downloaded=false + for attempt in 1 2 3; do + meta=$(gcloud storage objects describe "$object" --format=json) + gen=$(jq -r '.generation' <<< "$meta") + timestamp=$(jq -re '.metadata["refresh-completed-at"]' <<< "$meta") + + if gcloud storage cp "$object" "$dest" --if-generation-match="$gen"; then + touch -d "$timestamp" "$dest" + downloaded=true + break + fi + echo "Source '${source}': download attempt ${attempt} failed, retrying" + done + + if [[ "$downloaded" != "true" ]]; then + echo "::error::${source}: download failed after 3 attempts" + exit 1 + fi + done < <("$sc" sources "$STREAM") - name: Validate sources id: download-validate + if: steps.check-changes.outputs.changed == 'true' run: | set -euo pipefail @@ -99,7 +179,7 @@ jobs: echo "source '${source}': OK" valid_files+=("$dest") fi - done < <(yq ".bundles.${STREAM}[]" "$SOURCE_CONFIG") + done < <(./.github/workflows/scripts/scanner-updater-config.sh sources "$STREAM") ( IFS=','; echo "failed=${failed_sources[*]}" >> "$GITHUB_OUTPUT" ) @@ -112,6 +192,7 @@ jobs: echo "Validated: ${#valid_files[@]} of $(( ${#valid_files[@]} + ${#failed_sources[@]} )) source(s) OK" - name: Assemble and upload bundle + if: steps.check-changes.outputs.changed == 'true' run: | set -euo pipefail diff --git a/.github/workflows/scanner-updater-export.yaml b/.github/workflows/scanner-updater-export.yaml index 9192f8674006f..3c322cce89acc 100644 --- a/.github/workflows/scanner-updater-export.yaml +++ b/.github/workflows/scanner-updater-export.yaml @@ -69,15 +69,16 @@ jobs: UPDATER_SOURCE: ${{ inputs.source }} SOURCE_CONFIG: scanner/updater/ci/source-config.yaml run: | - # check-freshness.sh exit codes: 0=fresh, 1=stale/missing, 2=error. - rc=0 - ./.github/workflows/scripts/scanner-updater-check-freshness.sh || rc=$? - if [[ $rc -eq 0 ]]; then + interval_secs=$(./.github/workflows/scripts/scanner-updater-config.sh interval-secs "$UPDATER_SOURCE") + + fresh=$(./.github/workflows/scripts/scanner-updater-check-freshness.sh --max-age "$interval_secs") || { + echo "result=error" >> "$GITHUB_OUTPUT" + exit 0 + } + if [[ "$fresh" == "true" ]]; then echo "result=skip" >> "$GITHUB_OUTPUT" - elif [[ $rc -eq 1 ]]; then - echo "result=run" >> "$GITHUB_OUTPUT" else - echo "result=error" >> "$GITHUB_OUTPUT" + echo "result=run" >> "$GITHUB_OUTPUT" fi - name: Fail on recheck error diff --git a/.github/workflows/scanner-updater-schedule.yaml b/.github/workflows/scanner-updater-schedule.yaml index e1fb00dc1c2b4..7694a2ce236ed 100644 --- a/.github/workflows/scanner-updater-schedule.yaml +++ b/.github/workflows/scanner-updater-schedule.yaml @@ -81,8 +81,9 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail - bucket=$(yq '.storage.bucket' "$SOURCE_CONFIG") - prefix=$(yq '.storage.prefix' "$SOURCE_CONFIG") + sc=./.github/workflows/scripts/scanner-updater-config.sh + bucket=$("$sc" bucket) + prefix=$("$sc" prefix) matrix="{\"include\":[{\"source\":\"manual\",\"ref\":\"${PR_SHA}\",\"object\":\"gs://${bucket}/${prefix}/pr-${PR_NUMBER}/sources/manual.json.zst\"}]}" printf '{"has_due":true,"has_errors":false,"errors":[],"matrix":%s}\n' "$matrix" > matrix-result.json diff --git a/.github/workflows/scripts/scanner-updater-check-freshness.sh b/.github/workflows/scripts/scanner-updater-check-freshness.sh index 961a679bea2f5..562c838df0577 100755 --- a/.github/workflows/scripts/scanner-updater-check-freshness.sh +++ b/.github/workflows/scripts/scanner-updater-check-freshness.sh @@ -1,86 +1,97 @@ #!/usr/bin/env bash # -# Check whether an updater's vulnerability data is fresh. +# Check whether a GCS object is newer than a reference point. # -# Uses an unauthenticated curl against the public GCS download URL to -# classify the object state (200/404/other), then gcloud for creation_time -# when the object exists. +# Uses an unauthenticated HTTP HEAD request against the public GCS +# download URL to classify the object state (200/404/other), then +# gcloud for the object timestamp when it exists. +# +# Exactly one of --newer-than or --max-age must be provided. +# +# --newer-than : +# The object is "fresh" if its timestamp is after . +# +# --max-age : +# The object is "fresh" if its age is below +# (equivalent to --newer-than "$(date +%s) - N"). +# +# Output (stdout): +# "true" — object exists and is newer than the threshold +# "false" — object is not newer, or does not exist (404) # # Exit codes: -# 0 — object exists and is fresh (age < interval) -# 1 — object is missing (404) or stale (age >= interval) -# 2 — unexpected failure: caller should treat as error and notify +# 0 — check completed (result is on stdout) +# non-zero — unexpected failure (stdout is irrelevant) # # Inputs (environment): -# GCS_OBJECT — full GCS object path (gs://bucket/path/to/data.json.zst) -# UPDATER_SOURCE — updater name, used to look up interval in config -# SOURCE_CONFIG — path to source-config.yaml +# GCS_OBJECT — full GCS object path (gs://bucket/path/to/object) # -# Requires: curl, yq, gcloud. +# Requires: curl, gcloud, date. set -euo pipefail -# The ERR trap below forces any unexpected command failure to exit 2 instead of -# the failing command's own exit code. Without it, set -e would exit with the -# failing command's code (usually 1), making it indistinguishable from the -# intentional "not fresh" exit. The trap guarantees that exit 1 can only come -# from our explicit exit 1 calls. -trap 'echo "::error::unexpected failure in check-freshness.sh at line $LINENO"; exit 2' ERR +trap 'echo "::error::unexpected failure in check-freshness.sh at line $LINENO" >&2; exit 2' ERR -object="${GCS_OBJECT:?GCS_OBJECT is required}" -updater="${UPDATER_SOURCE:?UPDATER_SOURCE is required}" -config="${SOURCE_CONFIG:?SOURCE_CONFIG is required}" +threshold="" +while [[ $# -gt 0 ]]; do + case "$1" in + --newer-than) + if [[ -n "$threshold" ]]; then + echo "::error::--newer-than and --max-age are mutually exclusive" >&2 + exit 1 + fi + threshold="${2:?--newer-than requires an epoch value}" + shift 2 + ;; + --max-age) + if [[ -n "$threshold" ]]; then + echo "::error::--newer-than and --max-age are mutually exclusive" >&2 + exit 1 + fi + max_age="${2:?--max-age requires a value in seconds}" + threshold=$(( $(date +%s) - max_age )) + shift 2 + ;; + *) + echo "::error::unknown argument: $1" >&2 + exit 1 + ;; + esac +done -# Derive the download URL from the gs:// path. -download_url="https://storage.googleapis.com/${object#gs://}" +if [[ -z "$threshold" ]]; then + echo "::error::one of --newer-than or --max-age is required" >&2 + exit 1 +fi -duration_to_seconds() { - local dur="${1:?}" - local total=0 remaining="$dur" - while [[ -n "$remaining" ]]; do - if [[ "$remaining" =~ ^([0-9]+)h(.*)$ ]]; then - total=$(( total + BASH_REMATCH[1] * 3600 )); remaining="${BASH_REMATCH[2]}" - elif [[ "$remaining" =~ ^([0-9]+)m(.*)$ ]]; then - total=$(( total + BASH_REMATCH[1] * 60 )); remaining="${BASH_REMATCH[2]}" - elif [[ "$remaining" =~ ^([0-9]+)s(.*)$ ]]; then - total=$(( total + BASH_REMATCH[1] )); remaining="${BASH_REMATCH[2]}" - else - echo "::error::cannot parse duration: $remaining"; exit 2 - fi - done - echo "$total" -} +object="${GCS_OBJECT:?GCS_OBJECT is required}" -# Read interval from config. -default_interval=$(yq '.updaters.default.interval // "4h"' "$config") -interval=$(yq ".updaters.\"${updater}\".interval // \"$default_interval\"" "$config") -interval_secs=$(duration_to_seconds "$interval") +# Derive the public download URL from the gs:// path. +download_url="https://storage.googleapis.com/${object#gs://}" -# Check object existence via unauthenticated curl. -http_code=$(curl -s -o /dev/null -w "%{http_code}" \ +http_code=$(curl --head --silent --show-error -o /dev/null -w "%{http_code}" \ --connect-timeout 10 --max-time 15 \ "$download_url") case "$http_code" in 200) - creation_time=$(gcloud storage objects describe "$object" \ + creation_time=$(timeout 30 gcloud storage objects describe "$object" \ --format="value(creation_time)") - created_epoch=$(date -d "$creation_time" +%s) - now=$(date +%s) - age=$(( now - created_epoch )) - if (( age < interval_secs )); then - echo >&2 "INFO: ${object}: fresh (age=${age}s < interval=${interval_secs}s)" - exit 0 + object_epoch=$(date -d "$creation_time" +%s) + if (( object_epoch > threshold )); then + echo >&2 "INFO: ${object}: newer than threshold (${object_epoch} > ${threshold})" + echo "true" + else + echo >&2 "INFO: ${object}: not newer than threshold (${object_epoch} <= ${threshold})" + echo "false" fi - echo >&2 "INFO: ${object}: stale (age=${age}s >= interval=${interval_secs}s)" - exit 1 ;; 404) echo >&2 "INFO: ${object}: not found" - exit 1 + echo "false" ;; *) - echo "::error::${object}: GCS returned HTTP $http_code" - exit 2 + echo "::error::${object}: GCS returned HTTP $http_code" >&2 + exit 1 ;; esac diff --git a/.github/workflows/scripts/scanner-updater-config.sh b/.github/workflows/scripts/scanner-updater-config.sh new file mode 100755 index 0000000000000..6af97f972a0bd --- /dev/null +++ b/.github/workflows/scripts/scanner-updater-config.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Query source-config.yaml for scanner updater workflows. +# +# Usage: +# scanner-updater-config.sh [args...] +# +# Commands: +# bucket Storage bucket name. +# prefix Storage prefix path. +# sources List updater sources for a stream, one per line. +# source-object GCS object path for a source. +# bundle-object GCS object path for a bundle. +# interval-secs Updater refresh interval in seconds. +# +# Inputs (environment): +# SOURCE_CONFIG — path to source-config.yaml +# +# Requires: yq. + +set -euo pipefail + +config="${SOURCE_CONFIG:?SOURCE_CONFIG is required}" + +if [[ ! -f "$config" ]]; then + echo "::error::config file not found: $config" >&2 + exit 1 +fi + +if ! yq '.' "$config" > /dev/null 2>&1; then + echo "::error::config file is not valid YAML: $config" >&2 + exit 1 +fi + +duration_to_seconds() { + local dur="${1:?missing required argument 'duration'}"; shift + local total=0 remaining="$dur" + while [[ -n "$remaining" ]]; do + if [[ "$remaining" =~ ^([0-9]+)h(.*)$ ]]; then + total=$(( total + BASH_REMATCH[1] * 3600 )); remaining="${BASH_REMATCH[2]}" + elif [[ "$remaining" =~ ^([0-9]+)m(.*)$ ]]; then + total=$(( total + BASH_REMATCH[1] * 60 )); remaining="${BASH_REMATCH[2]}" + elif [[ "$remaining" =~ ^([0-9]+)s(.*)$ ]]; then + total=$(( total + BASH_REMATCH[1] )); remaining="${BASH_REMATCH[2]}" + else + echo "::error::cannot parse duration: $remaining" >&2; exit 1 + fi + done + echo "$total" +} + +command="${1:?missing required argument 'command'}"; shift + +case "$command" in + bucket) + yq '.storage.bucket' "$config" + ;; + prefix) + yq '.storage.prefix' "$config" + ;; + sources) + stream="${1:?missing required argument 'stream'}"; shift + yq ".bundles.\"${stream}\"[]" "$config" + ;; + source-object) + stream="${1:?missing required argument 'stream'}"; shift + source="${1:?missing required argument 'source'}"; shift + bucket=$(yq '.storage.bucket' "$config") + prefix=$(yq '.storage.prefix' "$config") + echo "gs://${bucket}/${prefix}/${stream}/sources/${source}.json.zst" + ;; + bundle-object) + stream="${1:?missing required argument 'stream'}"; shift + bucket=$(yq '.storage.bucket' "$config") + prefix=$(yq '.storage.prefix' "$config") + echo "gs://${bucket}/${prefix}/${stream}/vulnerabilities.zip" + ;; + interval-secs) + source="${1:?missing required argument 'source'}"; shift + default_interval=$(yq '.updaters.default.interval // "4h"' "$config") + interval=$(yq ".updaters.\"${source}\".interval // \"$default_interval\"" "$config") + duration_to_seconds "$interval" + ;; + *) + echo "::error::unknown command: $command" >&2 + exit 1 + ;; +esac diff --git a/.github/workflows/scripts/scanner-updater-matrix.sh b/.github/workflows/scripts/scanner-updater-matrix.sh index 682162ad42d44..a4fd4b0e1a744 100755 --- a/.github/workflows/scripts/scanner-updater-matrix.sh +++ b/.github/workflows/scripts/scanner-updater-matrix.sh @@ -20,8 +20,6 @@ set -euo pipefail -config="${SOURCE_CONFIG:?SOURCE_CONFIG is required}" - for cmd in yq jq; do if ! command -v "$cmd" &>/dev/null; then echo "::error::$cmd is required but not found" @@ -29,40 +27,38 @@ for cmd in yq jq; do fi done -# Read storage location from config. -bucket=$(yq '.storage.bucket' "$config") -prefix=$(yq '.storage.prefix' "$config") +sc="$(dirname "$0")/scanner-updater-config.sh" + +bucket=$("$sc" bucket) +prefix=$("$sc" prefix) # Expand stream/ref/updater triples, check freshness, emit due pairs. ./.github/workflows/scripts/scanner-get-released-tags.sh \ | jq -r '.[] | "\(.version) \(.ref)"' \ | while read -r stream ref; do - yq ".bundles.\"$stream\" | .[]" "$config" \ + "$sc" sources "$stream" \ | while read -r updater; do echo "$stream $ref $updater" done done \ | while read -r stream ref updater; do object="gs://${bucket}/${prefix}/${stream}/sources/${updater}.json.zst" + interval_secs=$("$sc" interval-secs "$updater") - # check-freshness.sh exit codes: 0=fresh, 1=stale/missing, 2=error. - if GCS_OBJECT="$object" \ - UPDATER_SOURCE="$updater" \ - SOURCE_CONFIG="$config" \ - ./.github/workflows/scripts/scanner-updater-check-freshness.sh - then - continue - elif [[ $? -eq 1 ]]; then - jq -n \ - --arg source "$updater" \ - --arg ref "$ref" \ - --arg object "$object" \ - '{source: $source, ref: $ref, object: $object}' - else + fresh=$(GCS_OBJECT="$object" \ + ./.github/workflows/scripts/scanner-updater-check-freshness.sh --max-age "$interval_secs") || { echo "::error::${object}: freshness check failed" jq -n --arg msg "${object}: freshness check failed" '{error: $msg}' continue + } + if [[ "$fresh" == "true" ]]; then + continue fi + jq -n \ + --arg source "$updater" \ + --arg ref "$ref" \ + --arg object "$object" \ + '{source: $source, ref: $ref, object: $object}' done \ | jq -s '{ has_due: (map(select(.source)) | length > 0 | tostring), From df7fd000f5fc8a88e979360fc6e845fe74595d92 Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:13:34 -0500 Subject: [PATCH 072/638] ROX-36235: Expose Filterable CVE origin (#22412) --- central/cve/image/v2/datastore/store/postgres/store.go | 5 ++++- central/graphql/resolvers/image_vulnerabilities.go | 2 ++ .../graphql/resolvers/image_vulnerabilities_utilities.go | 4 ++++ central/image/datastore/store/v2/postgres/store.go | 6 +++++- central/imagev2/datastore/store/postgres/store.go | 6 +++++- generated/storage/cve.pb.go | 2 +- pkg/postgres/schema/image_cves_v2.go | 1 + pkg/search/options.go | 1 + proto/storage/cve.proto | 2 +- 9 files changed, 24 insertions(+), 5 deletions(-) diff --git a/central/cve/image/v2/datastore/store/postgres/store.go b/central/cve/image/v2/datastore/store/postgres/store.go index 8e98d58955c64..c91451c625918 100644 --- a/central/cve/image/v2/datastore/store/postgres/store.go +++ b/central/cve/image/v2/datastore/store/postgres/store.go @@ -121,10 +121,11 @@ func insertIntoImageCvesV2(batch *pgx.Batch, obj *storage.ImageCVEV2) error { obj.GetAdvisory().GetLink(), pgutils.NilOrString(obj.GetImageIdV2()), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, } - finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, Advisory_Name, Advisory_Link, ImageIdV2, FixAvailableTimestamp, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, ImageIdV2 = EXCLUDED.ImageIdV2, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, Advisory_Name, Advisory_Link, ImageIdV2, FixAvailableTimestamp, Origin, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, ImageIdV2 = EXCLUDED.ImageIdV2, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, Origin = EXCLUDED.Origin, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -151,6 +152,7 @@ var copyColsImageCvesV2 = []string{ "advisory_link", "imageidv2", "fixavailabletimestamp", + "origin", "serialized", } @@ -205,6 +207,7 @@ func copyFromImageCvesV2(ctx context.Context, s pgSearch.Deleter, tx *postgres.T obj.GetAdvisory().GetLink(), pgutils.NilOrString(obj.GetImageIdV2()), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, }, nil }) diff --git a/central/graphql/resolvers/image_vulnerabilities.go b/central/graphql/resolvers/image_vulnerabilities.go index 6d939f8aa78db..6d81452f5e2da 100644 --- a/central/graphql/resolvers/image_vulnerabilities.go +++ b/central/graphql/resolvers/image_vulnerabilities.go @@ -59,6 +59,7 @@ func init() { "images(query: String, pagination: Pagination): [Image!]!", "operatingSystem: String!", "vulnerabilityState: String!", + "origin: String!", "nvdCvss: Float!", "nvdScoreVersion: String!", )), @@ -86,6 +87,7 @@ type ImageVulnerabilityResolver interface { Images(ctx context.Context, args PaginatedQuery) ([]ImageResolver, error) OperatingSystem(ctx context.Context) string VulnerabilityState(ctx context.Context) string + Origin(ctx context.Context) string Nvdcvss(ctx context.Context) float64 NvdScoreVersion(ctx context.Context) string } diff --git a/central/graphql/resolvers/image_vulnerabilities_utilities.go b/central/graphql/resolvers/image_vulnerabilities_utilities.go index b6434795d4577..63f3dec694cfc 100644 --- a/central/graphql/resolvers/image_vulnerabilities_utilities.go +++ b/central/graphql/resolvers/image_vulnerabilities_utilities.go @@ -108,3 +108,7 @@ func (resolver *imageCVEV2Resolver) State(ctx context.Context) string { value := resolver.data.GetState() return value.String() } + +func (resolver *imageCVEV2Resolver) Origin(_ context.Context) string { + return resolver.data.GetOrigin().String() +} diff --git a/central/image/datastore/store/v2/postgres/store.go b/central/image/datastore/store/v2/postgres/store.go index 60c5528d23d44..5ffb370d854d1 100644 --- a/central/image/datastore/store/v2/postgres/store.go +++ b/central/image/datastore/store/v2/postgres/store.go @@ -329,6 +329,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTime ti "advisory_name", "advisory_link", "fixavailabletimestamp", + "origin", "serialized", } @@ -370,6 +371,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTime ti obj.GetAdvisory().GetName(), obj.GetAdvisory().GetLink(), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, }) @@ -1009,11 +1011,13 @@ func (s *storeImpl) insertIntoImageComponentV2Cves(batch *pgx.Batch, obj *storag obj.GetFixedBy(), obj.GetComponentId(), obj.GetAdvisory().GetName(), + obj.GetAdvisory().GetLink(), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, } - finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, advisory_name, FixAvailableTimestamp, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, advisory_name = EXCLUDED.advisory_name, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, advisory_name, advisory_link, FixAvailableTimestamp, Origin, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, advisory_name = EXCLUDED.advisory_name, advisory_link = EXCLUDED.advisory_link, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, Origin = EXCLUDED.Origin, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil diff --git a/central/imagev2/datastore/store/postgres/store.go b/central/imagev2/datastore/store/postgres/store.go index 4997af72493c6..225bc9ade76a0 100644 --- a/central/imagev2/datastore/store/postgres/store.go +++ b/central/imagev2/datastore/store/postgres/store.go @@ -333,6 +333,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta "advisory_name", "advisory_link", "fixavailabletimestamp", + "origin", "serialized", } @@ -375,6 +376,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta obj.GetAdvisory().GetName(), obj.GetAdvisory().GetLink(), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, }) @@ -1070,11 +1072,13 @@ func (s *storeImpl) insertIntoImageComponentV2Cves(batch *pgx.Batch, obj *storag obj.GetFixedBy(), obj.GetComponentId(), obj.GetAdvisory().GetName(), + obj.GetAdvisory().GetLink(), protocompat.NilOrTime(obj.GetFixAvailableTimestamp()), + obj.GetOrigin(), serialized, } - finalStr := "INSERT INTO image_cves_v2 (Id, ImageIdV2, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, advisory_name, FixAvailableTimestamp, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageIdV2 = EXCLUDED.ImageIdV2, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, advisory_name = EXCLUDED.advisory_name, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO image_cves_v2 (Id, ImageIdV2, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, advisory_name, advisory_link, FixAvailableTimestamp, Origin, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageIdV2 = EXCLUDED.ImageIdV2, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, advisory_name = EXCLUDED.advisory_name, advisory_link = EXCLUDED.advisory_link, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, Origin = EXCLUDED.Origin, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil diff --git a/generated/storage/cve.pb.go b/generated/storage/cve.pb.go index 189e39561f71e..b7a233c92d855 100644 --- a/generated/storage/cve.pb.go +++ b/generated/storage/cve.pb.go @@ -1755,7 +1755,7 @@ type ImageCVEV2 struct { FixAvailableTimestamp *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=fix_available_timestamp,json=fixAvailableTimestamp,proto3" json:"fix_available_timestamp,omitempty" search:"CVE Fix Available Timestamp,hidden"` // @gotags: search:"CVE Fix Available Timestamp,hidden" Datasource string `protobuf:"bytes,17,opt,name=datasource,proto3" json:"datasource,omitempty"` // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). - Origin VulnOrigin `protobuf:"varint,18,opt,name=origin,proto3,enum=storage.VulnOrigin" json:"origin,omitempty"` + Origin VulnOrigin `protobuf:"varint,18,opt,name=origin,proto3,enum=storage.VulnOrigin" json:"origin,omitempty" search:"CVE Origin,hidden"` // @gotags: search:"CVE Origin,hidden" unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } diff --git a/pkg/postgres/schema/image_cves_v2.go b/pkg/postgres/schema/image_cves_v2.go index 3b179ba5a313c..25b19004731be 100644 --- a/pkg/postgres/schema/image_cves_v2.go +++ b/pkg/postgres/schema/image_cves_v2.go @@ -91,6 +91,7 @@ type ImageCvesV2 struct { AdvisoryLink string `gorm:"column:advisory_link;type:varchar"` ImageIDV2 string `gorm:"column:imageidv2;type:varchar"` FixAvailableTimestamp *time.Time `gorm:"column:fixavailabletimestamp;type:timestamp"` + Origin storage.VulnOrigin `gorm:"column:origin;type:integer"` Serialized []byte `gorm:"column:serialized;type:bytea"` ImagesRef Images `gorm:"foreignKey:imageid;references:id;belongsTo;constraint:OnDelete:CASCADE"` ImageComponentV2Ref ImageComponentV2 `gorm:"foreignKey:componentid;references:id;belongsTo;constraint:OnDelete:CASCADE"` diff --git a/pkg/search/options.go b/pkg/search/options.go index 9d9f38637eff1..9c0683b72f631 100644 --- a/pkg/search/options.go +++ b/pkg/search/options.go @@ -61,6 +61,7 @@ var ( CVEPublishedOn = newFieldLabel("CVE Published On") CVEFixAvailable = newFieldLabel("CVE Fix Available Timestamp") CVECreatedTime = newFieldLabel("CVE Created Time") + CVEOrigin = newFieldLabel("CVE Origin") CVESuppressed = newFieldLabel("CVE Snoozed") CVESuppressExpiry = newFieldLabel("CVE Snooze Expiry") CVSS = newFieldLabel("CVSS") diff --git a/proto/storage/cve.proto b/proto/storage/cve.proto index 178ee1c04768e..8336cf92e8403 100644 --- a/proto/storage/cve.proto +++ b/proto/storage/cve.proto @@ -225,7 +225,7 @@ message ImageCVEV2 { google.protobuf.Timestamp fix_available_timestamp = 16; // @gotags: search:"CVE Fix Available Timestamp,hidden" string datasource = 17; // origin indicates where vulnerability details came from, origin may be an aggregator (such as OSV). - VulnOrigin origin = 18; + VulnOrigin origin = 18; // @gotags: search:"CVE Origin,hidden" } message NodeCVE { From be279dfd36fe5986971ba75d9cb1ebbc1ccb460e Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Wed, 26 Aug 2026 01:51:09 -0300 Subject: [PATCH 073/638] ROX-36136: fix bundler here-string shell compat (#22462) --- .github/workflows/scanner-updater-bundle.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scanner-updater-bundle.yaml b/.github/workflows/scanner-updater-bundle.yaml index 2d71a99b2d92f..71ce222e7463a 100644 --- a/.github/workflows/scanner-updater-bundle.yaml +++ b/.github/workflows/scanner-updater-bundle.yaml @@ -57,6 +57,9 @@ jobs: concurrency: group: scanner-updater-bundle-${{ matrix.stream }} cancel-in-progress: false + defaults: + run: + shell: bash env: STREAM: ${{ matrix.stream }} SOURCE_CONFIG: scanner/updater/ci/source-config.yaml @@ -132,7 +135,7 @@ jobs: - name: Download sources if: steps.check-changes.outputs.changed == 'true' run: | - set -euo pipefail + set -euxo pipefail sc=./.github/workflows/scripts/scanner-updater-config.sh mkdir -p bundles @@ -146,7 +149,7 @@ jobs: for attempt in 1 2 3; do meta=$(gcloud storage objects describe "$object" --format=json) gen=$(jq -r '.generation' <<< "$meta") - timestamp=$(jq -re '.metadata["refresh-completed-at"]' <<< "$meta") + timestamp=$(jq -re '.custom_fields["refresh-completed-at"]' <<< "$meta") if gcloud storage cp "$object" "$dest" --if-generation-match="$gen"; then touch -d "$timestamp" "$dest" From 1719d183da3daabec6e53aaf185f9ff7b5ae622c Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 05:38:02 +0000 Subject: [PATCH 074/638] Update all dependencies (#22465) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 4 ++-- image/rhel/konflux.Dockerfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index bf850548048d0..5ac9f65d1e2e5 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:8eb2830d0936237fc13a1f2f7e45aecf90d69043380ad167fad0343632937f41 + image: registry.redhat.io/ubi9-minimal:latest@sha256:580752f96d36c4132bffd30f9c34865bf4bd87f6aa161c969d117f21732e50f7 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 945ce12704b5a..506b5e162521c 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:8eb2830d0936237fc13a1f2f7e45aecf90d69043380ad167fad0343632937f41 + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:580752f96d36c4132bffd30f9c34865bf4bd87f6aa161c969d117f21732e50f7 imagePullPolicy: IfNotPresent script: | #!/bin/bash @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787559109@sha256:643754d95cf8907b109b3e9182932e9c6e05334c97a74bb5cd991617e3d03080 + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787668499@sha256:f221165cd493d89c13ce384e8e58914571ea353f47a1443698d981494842ff83 imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index c423d9a34be53..016ac63b6b9ef 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -37,7 +37,7 @@ RUN mkdir -p image/rhel/docs/api/v1 && \ RUN make copy-go-binaries-to-image-dir -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:4c44b8f1cfbfc36b900216809eeb6ae49f4f7b90fe1d47d66ab6be1cb5ee58d4 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:57ad03c5db9382cd266af665819f60e5b1490fc27ee589320159df59f9eb453b as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app From 480076e4cee9663171f2772428ff728d29c5c7d2 Mon Sep 17 00:00:00 2001 From: Johannes Malsam Date: Wed, 26 Aug 2026 08:09:38 +0200 Subject: [PATCH 075/638] fix: set ROX_CENTRAL_WORKER_ENABLED when CR enables worker (#22441) --- .../templates/01-central-13-deployment.yaml.htpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl index 9505542ea935c..b9dfda2932057 100644 --- a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl +++ b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl @@ -155,6 +155,10 @@ spec: value: "false" {{- end }} [<- end >] + {{- if ._rox.centralWorker.enabled }} + - name: ROX_CENTRAL_WORKER_ENABLED + value: "true" + {{- end }} {{- include "srox.envVars" (list . "deployment" "central" "central") | nindent 8 }} volumeMounts: - name: varlog From 4724d5ae60f7a2bb77ba148ffe523dcde0d4d56d Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Tue, 25 Aug 2026 23:30:13 -0700 Subject: [PATCH 076/638] ROX-36380: Set capacity for slices in pkg part 2 (#22390) Co-authored-by: Claude Sonnet 4.5 --- .golangci.yml | 7 +++++ pkg/auth/authproviders/registry_impl.go | 1 + pkg/auth/permissions/utils/role.go | 2 +- .../query_to_field_value_mappers.go | 2 +- .../gcp/auth/cred_manager_impl.go | 9 ++++-- pkg/compliance/checks/common/file.go | 2 +- .../transform/access_scope.go | 2 +- pkg/features/features.go | 5 ++-- pkg/gjson/row.go | 2 +- pkg/grpc/server.go | 3 +- pkg/images/utils/convert_utils.go | 4 +-- pkg/k8srbac/policy_rule_field_set.go | 6 ++-- .../github.com/jackc/pgx/v5/mocks/rows.go | 2 +- pkg/netutil/cidr.go | 5 ++-- pkg/netutil/private_subnet.go | 17 +++++------ pkg/networkgraph/tree/radix.go | 8 +++--- pkg/postgres/pgadmin/admin_utils.go | 28 +++++++------------ pkg/postgres/schema/all.go | 4 +-- pkg/postgres/walker/walker.go | 5 ++-- pkg/search/postgres/query/numeric_query.go | 2 +- .../centralgrpc/connect_to_central.go | 6 ++-- pkg/testutils/matchers.go | 2 +- 22 files changed, 64 insertions(+), 60 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b35db3e3ff515..1bc4853049192 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,6 +31,7 @@ linters: - ineffassign - modernize - nolintlint + - prealloc - protogetter - revive # replaces golint - rowserrcheck @@ -188,6 +189,12 @@ linters: - linters: - forbidigo path: roxctl/central/generate/interactive.go + - linters: + - prealloc + path-except: ^(pkg)/ + - linters: + - prealloc + path: _test\.go - linters: - forbidigo - wrapcheck diff --git a/pkg/auth/authproviders/registry_impl.go b/pkg/auth/authproviders/registry_impl.go index 36e101a3861be..0bfea2e144fd7 100644 --- a/pkg/auth/authproviders/registry_impl.go +++ b/pkg/auth/authproviders/registry_impl.go @@ -62,6 +62,7 @@ func (r *registryImpl) Init() error { r.providers = make(map[string]Provider) err := r.store.ForEachAuthProvider(sac.WithAllAccess(context.Background()), func(storedValue *storage.AuthProvider) error { // Construct the options for the provider, using the stored definition, and the defaults for previously stored objects. + //nolint:prealloc // Preallocating would not improve performance here options := []ProviderOption{ WithStorageView(storedValue), WithAttributeVerifier(storedValue), diff --git a/pkg/auth/permissions/utils/role.go b/pkg/auth/permissions/utils/role.go index 10dbae16aa404..15e9a8d7b127b 100644 --- a/pkg/auth/permissions/utils/role.go +++ b/pkg/auth/permissions/utils/role.go @@ -8,7 +8,7 @@ import ( // Converts a slice of ResourceWithAccess to a slice of *v1.Permission. func resourcesWithAccessToPermissions(resourceWithAccess ...permissions.ResourceWithAccess) []*v1.Permission { - var perms []*v1.Permission + perms := make([]*v1.Permission, 0, len(resourceWithAccess)) for _, rAndA := range resourceWithAccess { perms = append(perms, &v1.Permission{ Resource: string(rAndA.Resource.GetResource()), diff --git a/pkg/booleanpolicy/query_to_field_value_mappers.go b/pkg/booleanpolicy/query_to_field_value_mappers.go index 92164a4d1e8d7..7700f4b9e74ed 100644 --- a/pkg/booleanpolicy/query_to_field_value_mappers.go +++ b/pkg/booleanpolicy/query_to_field_value_mappers.go @@ -165,7 +165,7 @@ func invertBooleanMap(searchTerms []string) ([]string, bool) { } func leftRightCompoundMap(searchTerms []string) ([]string, bool) { - var mustBeCompound []string + mustBeCompound := make([]string, 0, len(searchTerms)) for _, term := range searchTerms { if !strings.Contains(term, "=") { term = term + "=" diff --git a/pkg/cloudproviders/gcp/auth/cred_manager_impl.go b/pkg/cloudproviders/gcp/auth/cred_manager_impl.go index f7d28696c813e..c90549952271a 100644 --- a/pkg/cloudproviders/gcp/auth/cred_manager_impl.go +++ b/pkg/cloudproviders/gcp/auth/cred_manager_impl.go @@ -3,6 +3,7 @@ package auth import ( "bytes" "context" + "slices" artifactv1 "cloud.google.com/go/artifactregistry/apiv1" securitycenterv1 "cloud.google.com/go/securitycenter/apiv1" @@ -108,9 +109,11 @@ func (c *gcpCredentialsManagerImpl) GetCredentials(ctx context.Context) (*google ) } - scopes := []string{storagev1.CloudPlatformScope} - scopes = append(scopes, artifactv1.DefaultAuthScopes()...) - scopes = append(scopes, securitycenterv1.DefaultAuthScopes()...) + scopes := slices.Concat( + []string{storagev1.CloudPlatformScope}, + artifactv1.DefaultAuthScopes(), + securitycenterv1.DefaultAuthScopes(), + ) c.mutex.RLock() defer c.mutex.RUnlock() diff --git a/pkg/compliance/checks/common/file.go b/pkg/compliance/checks/common/file.go index de59f5050ad2a..76ead2f4361e5 100644 --- a/pkg/compliance/checks/common/file.go +++ b/pkg/compliance/checks/common/file.go @@ -34,7 +34,7 @@ func RecursiveOwnershipCheckIfDirExists(dir, user, group string) *standards.Chec // CheckRecursiveOwnership checks the files against the passed user and group func CheckRecursiveOwnership(f *compliance.File, user, group string) []*storage.ComplianceResultValue_Evidence { - var results []*storage.ComplianceResultValue_Evidence + var results []*storage.ComplianceResultValue_Evidence //nolint:prealloc // recursive function with unpredictable size results = append(results, ownershipCheck(f, user, group)...) for _, f := range f.GetChildren() { results = append(results, CheckRecursiveOwnership(f, user, group)...) diff --git a/pkg/declarativeconfig/transform/access_scope.go b/pkg/declarativeconfig/transform/access_scope.go index 82e41c474edbf..8ca08527b1d96 100644 --- a/pkg/declarativeconfig/transform/access_scope.go +++ b/pkg/declarativeconfig/transform/access_scope.go @@ -87,7 +87,7 @@ func includedNamespacesFromScopeConfig(scope *declarativeconfig.AccessScope) []* } func labelSelectorsFromScopeConfig(labelSelectors []declarativeconfig.LabelSelector) ([]*storage.SetBasedLabelSelector, error) { - var setBasedLabelSelectors []*storage.SetBasedLabelSelector + setBasedLabelSelectors := make([]*storage.SetBasedLabelSelector, 0, len(labelSelectors)) var labelSelectorErrs *multierror.Error for _, ls := range labelSelectors { reqs := make([]*storage.SetBasedLabelSelector_Requirement, 0, len(ls.Requirements)) diff --git a/pkg/features/features.go b/pkg/features/features.go index d2f2d1e4356fd..e90aef28ef887 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -54,8 +54,9 @@ func sortEnvVars() []string { // LogFeatureFlags logs the global state of all features flags. func LogFeatureFlags() { - data := []interface{}{} - for _, envVar := range sortEnvVars() { + sortedVars := sortEnvVars() + data := make([]interface{}, 0, len(sortedVars)) + for _, envVar := range sortedVars { flag := Flags[envVar] data = append(data, logging.Any(flag.EnvVar(), flag.Enabled())) } diff --git a/pkg/gjson/row.go b/pkg/gjson/row.go index 4fa289f9a06a2..601c9b33f589b 100644 --- a/pkg/gjson/row.go +++ b/pkg/gjson/row.go @@ -486,7 +486,7 @@ func (ct *columnTree) createColumnFromColumnNodes(columnIndex int) []string { return nodes[i].index < nodes[j].index }) - var column []string + var column []string //nolint:prealloc // size depends on duplicateElems calls, unpredictable for _, node := range nodes { amount := node.countLeafNodes(0) diff --git a/pkg/grpc/server.go b/pkg/grpc/server.go index 1840da72286b5..fbfa7f58d037f 100644 --- a/pkg/grpc/server.go +++ b/pkg/grpc/server.go @@ -365,7 +365,8 @@ func (a *apiImpl) muxer(localConn *grpc.ClientConn) http.Handler { preAuthHTTPInterceptors = append(preAuthHTTPInterceptors, a.config.RateLimiter.GetHTTPInterceptor()) } - contextUpdaters := []contextutil.ContextUpdater{authn.ContextUpdater(a.config.IdentityExtractors...)} + contextUpdaters := make([]contextutil.ContextUpdater, 0, 1+len(a.config.PreAuthContextEnrichers)) + contextUpdaters = append(contextUpdaters, authn.ContextUpdater(a.config.IdentityExtractors...)) contextUpdaters = append(contextUpdaters, a.config.PreAuthContextEnrichers...) preAuthHTTPInterceptors = append(preAuthHTTPInterceptors, contextutil.HTTPInterceptor(contextUpdaters...)) diff --git a/pkg/images/utils/convert_utils.go b/pkg/images/utils/convert_utils.go index 489d4464f9886..f0afcdde1cea4 100644 --- a/pkg/images/utils/convert_utils.go +++ b/pkg/images/utils/convert_utils.go @@ -48,7 +48,7 @@ func ConvertToV1(image *storage.ImageV2, names ...*storage.ImageName) *storage.I // ConvertNotesToV1 converts a list of storage.ImageV2_Note to a list of storage.Image_Note. func ConvertNotesToV1(notes []storage.ImageV2_Note) []storage.Image_Note { - res := make([]storage.Image_Note, 0) + res := make([]storage.Image_Note, 0, len(notes)) for _, note := range notes { res = append(res, storage.Image_Note(note.Number())) } @@ -110,7 +110,7 @@ func convertToV2(image *storage.Image, nameOverride *storage.ImageName) *storage } func ConvertNotesToV2(notes []storage.Image_Note) []storage.ImageV2_Note { - res := make([]storage.ImageV2_Note, 0) + res := make([]storage.ImageV2_Note, 0, len(notes)) for _, note := range notes { res = append(res, storage.ImageV2_Note(note.Number())) } diff --git a/pkg/k8srbac/policy_rule_field_set.go b/pkg/k8srbac/policy_rule_field_set.go index 5120ad2a5d4fd..d375098118b53 100644 --- a/pkg/k8srbac/policy_rule_field_set.go +++ b/pkg/k8srbac/policy_rule_field_set.go @@ -1,6 +1,8 @@ package k8srbac import ( + "slices" + "github.com/stackrox/rox/generated/storage" ) @@ -36,9 +38,7 @@ func (k *policyRuleFieldSet) Merge(to, from *storage.PolicyRule) bool { } else if fIndex == len(k.fields)-1 { // all but last field. matchFields = k.fields[:len(k.fields)-1] } else { // all but some middle field. - matchFields = make([]PolicyRuleField, fIndex) - copy(matchFields, k.fields[:fIndex]) - matchFields = append(matchFields, k.fields[fIndex+1:]...) + matchFields = slices.Concat(k.fields[:fIndex], k.fields[fIndex+1:]) } if NewPolicyRuleFieldSet(matchFields...).Equals(to, from) { mergeField.Merge(to, from) diff --git a/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go b/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go index 0f84d8ac25317..5deff357c4ee4 100644 --- a/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go +++ b/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go @@ -150,7 +150,7 @@ type Rows struct { // to be used as sql driver.Rows. // Use Sqlmock.NewRows instead if using a custom converter func NewRows(columns []string) *Rows { - var coldefs []pgconn.FieldDescription + coldefs := make([]pgconn.FieldDescription, 0, len(columns)) for _, column := range columns { coldefs = append(coldefs, pgconn.FieldDescription{Name: column}) } diff --git a/pkg/netutil/cidr.go b/pkg/netutil/cidr.go index 67f6cef698d02..67d5b97923fba 100644 --- a/pkg/netutil/cidr.go +++ b/pkg/netutil/cidr.go @@ -2,6 +2,7 @@ package netutil import ( "net" + "slices" "github.com/stackrox/rox/pkg/utils" ) @@ -16,9 +17,7 @@ func MustParseCIDR(cidr string) *net.IPNet { // IsIPNetOverlapingPrivateRange checks if network overlaps with private subnets func IsIPNetOverlapingPrivateRange(ipNet *net.IPNet) bool { - var privateSubnets []*net.IPNet - privateSubnets = append(privateSubnets, IPv4PrivateNetworks...) - privateSubnets = append(privateSubnets, IPv6PrivateNetworks...) + privateSubnets := slices.Concat(IPv4PrivateNetworks, IPv6PrivateNetworks) return AnyOverlap(ipNet, privateSubnets) } diff --git a/pkg/netutil/private_subnet.go b/pkg/netutil/private_subnet.go index 7345ce0912b85..ed50802a3b989 100644 --- a/pkg/netutil/private_subnet.go +++ b/pkg/netutil/private_subnet.go @@ -1,6 +1,8 @@ package netutil -import "net" +import ( + "net" +) var ( // IPv4PrivateNetworks holds RFC1918 addresses plus other reserved IPv4 ranges. @@ -44,11 +46,10 @@ var ( // GetPrivateSubnets returns a slice of IPv4 and IPv6 addresses considered as private ranges including localhost addresses. func GetPrivateSubnets() []*net.IPNet { - var subnets []*net.IPNet - subnets = append(subnets, IPv4PrivateNetworks...) - subnets = append(subnets, IPv4LocalHost) - subnets = append(subnets, IPv6PrivateNetworks...) - subnets = append(subnets, IPv6LocalHost) - subnets = append(subnets, IPv4MappedIPv6Loopback) - return subnets + result := make([]*net.IPNet, 0, len(IPv4PrivateNetworks)+len(IPv6PrivateNetworks)+3) + result = append(result, IPv4PrivateNetworks...) + result = append(result, IPv4LocalHost) + result = append(result, IPv6PrivateNetworks...) + result = append(result, IPv6LocalHost, IPv4MappedIPv6Loopback) + return result } diff --git a/pkg/networkgraph/tree/radix.go b/pkg/networkgraph/tree/radix.go index 3af1768dbf0a3..369ca078e19eb 100644 --- a/pkg/networkgraph/tree/radix.go +++ b/pkg/networkgraph/tree/radix.go @@ -2,6 +2,7 @@ package tree import ( "net" + "slices" "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" @@ -175,10 +176,9 @@ func (t *nRadixTree) getSuccessorWithValsNoLock(startNode *nRadixNode, curr *nRa return []*storage.NetworkEntityInfo{curr.value} } - var ret []*storage.NetworkEntityInfo - ret = append(ret, t.getSuccessorWithValsNoLock(startNode, curr.left)...) - ret = append(ret, t.getSuccessorWithValsNoLock(startNode, curr.right)...) - return ret + left := t.getSuccessorWithValsNoLock(startNode, curr.left) + right := t.getSuccessorWithValsNoLock(startNode, curr.right) + return slices.Concat(left, right) } func (t *nRadixTree) Get(key string) *storage.NetworkEntityInfo { diff --git a/pkg/postgres/pgadmin/admin_utils.go b/pkg/postgres/pgadmin/admin_utils.go index 1de44acc1480d..36cc0eff5b156 100644 --- a/pkg/postgres/pgadmin/admin_utils.go +++ b/pkg/postgres/pgadmin/admin_utils.go @@ -57,15 +57,11 @@ func DropDB(sourceMap map[string]string, adminConfig *postgres.Config, databaseN utils.Should(errors.New("unexpected call, should not try to drop a database from an external Postgres.")) } - // Set the options for pg_dump from the connection config - options := []string{ - "-f", - "--if-exists", - databaseName, - } - - // Get the common DB connection info - options = append(options, GetConnectionOptions(adminConfig)...) + // Set the options for dropdb including the connection config + connectionOptions := GetConnectionOptions(adminConfig) + options := make([]string, 0, 3+len(connectionOptions)) + options = append(options, "-f", "--if-exists", databaseName) + options = append(options, connectionOptions...) cmd := exec.Command("dropdb", options...) @@ -87,15 +83,11 @@ func CreateDB(sourceMap map[string]string, adminConfig *postgres.Config, dbTempl log.Infof("CreateDB %q", dbName) - // Set the options for pg_dump from the connection config - options := []string{ - "-T", - dbTemplate, - dbName, - } - - // Get the common DB connection info - options = append(options, GetConnectionOptions(adminConfig)...) + // Set the options for createdb including the connection config + connectionOptions := GetConnectionOptions(adminConfig) + options := make([]string, 0, 3+len(connectionOptions)) + options = append(options, "-T", dbTemplate, dbName) + options = append(options, connectionOptions...) cmd := exec.Command("createdb", options...) diff --git a/pkg/postgres/schema/all.go b/pkg/postgres/schema/all.go index 184770ff0f887..c28e328b9c0b0 100644 --- a/pkg/postgres/schema/all.go +++ b/pkg/postgres/schema/all.go @@ -67,7 +67,7 @@ func getAllRegisteredTablesInOrder() []*registeredTable { } slices.Sort(tables) - var rts []*registeredTable + var rts []*registeredTable //nolint:prealloc // recursive with unpredictable size for _, table := range tables { rts = append(rts, getRegisteredTablesFor(visited, table)...) } @@ -174,7 +174,7 @@ func GetAllIndexDefinitions() []*postgres.IndexDefinition { } func flattenIndexes(stmt *postgres.CreateStmts) []*postgres.IndexDefinition { - var result []*postgres.IndexDefinition + var result []*postgres.IndexDefinition //nolint:prealloc // recursive index flattening, unpredictable size result = append(result, stmt.Indexes...) for _, child := range stmt.Children { result = append(result, flattenIndexes(child)...) diff --git a/pkg/postgres/walker/walker.go b/pkg/postgres/walker/walker.go index 42ced81fa6442..fcfe5a6e82e1e 100644 --- a/pkg/postgres/walker/walker.go +++ b/pkg/postgres/walker/walker.go @@ -3,6 +3,7 @@ package walker import ( "fmt" "reflect" + "slices" "strings" "github.com/stackrox/rox/pkg/postgres" @@ -82,7 +83,7 @@ func addCommonFields(s *Schema, parentPrimaryKeys ...Field) { } } else { // Child table: add parent FK fields and idx column. - var additionalFields []Field + additionalFields := make([]Field, 0, len(parentPrimaryKeys)+1) for _, parentPrimaryKey := range parentPrimaryKeys { var columnNameInChild string var columnNameInChildForCodeVariables string @@ -114,7 +115,7 @@ func addCommonFields(s *Schema, parentPrimaryKeys ...Field) { }) } additionalFields = append(additionalFields, getIdxField(s)) - s.Fields = append(additionalFields, s.Fields...) + s.Fields = slices.Concat(additionalFields, s.Fields) } if len(s.Children) > 0 { diff --git a/pkg/search/postgres/query/numeric_query.go b/pkg/search/postgres/query/numeric_query.go index 154e9d550eef3..84303c994779a 100644 --- a/pkg/search/postgres/query/numeric_query.go +++ b/pkg/search/postgres/query/numeric_query.go @@ -24,7 +24,7 @@ var ( } validPrefixesSortedByLengthDec = func() []string { - var validPrefixes []string + validPrefixes := make([]string, 0, 2*len(prefixesAndInversions)+2) for _, pAndI := range prefixesAndInversions { validPrefixes = append(validPrefixes, pAndI.prefix) validPrefixes = append(validPrefixes, pAndI.inversion) diff --git a/pkg/testutils/centralgrpc/connect_to_central.go b/pkg/testutils/centralgrpc/connect_to_central.go index 6e138a9e82250..ff46258b72862 100644 --- a/pkg/testutils/centralgrpc/connect_to_central.go +++ b/pkg/testutils/centralgrpc/connect_to_central.go @@ -166,11 +166,9 @@ func grpcConnectionToCentral(t testutils.T, optsFuncs ...func(options *clientcon grpc_retry.WithRetriable(shouldRetryForTests), } - grpcDialOpts := []grpc.DialOption{} - // Add logging interceptors if the test object supports logging (e.g., *testing.T) // Logging interceptor first, then retry - this ensures we log each retry attempt - grpcDialOpts = append(grpcDialOpts, + grpcDialOpts := []grpc.DialOption{ grpc.WithChainUnaryInterceptor( loggingUnaryInterceptor(t), grpc_retry.UnaryClientInterceptor(retryOpts...), @@ -179,7 +177,7 @@ func grpcConnectionToCentral(t testutils.T, optsFuncs ...func(options *clientcon loggingStreamInterceptor(t), grpc_retry.StreamClientInterceptor(retryOpts...), ), - ) + } conn, err := clientconn.GRPCConnection(context.Background(), mtls.CentralSubject, endpoint, opts, grpcDialOpts...) require.NoError(t, err) diff --git a/pkg/testutils/matchers.go b/pkg/testutils/matchers.go index 5c399103cb5da..09ef143eee387 100644 --- a/pkg/testutils/matchers.go +++ b/pkg/testutils/matchers.go @@ -150,7 +150,7 @@ func AssertionMatcher(assertFn interface{}, args ...interface{}) gomock.Matcher } func (m *assertionMatcher) String() string { - var argStrings []string + argStrings := make([]string, 0, len(m.staticArgs)) for _, argVal := range m.staticArgs { argStrings = append(argStrings, fmt.Sprintf("%s", argVal.Interface())) } From 06e8adc61138975a1240a228a2a49846ef7b0918 Mon Sep 17 00:00:00 2001 From: Charmik Sheth <101146970+charmik-redhat@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:06:42 -0700 Subject: [PATCH 077/638] ROX-36247: Refactor scheduler for multiple report queues (#22392) Co-authored-by: Claude Opus 4.6 --- .../scheduler/v2/reportqueue/report_queue.go | 114 ++++++++ .../v2/reportqueue/report_queue_test.go | 182 +++++++++++++ .../reports/scheduler/v2/scheduler_impl.go | 255 ++++++++---------- .../scheduler/v2/scheduler_impl_test.go | 68 +---- central/reports/scheduler/v2/singleton.go | 1 + pkg/queue/queue.go | 25 ++ pkg/queue/queue_test.go | 33 +++ 7 files changed, 480 insertions(+), 198 deletions(-) create mode 100644 central/reports/scheduler/v2/reportqueue/report_queue.go create mode 100644 central/reports/scheduler/v2/reportqueue/report_queue_test.go diff --git a/central/reports/scheduler/v2/reportqueue/report_queue.go b/central/reports/scheduler/v2/reportqueue/report_queue.go new file mode 100644 index 0000000000000..e94cebe26ba1e --- /dev/null +++ b/central/reports/scheduler/v2/reportqueue/report_queue.go @@ -0,0 +1,114 @@ +package reportqueue + +import ( + "context" + + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/queue" + "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/sync" +) + +// ReportQueue is a thread-safe FIFO queue of report requests. It tracks which +// report configurations are currently running so that at most one report per +// config executes at a time, and stores cancel functions for running reports +// to support user-initiated cancellation. +type ReportQueue struct { + mu sync.Mutex + queue *queue.Queue[*reportGen.ReportRequest] + runningConfigs set.StringSet + jobIDToCancelFunc map[string]context.CancelCauseFunc +} + +// New creates a new empty ReportQueue. +func New() *ReportQueue { + return &ReportQueue{ + queue: queue.NewQueue[*reportGen.ReportRequest](), + runningConfigs: set.NewStringSet(), + jobIDToCancelFunc: make(map[string]context.CancelCauseFunc), + } +} + +// Enqueue adds a report request to the back of the queue. +// The underlying queue is internally synchronized, so no additional locking is needed. +func (q *ReportQueue) Enqueue(req *reportGen.ReportRequest) { + q.queue.Push(req) +} + +// Dequeue removes and returns the first runnable report request from the queue. +// VIEW_BASED reports are always runnable. Config-based reports are runnable only +// if their config ID is not in the running set. Returns nil if no runnable +// request is found. For non-VIEW_BASED reports, the config ID is added to the +// running set upon dequeue. +func (q *ReportQueue) Dequeue() *reportGen.ReportRequest { + q.mu.Lock() + defer q.mu.Unlock() + + req, ok := q.queue.PullWithPred(func(req *reportGen.ReportRequest) bool { + if req.ReportSnapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_VIEW_BASED { + return true + } + return !q.runningConfigs.Contains(req.ReportSnapshot.GetReportConfigurationId()) + }) + if !ok { + return nil + } + if req.ReportSnapshot.GetReportStatus().GetReportRequestType() != storage.ReportStatus_VIEW_BASED { + q.runningConfigs.Add(req.ReportSnapshot.GetReportConfigurationId()) + } + return req +} + +// Remove removes a queued (not yet running) request by report snapshot ID. +// Returns the removed request, or nil if not found. +// The underlying queue is internally synchronized, so no additional locking is needed. +func (q *ReportQueue) Remove(reportID string) *reportGen.ReportRequest { + req, _ := q.queue.PullWithPred(func(req *reportGen.ReportRequest) bool { + return req.ReportSnapshot.GetReportId() == reportID + }) + return req +} + +// MarkReportDoneForConfig removes a config ID from the running set, allowing +// another report for the same config to be dequeued. +func (q *ReportQueue) MarkReportDoneForConfig(configID string) { + q.mu.Lock() + defer q.mu.Unlock() + q.runningConfigs.Remove(configID) +} + +// AddCancelFunc stores a cancel function for a running report, keyed by report ID. +func (q *ReportQueue) AddCancelFunc(reportID string, cancel context.CancelCauseFunc) { + q.mu.Lock() + defer q.mu.Unlock() + q.jobIDToCancelFunc[reportID] = cancel +} + +// RemoveCancelFunc removes the cancel function for the given report ID. +func (q *ReportQueue) RemoveCancelFunc(reportID string) { + q.mu.Lock() + defer q.mu.Unlock() + delete(q.jobIDToCancelFunc, reportID) +} + +// TryCancel calls the cancel function for the given report ID with +// ErrUserCancelled as the cause. Returns true if a cancel function was found +// and invoked. +func (q *ReportQueue) TryCancel(reportID string) bool { + cancel := concurrency.WithLock1(&q.mu, func() context.CancelCauseFunc { + return q.jobIDToCancelFunc[reportID] + }) + if cancel == nil { + return false + } + cancel(reportGen.ErrUserCancelled) + return true +} + +// Len returns the number of items in the queue. +// The underlying queue is internally synchronized, so no additional locking is needed. +func (q *ReportQueue) Len() int { + return q.queue.Len() +} diff --git a/central/reports/scheduler/v2/reportqueue/report_queue_test.go b/central/reports/scheduler/v2/reportqueue/report_queue_test.go new file mode 100644 index 0000000000000..73ead9e751a86 --- /dev/null +++ b/central/reports/scheduler/v2/reportqueue/report_queue_test.go @@ -0,0 +1,182 @@ +package reportqueue + +import ( + "context" + "testing" + + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeRequest(reportID, configID string, requestType storage.ReportStatus_RunMethod) *reportGen.ReportRequest { + return &reportGen.ReportRequest{ + ReportSnapshot: &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: configID, + ReportStatus: &storage.ReportStatus{ + ReportRequestType: requestType, + }, + }, + } +} + +func TestEnqueueDequeueFIFO(t *testing.T) { + q := New() + + req1 := makeRequest("r1", "c1", storage.ReportStatus_ON_DEMAND) + req2 := makeRequest("r2", "c2", storage.ReportStatus_ON_DEMAND) + req3 := makeRequest("r3", "c3", storage.ReportStatus_SCHEDULED) + + q.Enqueue(req1) + q.Enqueue(req2) + q.Enqueue(req3) + assert.Equal(t, 3, q.Len()) + + got := q.Dequeue() + assert.Equal(t, "r1", got.ReportSnapshot.GetReportId()) + got = q.Dequeue() + assert.Equal(t, "r2", got.ReportSnapshot.GetReportId()) + got = q.Dequeue() + assert.Equal(t, "r3", got.ReportSnapshot.GetReportId()) + + assert.Nil(t, q.Dequeue()) + assert.Equal(t, 0, q.Len()) +} + +func TestDequeueSkipsRunningConfig(t *testing.T) { + q := New() + + req1 := makeRequest("r1", "c1", storage.ReportStatus_ON_DEMAND) + req2 := makeRequest("r2", "c1", storage.ReportStatus_ON_DEMAND) + req3 := makeRequest("r3", "c2", storage.ReportStatus_ON_DEMAND) + + q.Enqueue(req1) + q.Enqueue(req2) + q.Enqueue(req3) + + // Dequeue r1 → c1 is now in runningConfigs + got := q.Dequeue() + assert.Equal(t, "r1", got.ReportSnapshot.GetReportId()) + + // r2 is for c1 (running), should be skipped; r3 (c2) is returned + got = q.Dequeue() + assert.Equal(t, "r3", got.ReportSnapshot.GetReportId()) + + // r2 still blocked + assert.Nil(t, q.Dequeue()) + assert.Equal(t, 1, q.Len()) +} + +func TestDequeueViewBasedAlwaysRunnable(t *testing.T) { + q := New() + + req1 := makeRequest("r1", "c1", storage.ReportStatus_ON_DEMAND) + reqView := makeRequest("r-view", "", storage.ReportStatus_VIEW_BASED) + + q.Enqueue(req1) + q.Enqueue(reqView) + + // Dequeue r1 → c1 in runningConfigs + got := q.Dequeue() + assert.Equal(t, "r1", got.ReportSnapshot.GetReportId()) + + // VIEW_BASED always passes regardless of runningConfigs + got = q.Dequeue() + require.NotNil(t, got) + assert.Equal(t, "r-view", got.ReportSnapshot.GetReportId()) +} + +func TestMarkReportDoneForConfigUnblocks(t *testing.T) { + q := New() + + req1 := makeRequest("r1", "c1", storage.ReportStatus_ON_DEMAND) + req2 := makeRequest("r2", "c1", storage.ReportStatus_SCHEDULED) + + q.Enqueue(req1) + q.Enqueue(req2) + + got := q.Dequeue() + assert.Equal(t, "r1", got.ReportSnapshot.GetReportId()) + + // r2 blocked because c1 is running + assert.Nil(t, q.Dequeue()) + + q.MarkReportDoneForConfig("c1") + + // Now r2 should be dequeued + got = q.Dequeue() + require.NotNil(t, got) + assert.Equal(t, "r2", got.ReportSnapshot.GetReportId()) +} + +func TestRemoveFromQueue(t *testing.T) { + q := New() + + req1 := makeRequest("r1", "c1", storage.ReportStatus_ON_DEMAND) + req2 := makeRequest("r2", "c2", storage.ReportStatus_ON_DEMAND) + + q.Enqueue(req1) + q.Enqueue(req2) + + removed := q.Remove("r1") + require.NotNil(t, removed) + assert.Equal(t, "r1", removed.ReportSnapshot.GetReportId()) + assert.Equal(t, 1, q.Len()) + + // Removing non-existent returns nil + assert.Nil(t, q.Remove("nonexistent")) + assert.Equal(t, 1, q.Len()) +} + +func TestAddCancelFuncAndTryCancel(t *testing.T) { + q := New() + + var capturedCause error + ctx, cancel := context.WithCancelCause(context.Background()) + defer cancel(nil) + + q.AddCancelFunc("r1", cancel) + + cancelled := q.TryCancel("r1") + assert.True(t, cancelled) + + capturedCause = context.Cause(ctx) + assert.ErrorIs(t, capturedCause, reportGen.ErrUserCancelled) +} + +func TestTryCancelReturnsFalseForUnknown(t *testing.T) { + q := New() + assert.False(t, q.TryCancel("nonexistent")) +} + +func TestRemoveCancelFunc(t *testing.T) { + q := New() + + _, cancel := context.WithCancelCause(context.Background()) + + q.AddCancelFunc("r1", cancel) + q.RemoveCancelFunc("r1") + + // After removal, TryCancel should return false + assert.False(t, q.TryCancel("r1")) +} + +func TestViewBasedNotAddedToRunningConfigs(t *testing.T) { + q := New() + + reqView1 := makeRequest("r-view-1", "", storage.ReportStatus_VIEW_BASED) + reqView2 := makeRequest("r-view-2", "", storage.ReportStatus_VIEW_BASED) + + q.Enqueue(reqView1) + q.Enqueue(reqView2) + + got := q.Dequeue() + assert.Equal(t, "r-view-1", got.ReportSnapshot.GetReportId()) + + // Second VIEW_BASED should also dequeue without being blocked + got = q.Dequeue() + require.NotNil(t, got) + assert.Equal(t, "r-view-2", got.ReportSnapshot.GetReportId()) +} diff --git a/central/reports/scheduler/v2/scheduler_impl.go b/central/reports/scheduler/v2/scheduler_impl.go index d68256d5fd6ac..76b6dc706d4fa 100644 --- a/central/reports/scheduler/v2/scheduler_impl.go +++ b/central/reports/scheduler/v2/scheduler_impl.go @@ -1,7 +1,6 @@ package v2 import ( - "container/list" "context" "sync/atomic" "time" @@ -13,6 +12,7 @@ import ( "github.com/stackrox/rox/central/reports/common" reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + "github.com/stackrox/rox/central/reports/scheduler/v2/reportqueue" reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" "github.com/stackrox/rox/central/reports/validation" collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" @@ -21,13 +21,13 @@ import ( "github.com/stackrox/rox/pkg/dblock" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errox" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/postgres" "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/protoconv/schedule" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/search" - "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" "golang.org/x/sync/semaphore" "gopkg.in/robfig/cron.v2" @@ -39,6 +39,14 @@ var ( scheduledCtx = sac.WithAllAccess(context.Background()) ) +// queueGeneratorBinding pairs a report job queue with the generator that processes +// jobs from that queue. E.g., image CVE report job queue <-> image CVE report +// generator, node CVE report job queue <-> node CVE report generator. +type queueGeneratorBinding struct { + queue *reportqueue.ReportQueue + generator reportGen.ReportGenerator +} + type scheduler struct { // Used to map reportConfigs to their cron jobs. This is only used for scheduled reports, On-demand reports are directly added to reportsQueue reportConfigToEntryIDs map[string]cron.EntryID @@ -47,21 +55,20 @@ type scheduler struct { reportSnapshotStore reportSnapshotDS.DataStore collectionDatastore collectionDS.DataStore notifierDatastore notifierDS.DataStore - reportGenerator reportGen.ReportGenerator validator *validation.Validator - reportRequestsQueue *list.List + // Ordered list of queue-generator bindings. The main loop round-robins through + // these when selecting the next report to run. + queues []queueGeneratorBinding + nextQueueIdx int + // Maps report type to its queue for routing submissions. + queueByType map[storage.ReportSnapshot_ReportType]*reportqueue.ReportQueue - // Use to signal the scheduler to find and run a new report if a routine is available - // This signal is triggered when a new request is added to reportsQueue. It is also triggered when a report completes - // execution to inform the scheduler that a routine is free. The signal is reset when there is no report to run. + // Signals the scheduler to find and run a new report. Triggered when a new + // request is enqueued or when a report completes. Reset when no runnable report + // is found across any queue. readyForReports concurrency.Signal - // Stores config IDs for which a report is currently running. Used to make sure only one report per config runs at a time. - runningReportConfigs set.StringSet - // Stores cancel functions for running reports, keyed by report ID. - // Used to cancel in-flight database queries and blob store writes when a user cancels a PREPARING report. - runningReportCancels map[string]context.CancelCauseFunc - Schema *graphql.Schema + Schema *graphql.Schema /* Concurrency and synchronization related fields */ // isStarted will make sure only one scheduling routine runs for an instance of scheduler @@ -69,13 +76,10 @@ type scheduler struct { // isStopped will prevent scheduler from being re-started once it is stopped isStopped atomic.Bool - /* Concurrency and synchronization related fields */ stopper concurrency.Stopper // Use to synchronize access to reportConfigToEntryIDs map cronJobsLock sync.Mutex - // Use to synchronize access to reportsQueue, runningReportConfigs, and runningReportCancels - schedulerLock sync.Mutex // Use to lock any database tables if needed to prevent race conditions dbLock sync.Mutex // NOTE: Lock only one mutex at a time. Do not lock another mutex when one is already held. @@ -89,7 +93,8 @@ type scheduler struct { // New instantiates a new cron scheduler and supports adding and removing report requests func New(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotStore reportSnapshotDS.DataStore, collectionDatastore collectionDS.DataStore, notifierDatastore notifierDS.DataStore, - reportGenerator reportGen.ReportGenerator, validator *validation.Validator) Scheduler { + imageReportGenerator reportGen.ReportGenerator, nodeReportGenerator reportGen.ReportGenerator, + validator *validation.Validator) Scheduler { cronScheduler := cron.New() cronScheduler.Start() @@ -98,25 +103,40 @@ func New(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotStore rep panic(err) } return newSchedulerImpl(reportConfigDatastore, reportSnapshotStore, collectionDatastore, notifierDatastore, - reportGenerator, validator, cronScheduler, ourSchema) + imageReportGenerator, nodeReportGenerator, validator, cronScheduler, ourSchema) } func newSchedulerImpl(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotStore reportSnapshotDS.DataStore, collectionDatastore collectionDS.DataStore, notifierDatastore notifierDS.DataStore, - reportGenerator reportGen.ReportGenerator, validator *validation.Validator, cronScheduler *cron.Cron, + imageReportGenerator reportGen.ReportGenerator, nodeReportGenerator reportGen.ReportGenerator, + validator *validation.Validator, cronScheduler *cron.Cron, schema *graphql.Schema) *scheduler { + + imageQueue := reportqueue.New() + queues := []queueGeneratorBinding{ + {queue: imageQueue, generator: imageReportGenerator}, + } + queueByType := map[storage.ReportSnapshot_ReportType]*reportqueue.ReportQueue{ + storage.ReportSnapshot_VULNERABILITY: imageQueue, + } + + if features.NodeVulnerabilityReports.Enabled() && nodeReportGenerator != nil { + nodeQueue := reportqueue.New() + queues = append(queues, queueGeneratorBinding{queue: nodeQueue, generator: nodeReportGenerator}) + queueByType[storage.ReportSnapshot_NODE_VULNERABILITY] = nodeQueue + } + s := &scheduler{ reportConfigToEntryIDs: make(map[string]cron.EntryID), reportConfigDatastore: reportConfigDatastore, reportSnapshotStore: reportSnapshotStore, collectionDatastore: collectionDatastore, notifierDatastore: notifierDatastore, - reportGenerator: reportGenerator, validator: validator, - reportRequestsQueue: list.New(), + queues: queues, + nextQueueIdx: 0, + queueByType: queueByType, readyForReports: concurrency.NewSignal(), - runningReportConfigs: set.NewStringSet(), - runningReportCancels: make(map[string]context.CancelCauseFunc), Schema: schema, stopper: concurrency.NewStopper(), cron: cronScheduler, @@ -186,8 +206,8 @@ func (s *scheduler) runReports() { case <-s.stopper.Flow().StopRequested(): return case <-s.readyForReports.Done(): - reportRequest := s.selectNextRunnableReport() - if reportRequest == nil { + req, q, gen := s.selectNextJobRoundRobin() + if req == nil { s.readyForReports.Reset() continue } @@ -195,72 +215,37 @@ func (s *scheduler) runReports() { log.Errorf("Error acquiring semaphore to run new report: %v", err) continue } - log.Infof("Executing report '%s' at %v", reportRequest.ReportSnapshot.GetName(), time.Now().Format(time.RFC822)) - go s.runSingleReport(reportRequest) + log.Infof("Executing report '%s' at %v", req.ReportSnapshot.GetName(), time.Now().Format(time.RFC822)) + go s.runSingleReport(q, gen, req) } } } -func (s *scheduler) selectNextRunnableReport() *reportGen.ReportRequest { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - - request := findAndRemoveFromQueue(s.reportRequestsQueue, func(req *reportGen.ReportRequest) bool { - if req.ReportSnapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_VIEW_BASED { - return true +func (s *scheduler) selectNextJobRoundRobin() (*reportGen.ReportRequest, *reportqueue.ReportQueue, reportGen.ReportGenerator) { + n := len(s.queues) + for i := range n { + idx := (s.nextQueueIdx + i) % n + qb := s.queues[idx] + if req := qb.queue.Dequeue(); req != nil { + s.nextQueueIdx = (idx + 1) % n + return req, qb.queue, qb.generator } - return !s.runningReportConfigs.Contains(req.ReportSnapshot.GetReportConfigurationId()) - }) - if request == nil { - return nil - } - if request.ReportSnapshot.GetVulnReportFilters() != nil { - s.runningReportConfigs.Add(request.ReportSnapshot.GetReportConfigurationId()) } - return request + return nil, nil, nil } -func (s *scheduler) runSingleReport(req *reportGen.ReportRequest) { +func (s *scheduler) runSingleReport(q *reportqueue.ReportQueue, gen reportGen.ReportGenerator, req *reportGen.ReportRequest) { defer s.readyForReports.Signal() defer s.concurrencySema.Release(1) - defer s.removeFromRunningReportConfigs(req.ReportSnapshot.GetReportConfigurationId()) + defer q.MarkReportDoneForConfig(req.ReportSnapshot.GetReportConfigurationId()) reportID := req.ReportSnapshot.GetReportId() ctx, cancel := context.WithCancelCause(context.Background()) - s.addRunningReportCancel(reportID, cancel) + q.AddCancelFunc(reportID, cancel) defer cancel(nil) - defer s.removeRunningReportCancel(reportID) + defer q.RemoveCancelFunc(reportID) - s.reportGenerator.ProcessReportRequest(ctx, req) -} - -func (s *scheduler) removeFromRunningReportConfigs(configID string) { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - s.runningReportConfigs.Remove(configID) -} - -func (s *scheduler) addRunningReportCancel(reportID string, cancel context.CancelCauseFunc) { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - s.runningReportCancels[reportID] = cancel -} - -func (s *scheduler) removeRunningReportCancel(reportID string) { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - delete(s.runningReportCancels, reportID) -} - -func (s *scheduler) tryCancelRunningReport(reportID string) bool { - cancel := concurrency.WithLock1(&s.schedulerLock, func() context.CancelCauseFunc { - return s.runningReportCancels[reportID] - }) - if cancel == nil { - return false - } - cancel(reportGen.ErrUserCancelled) - return true + gen.ProcessReportRequest(ctx, req) } // UpsertReportSchedule adds/updates the schedule at which reports for the given report config are executed. @@ -305,31 +290,29 @@ func (s *scheduler) RemoveReportSchedule(reportConfigID string) { // being prepared, its context is cancelled, which propagates cancellation to in-flight database // queries and blob store writes. func (s *scheduler) CancelReportRequest(ctx context.Context, reportID string) (bool, error) { - req := s.tryRemoveFromRequestQueue(reportID) - if req != nil { - req.ReportSnapshot.ReportStatus.ErrorMsg = reportGen.ErrUserCancelled.Error() - req.ReportSnapshot.ReportStatus.CompletedAt = protocompat.TimestampNow() - req.ReportSnapshot.ReportStatus.RunState = storage.ReportStatus_FAILURE - if err := s.reportSnapshotStore.UpdateReportSnapshot(ctx, req.ReportSnapshot); err != nil { - return false, errors.Wrapf(err, "Error updating report snapshot to FAILURE for report ID '%s'", reportID) + for _, qb := range s.queues { + req := qb.queue.Remove(reportID) + if req != nil { + req.ReportSnapshot.ReportStatus.ErrorMsg = reportGen.ErrUserCancelled.Error() + req.ReportSnapshot.ReportStatus.CompletedAt = protocompat.TimestampNow() + req.ReportSnapshot.ReportStatus.RunState = storage.ReportStatus_FAILURE + if err := s.reportSnapshotStore.UpdateReportSnapshot(ctx, req.ReportSnapshot); err != nil { + return false, errors.Wrapf(err, "Error updating report snapshot to FAILURE for report ID '%s'", reportID) + } + return true, nil } - return true, nil } - return s.tryCancelRunningReport(reportID), nil -} - -// Returns the removed ReportRequest if found, nil otherwise -func (s *scheduler) tryRemoveFromRequestQueue(reportID string) *reportGen.ReportRequest { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - - return findAndRemoveFromQueue(s.reportRequestsQueue, func(req *reportGen.ReportRequest) bool { - return req.ReportSnapshot.GetReportId() == reportID - }) + for _, qb := range s.queues { + if qb.queue.TryCancel(reportID) { + return true, nil + } + } + return false, nil } func (s *scheduler) CanSubmitReportRequest(user *storage.SlimUser, reportConfig *storage.ReportConfiguration) (bool, error) { - return s.doesUserHavePendingReport(reportConfig.GetId(), user.GetId()) + return s.doesUserHavePendingReport(reportConfig.GetId(), user.GetId(), + storage.ReportSnapshot_ReportType(reportConfig.GetType())) } // SubmitReportRequest submits a report execution request. The report request can be either for an on demand report or a scheduled report. @@ -341,6 +324,11 @@ func (s *scheduler) SubmitReportRequest(ctx context.Context, request *reportGen. return "", err } + q := s.queueForSnapshot(request.ReportSnapshot) + if q == nil { + return "", errors.New("node vulnerability reports are not enabled") + } + request.ReportSnapshot.ReportStatus.RunState = storage.ReportStatus_WAITING request.ReportSnapshot.ReportStatus.QueuedAt = protocompat.TimestampNow() request.ReportSnapshot.ReportId, err = s.validateAndPersistSnapshot(ctx, request.ReportSnapshot, reSubmission) @@ -348,16 +336,14 @@ func (s *scheduler) SubmitReportRequest(ctx context.Context, request *reportGen. return "", err } - s.appendToReportsQueue(request) + q.Enqueue(request) + s.readyForReports.Signal() return request.ReportSnapshot.GetReportId(), nil } -func (s *scheduler) appendToReportsQueue(req *reportGen.ReportRequest) { - s.schedulerLock.Lock() - defer s.schedulerLock.Unlock() - s.reportRequestsQueue.PushBack(req) - s.readyForReports.Signal() +func (s *scheduler) queueForSnapshot(snap *storage.ReportSnapshot) *reportqueue.ReportQueue { + return s.queueByType[snap.GetType()] } func (s *scheduler) reportClosure(reportConfig *storage.ReportConfiguration) func() { @@ -441,8 +427,12 @@ func (s *scheduler) queuePendingReports() { } func (s *scheduler) queueScheduledReports() { + reportTypes := []string{storage.ReportConfiguration_VULNERABILITY.String()} + if features.NodeVulnerabilityReports.Enabled() { + reportTypes = append(reportTypes, storage.ReportConfiguration_NODE_VULNERABILITY.String()) + } query := search.NewQueryBuilder(). - AddExactMatches(search.ReportType, storage.ReportConfiguration_VULNERABILITY.String()). + AddExactMatches(search.ReportType, reportTypes...). ProtoQuery() reportConfigs, err := s.reportConfigDatastore.GetReportConfigurations(scheduledCtx, query) if err != nil { @@ -468,8 +458,12 @@ func (s *scheduler) recoverMissedSchedules() { return } + reportTypes := []string{storage.ReportConfiguration_VULNERABILITY.String()} + if features.NodeVulnerabilityReports.Enabled() { + reportTypes = append(reportTypes, storage.ReportConfiguration_NODE_VULNERABILITY.String()) + } query := search.NewQueryBuilder(). - AddExactMatches(search.ReportType, storage.ReportConfiguration_VULNERABILITY.String()). + AddExactMatches(search.ReportType, reportTypes...). ProtoQuery() reportConfigs, err := s.reportConfigDatastore.GetReportConfigurations(scheduledCtx, query) if err != nil { @@ -575,28 +569,6 @@ func findPreviousFireTime(cronSchedule cron.Schedule, now time.Time) time.Time { return previousFire } -/* Utility Functions */ - -// findAndRemoveFromQueue will find the first element that matches the given predicate and returns ReportRequest from that element -// Elements with values that are not of type *reportGen.ReportRequest will be skipped. -// Note: This function does not lock the queue, so any locks to prevent race conditions must be taken by the caller. -func findAndRemoveFromQueue(reportRequestsQueue *list.List, pred func(req *reportGen.ReportRequest) bool) *reportGen.ReportRequest { - var toRemove *list.Element - cur := reportRequestsQueue.Front() - for cur != nil { - req, ok := cur.Value.(*reportGen.ReportRequest) - if ok && pred(req) { - toRemove = cur - break - } - cur = cur.Next() - } - if toRemove == nil { - return nil - } - return reportRequestsQueue.Remove(toRemove).(*reportGen.ReportRequest) -} - // Validate report snapshot and store it to db if validation succeeds. // Will return report_id if successful. // Validation will check if the user requesting the report doesn't already have a pending report for the same config @@ -605,8 +577,11 @@ func (s *scheduler) validateAndPersistSnapshot(ctx context.Context, snapshot *st defer s.dbLock.Unlock() var err error if !reSubmission { - if snapshot.GetVulnReportFilters() != nil && snapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_ON_DEMAND { - userHasAnotherReport, err := s.doesUserHavePendingReport(snapshot.GetReportConfigurationId(), snapshot.GetRequester().GetId()) + requestType := snapshot.GetReportStatus().GetReportRequestType() + reportType := snapshot.GetType() + + if requestType == storage.ReportStatus_ON_DEMAND { + userHasAnotherReport, err := s.doesUserHavePendingReport(snapshot.GetReportConfigurationId(), snapshot.GetRequester().GetId(), reportType) if err != nil { return "", err } @@ -616,9 +591,8 @@ func (s *scheduler) validateAndPersistSnapshot(ctx context.Context, snapshot *st } } - // if user has an existing view based report job then dont queue a new one - if snapshot.GetViewBasedVulnReportFilters() != nil { - userHasAnotherReport, err := s.doesUserHaveViewBasedPendingReport(snapshot.GetRequester().GetId()) + if requestType == storage.ReportStatus_VIEW_BASED { + userHasAnotherReport, err := s.doesUserHaveViewBasedPendingReport(snapshot.GetRequester().GetId(), reportType) if err != nil { return "", err } @@ -627,11 +601,11 @@ func (s *scheduler) validateAndPersistSnapshot(ctx context.Context, snapshot *st } } - // View-based reports are authorized at the gRPC layer with only Image+Deployment - // view permissions (no WorkflowAdministration write required). The snapshot datastore - // requires WorkflowAdministration write, so we elevate to a privileged context here. + // View-based reports are authorized at the gRPC layer with only view permissions + // (no WorkflowAdministration write required). The snapshot datastore requires + // WorkflowAdministration write, so we elevate to a privileged context here. persistCtx := ctx - if snapshot.GetViewBasedVulnReportFilters() != nil { + if requestType == storage.ReportStatus_VIEW_BASED { persistCtx = sac.WithAllAccess(ctx) } snapshot.ReportId, err = s.reportSnapshotStore.AddReportSnapshot(persistCtx, snapshot) @@ -645,27 +619,26 @@ func (s *scheduler) validateAndPersistSnapshot(ctx context.Context, snapshot *st return snapshot.GetReportId(), nil } -func (s *scheduler) doesUserHaveViewBasedPendingReport(userID string) (bool, error) { +func (s *scheduler) doesUserHaveViewBasedPendingReport(userID string, reportType storage.ReportSnapshot_ReportType) (bool, error) { query := search.NewQueryBuilder(). AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()). AddExactMatches(search.ReportRequestType, storage.ReportStatus_VIEW_BASED.String()). AddExactMatches(search.UserID, userID). + AddExactMatches(search.ReportType, reportType.String()). ProtoQuery() runningReports, err := s.reportSnapshotStore.Count(scheduledCtx, query) if err != nil { return false, err } - if runningReports > 0 { - return true, nil - } - return false, nil + return runningReports > 0, nil } -func (s *scheduler) doesUserHavePendingReport(configID string, userID string) (bool, error) { +func (s *scheduler) doesUserHavePendingReport(configID string, userID string, reportType storage.ReportSnapshot_ReportType) (bool, error) { query := search.NewQueryBuilder(). AddExactMatches(search.ReportConfigID, configID). AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()). AddExactMatches(search.ReportRequestType, storage.ReportStatus_ON_DEMAND.String()). + AddExactMatches(search.ReportType, reportType.String()). ProtoQuery() runningReports, err := s.reportSnapshotStore.SearchReportSnapshots(scheduledCtx, query) if err != nil { diff --git a/central/reports/scheduler/v2/scheduler_impl_test.go b/central/reports/scheduler/v2/scheduler_impl_test.go index 9afaf3d9aa369..85b68d2c08347 100644 --- a/central/reports/scheduler/v2/scheduler_impl_test.go +++ b/central/reports/scheduler/v2/scheduler_impl_test.go @@ -154,7 +154,7 @@ func TestQueueScheduledReportsSkipsEmptyResourceScope(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(mockReportConfigDS, nil, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(mockReportConfigDS, nil, nil, nil, nil, nil, nil, cronScheduler, nil) s.queueScheduledReports() // Only the two valid configs should have been scheduled @@ -165,54 +165,6 @@ func TestQueueScheduledReportsSkipsEmptyResourceScope(t *testing.T) { assert.NotContains(t, s.reportConfigToEntryIDs, "config-nil-scope") } -func TestCancelRunningReportCancelsContext(t *testing.T) { - ctrl := gomock.NewController(t) - mockReportGen := reportGenMocks.NewMockReportGenerator(ctrl) - - cronScheduler := cron.New() - cronScheduler.Start() - defer cronScheduler.Stop() - - s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, cronScheduler, nil) - - started := make(chan struct{}) - done := make(chan struct{}) - var capturedCtx context.Context - - mockReportGen.EXPECT().ProcessReportRequest(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, req *reportGen.ReportRequest) { - capturedCtx = ctx - close(started) - <-done - }, - ) - - req := &reportGen.ReportRequest{ - ReportSnapshot: &storage.ReportSnapshot{ - ReportId: "test-report-id", - ReportConfigurationId: "test-config-id", - ReportStatus: &storage.ReportStatus{ - RunState: storage.ReportStatus_WAITING, - }, - }, - } - - // Acquire the semaphore since runSingleReport releases it - err := s.concurrencySema.Acquire(context.Background(), 1) - assert.NoError(t, err) - - go s.runSingleReport(req) - <-started - - cancelled := s.tryCancelRunningReport("test-report-id") - assert.True(t, cancelled) - - assert.Error(t, capturedCtx.Err()) - assert.ErrorIs(t, context.Cause(capturedCtx), reportGen.ErrUserCancelled) - - close(done) -} - func TestCancelReportRequestCancelsRunningReport(t *testing.T) { ctrl := gomock.NewController(t) mockReportGen := reportGenMocks.NewMockReportGenerator(ctrl) @@ -221,7 +173,8 @@ func TestCancelReportRequestCancelsRunningReport(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, nil, cronScheduler, nil) + imageQueue := s.queues[0].queue started := make(chan struct{}) done := make(chan struct{}) @@ -249,7 +202,7 @@ func TestCancelReportRequestCancelsRunningReport(t *testing.T) { err := s.concurrencySema.Acquire(context.Background(), 1) assert.NoError(t, err) - go s.runSingleReport(req) + go s.runSingleReport(imageQueue, mockReportGen, req) <-started // Report is not in queue (it's running), so CancelReportRequest should cancel the running context @@ -268,7 +221,7 @@ func TestCancelReportRequestReturnsFalseForUnknownReport(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, nil, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, nil, nil, nil, nil, nil, nil, cronScheduler, nil) cancelled, err := s.CancelReportRequest(context.Background(), "nonexistent-id") assert.NoError(t, err) @@ -337,10 +290,10 @@ func TestQueuePendingReports(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(mockReportConfigDS, mockSnapshotStore, mockCollectionDS, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(mockReportConfigDS, mockSnapshotStore, mockCollectionDS, nil, nil, nil, nil, cronScheduler, nil) s.queuePendingReports() - assert.Equal(t, 2, s.reportRequestsQueue.Len()) + assert.Equal(t, 2, s.queues[0].queue.Len()) } func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { @@ -351,7 +304,8 @@ func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, mockSnapshotStore, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, mockSnapshotStore, nil, nil, nil, nil, nil, cronScheduler, nil) + imageQueue := s.queues[0].queue req := &reportGen.ReportRequest{ ReportSnapshot: &storage.ReportSnapshot{ @@ -363,7 +317,7 @@ func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { }, }, } - s.reportRequestsQueue.PushBack(req) + imageQueue.Enqueue(req) mockSnapshotStore.EXPECT().UpdateReportSnapshot(gomock.Any(), gomock.Any()). DoAndReturn(func(_ context.Context, snap *storage.ReportSnapshot) error { @@ -376,5 +330,5 @@ func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { cancelled, err := s.CancelReportRequest(context.Background(), "waiting-report-id") assert.NoError(t, err) assert.True(t, cancelled) - assert.Equal(t, 0, s.reportRequestsQueue.Len()) + assert.Equal(t, 0, imageQueue.Len()) } diff --git a/central/reports/scheduler/v2/singleton.go b/central/reports/scheduler/v2/singleton.go index 8d1788568e943..a0abd532d8cfb 100644 --- a/central/reports/scheduler/v2/singleton.go +++ b/central/reports/scheduler/v2/singleton.go @@ -24,6 +24,7 @@ func initialize() { collectionDatastore, notifierDS.Singleton(), reportGen.Singleton(), + nil, validation.Singleton(), ) } diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go index 7ac0192d4b866..3d34a7ddd5d13 100644 --- a/pkg/queue/queue.go +++ b/pkg/queue/queue.go @@ -173,6 +173,31 @@ func (q *Queue[T]) Push(item T) { } } +// PullWithPred removes and returns the first element for which pred returns +// true, scanning front-to-back. Returns the removed item and true, or the nil +// value of T and false if no element matched. +func (q *Queue[T]) PullWithPred(pred func(T) bool) (T, bool) { + q.mutex.Lock() + defer q.mutex.Unlock() + + for e := q.queue.Front(); e != nil; e = e.Next() { + item := e.Value.(T) + if pred(item) { + q.queue.Remove(e) + if q.queue.Len() == 0 { + q.notEmptySignal.Reset() + } + if q.counterMetric != nil { + q.counterMetric.WithLabelValues(metrics.Remove.String()).Inc() + } + return item, true + } + } + + var nilT T + return nilT, false +} + // Len returns the number of elements in the queue. func (q *Queue[T]) Len() int { q.mutex.Lock() diff --git a/pkg/queue/queue_test.go b/pkg/queue/queue_test.go index 2a6663d89a978..4b8ecb7c92579 100644 --- a/pkg/queue/queue_test.go +++ b/pkg/queue/queue_test.go @@ -41,6 +41,39 @@ func TestQueue(t *testing.T) { assert.Nil(t, q.PullBlocking(context.Background())) } +func TestPullWithPred(t *testing.T) { + q := NewQueue[int]() + q.Push(1) + q.Push(2) + q.Push(3) + q.Push(4) + + item, ok := q.PullWithPred(func(v int) bool { return v%2 == 0 }) + assert.True(t, ok) + assert.Equal(t, 2, item) + assert.Equal(t, 3, q.Len()) + + item, ok = q.PullWithPred(func(v int) bool { return v > 3 }) + assert.True(t, ok) + assert.Equal(t, 4, item) + assert.Equal(t, 2, q.Len()) + + _, ok = q.PullWithPred(func(v int) bool { return v > 100 }) + assert.False(t, ok) + assert.Equal(t, 2, q.Len()) + + // Remaining items are 1, 3 + assert.Equal(t, 1, q.Pull()) + assert.Equal(t, 3, q.Pull()) + assert.Equal(t, 0, q.Len()) +} + +func TestPullWithPredEmptyQueue(t *testing.T) { + q := NewQueue[int]() + _, ok := q.PullWithPred(func(v int) bool { return true }) + assert.False(t, ok) +} + func TestQueueSeq(t *testing.T) { t.Run("Basic Iteration", func(t *testing.T) { q := NewQueue[int]() From 366fc8b687388d015fec6968703000c8e8cb833b Mon Sep 17 00:00:00 2001 From: Kirk Bater Date: Wed, 26 Aug 2026 03:45:04 -0400 Subject: [PATCH 078/638] refactor: Allows podman directly in make as alternative (#21593) Co-authored-by: Alex Vulaj --- Makefile | 37 +++++++++++++++++++++++++------------ scripts/check-debugger.sh | 8 +++++--- scripts/connect-ui.sh | 6 ++++-- scripts/docker-build.sh | 9 +++++++-- scripts/ensure_image.sh | 14 ++++++++------ 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 9e5daa549c260..9b913f0d2e4e9 100644 --- a/Makefile +++ b/Makefile @@ -8,15 +8,28 @@ BENCHTIME ?= 1x BENCHTIMEOUT ?= 20m BENCHCOUNT ?= 1 +CONTAINER_ENGINE ?= docker +export CONTAINER_ENGINE + podman = +# If CONTAINER_ENGINE is explicitly set to podman, detect it +ifeq ($(CONTAINER_ENGINE),podman) + podman = yes # docker --version might not contain any traces of podman in the latest # version, search for more output -ifneq (,$(findstring podman,$(shell docker --version 2>/dev/null))) +else ifneq (,$(findstring podman,$(shell $(CONTAINER_ENGINE) --version 2>/dev/null))) + podman = yes +else ifneq (,$(findstring Podman,$(shell $(CONTAINER_ENGINE) version 2>/dev/null))) podman = yes endif -ifneq (,$(findstring Podman,$(shell docker version 2>/dev/null))) +# if docker is not available and podman is installed, use podman instead +ifeq (,$(shell command -v docker 2>/dev/null)) +ifneq (,$(shell command -v podman 2>/dev/null)) podman = yes + CONTAINER_ENGINE=podman endif +endif + ifdef podman # Disable selinux for local podman builds. @@ -236,7 +249,7 @@ config-controller-build-nodeps: .PHONY: fast-central fast-central: deps @echo "+ $@" - docker run $(DOCKER_OPTS) -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make fast-central-build + $(CONTAINER_ENGINE) run $(DOCKER_OPTS) -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make fast-central-build $(SILENT)$(BASE_DIR)/scripts/k8s/kill-pod.sh central # fast is a dev mode options when using local dev @@ -254,7 +267,7 @@ fast-sensor-kubernetes: sensor-kubernetes-build-dockerized .PHONY: fast-migrator fast-migrator: @echo "+ $@" - docker run $(DOCKER_OPTS) -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make fast-migrator-build + $(CONTAINER_ENGINE) run $(DOCKER_OPTS) -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make fast-migrator-build .PHONY: fast-migrator-build fast-migrator-build: migrator-build-nodeps @@ -469,8 +482,8 @@ bin/$(HOST_OS)_$(GOARCH)/admission-control: build-prep .PHONY: build-volumes build-volumes: $(SILENT)mkdir -p $(CURDIR)/linux-gocache - $(SILENT)docker volume inspect $(GOPATH_VOLUME_NAME) >/dev/null 2>&1 || docker volume create $(GOPATH_VOLUME_NAME) - $(SILENT)docker volume inspect $(GOCACHE_VOLUME_NAME) >/dev/null 2>&1 || docker volume create $(GOCACHE_VOLUME_NAME) + $(SILENT)$(CONTAINER_ENGINE) volume inspect $(GOPATH_VOLUME_NAME) >/dev/null 2>&1 || $(CONTAINER_ENGINE) volume create $(GOPATH_VOLUME_NAME) + $(SILENT)$(CONTAINER_ENGINE) volume inspect $(GOCACHE_VOLUME_NAME) >/dev/null 2>&1 || $(CONTAINER_ENGINE) volume create $(GOCACHE_VOLUME_NAME) .PHONY: main-build main-build: build-prep main-build-dockerized @@ -479,12 +492,12 @@ main-build: build-prep main-build-dockerized .PHONY: sensor-build-dockerized sensor-build-dockerized: build-volumes @echo "+ $@" - docker run $(DOCKER_OPTS) --rm -e CI -e BUILD_TAG -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make sensor-build + $(CONTAINER_ENGINE) run $(DOCKER_OPTS) --rm -e CI -e BUILD_TAG -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make sensor-build .PHONY: sensor-kubernetes-build-dockerized sensor-kubernetes-build-dockerized: build-volumes @echo "+ $@" - docker run $(DOCKER_OPTS) -e CI -e BUILD_TAG -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make sensor-kubernetes-build + $(CONTAINER_ENGINE) run $(DOCKER_OPTS) -e CI -e BUILD_TAG -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make sensor-kubernetes-build .PHONY: sensor-build sensor-build: @@ -498,7 +511,7 @@ sensor-kubernetes-build: .PHONY: main-build-dockerized main-build-dockerized: build-volumes @echo "+ $@" - docker run $(DOCKER_OPTS) -i -e RACE -e CI -e BUILD_TAG -e SHORTCOMMIT -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make main-build-nodeps + $(CONTAINER_ENGINE) run $(DOCKER_OPTS) -i -e RACE -e CI -e BUILD_TAG -e SHORTCOMMIT -e GOTAGS -e DEBUG_BUILD -e CGO_ENABLED --rm $(GOPATH_WD_OVERRIDES) $(LOCAL_VOLUME_ARGS) $(BUILD_IMAGE) make main-build-nodeps .PHONY: main-build-nodeps main-build-nodeps: @@ -725,7 +738,7 @@ scale-image: scale-build clean-image cp bin/linux_$(GOARCH)/profiler scale/image/rhel/bin/profiler cp bin/linux_$(GOARCH)/chaos scale/image/rhel/bin/chaos chmod +w scale/image/rhel/bin/* - docker build \ + $(CONTAINER_ENGINE) build \ -t stackrox/scale:$(TAG) \ -t quay.io/rhacs-eng/scale:$(TAG) \ -f scale/image/Dockerfile scale @@ -734,7 +747,7 @@ webhookserver-image: webhookserver-build -mkdir webhookserver/bin cp bin/linux_$(GOARCH)/webhookserver webhookserver/bin/webhookserver chmod +w webhookserver/bin/webhookserver - docker build \ + $(CONTAINER_ENGINE) build \ -t stackrox/webhookserver:1.2 \ -t quay.io/rhacs-eng/webhookserver:1.2 \ -f webhookserver/Dockerfile webhookserver @@ -743,7 +756,7 @@ syslog-image: syslog-build -mkdir qa-tests-backend/test-images/syslog/bin cp bin/linux_$(GOARCH)/syslog qa-tests-backend/test-images/syslog/bin/syslog chmod +w qa-tests-backend/test-images/syslog/bin/syslog - docker build \ + $(DOCKERBUILD) \ -t stackrox/qa:syslog_server_1_0 \ -t quay.io/rhacs-eng/qa:syslog_server_1_0 \ -f qa-tests-backend/test-images/syslog/Dockerfile qa-tests-backend/test-images/syslog diff --git a/scripts/check-debugger.sh b/scripts/check-debugger.sh index 48d8bd1de6189..d4645d2d3a238 100755 --- a/scripts/check-debugger.sh +++ b/scripts/check-debugger.sh @@ -6,6 +6,8 @@ set -euo pipefail +CONTAINER_ENGINE="${CONTAINER_ENGINE:-docker}" + errecho() { echo >&2 -e "$@" } @@ -17,9 +19,9 @@ if [[ -n "${BUILD_TAG}" && "${DEBUG_BUILD}" == "yes" ]]; then fi # This searches for a file in the image without running the container. -container=$(docker create stackrox/main:"${TAG}") -docker export "${container}" | tar t | grep 'bin/dlv$' && found_dlv="yes" || found_dlv="no" -docker rm "${container}" &>/dev/null +container=$(${CONTAINER_ENGINE} create stackrox/main:"${TAG}") +${CONTAINER_ENGINE} export "${container}" | tar t | grep 'bin/dlv$' && found_dlv="yes" || found_dlv="no" +${CONTAINER_ENGINE} rm "${container}" &>/dev/null if [[ "${found_dlv}" != "${DEBUG_BUILD}" ]]; then if [[ "${DEBUG_BUILD}" == "yes" ]]; then diff --git a/scripts/connect-ui.sh b/scripts/connect-ui.sh index 62b2782be786e..f37f760b6f45c 100755 --- a/scripts/connect-ui.sh +++ b/scripts/connect-ui.sh @@ -2,8 +2,10 @@ set -e -# ensure logged in to docker -docker login +CONTAINER_ENGINE="${CONTAINER_ENGINE:-docker}" + +# ensure logged in to container registry +${CONTAINER_ENGINE} login # get clean cluster name CLUSTER_NAME="$(echo $1 | sed 's/-rg//')" diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh index 2ea8d02d38244..cbce9069e9aaa 100755 --- a/scripts/docker-build.sh +++ b/scripts/docker-build.sh @@ -2,9 +2,14 @@ set -e +CONTAINER_ENGINE="${CONTAINER_ENGINE:-docker}" + echo "Building with platform linux/${GOARCH}" -if docker info | grep buildx; then + +buildx_enabled=$(${CONTAINER_ENGINE} info 2>/dev/null | grep -q buildx && echo true || echo false) + +if [[ "${CONTAINER_ENGINE}" == "docker" ]] && [[ "${buildx_enabled}" == "true" ]]; then docker buildx build --platform "linux/${GOARCH}" --load "$@" else - docker build --platform "linux/${GOARCH}" "$@" + ${CONTAINER_ENGINE} build --platform "linux/${GOARCH}" "$@" fi diff --git a/scripts/ensure_image.sh b/scripts/ensure_image.sh index 5bc014bcf55a7..3e25d7e7ceacf 100755 --- a/scripts/ensure_image.sh +++ b/scripts/ensure_image.sh @@ -4,6 +4,8 @@ ## First, it tries to pull the image. If it succeeds, it exits. ## If not, it builds the image. If run in CI, it also pushes the image. +CONTAINER_ENGINE="${CONTAINER_ENGINE:-docker}" + die() { echo >&2 "$@" exit 1 @@ -16,30 +18,30 @@ dir="$3" [[ -n "${image}" && -n "${dockerfile}" && -n "${dir}" ]] || die "Usage $0 " echo "Potentially pulling image ${image}" -docker_pull_output="$(docker pull "${image}" 2>&1)" +docker_pull_output="$(${CONTAINER_ENGINE} pull "${image}" 2>&1)" if [[ "$?" -eq 0 ]]; then echo "Image exists. Exiting..." exit 0 fi if [[ ! "${docker_pull_output}" =~ ^.*manifest\ for.*not\ found.*$ ]]; then - die "Unexpected docker pull error: ${docker_pull_output}" + die "Unexpected ${CONTAINER_ENGINE} pull error: ${docker_pull_output}" fi set -e echo "Building the image since it doesn't exist" -docker build -t "${image}" -f "${dockerfile}" "${dir}" +${CONTAINER_ENGINE} build -t "${image}" -f "${dockerfile}" "${dir}" if [[ -n "${CI}" ]]; then case "$image" in quay.io/rhacs-eng/*) - docker login -u "$QUAY_RHACS_ENG_RW_USERNAME" --password-stdin <<<"$QUAY_RHACS_ENG_RW_PASSWORD" quay.io + ${CONTAINER_ENGINE} login -u "$QUAY_RHACS_ENG_RW_USERNAME" --password-stdin <<<"$QUAY_RHACS_ENG_RW_PASSWORD" quay.io ;; quay.io/stackrox-io/*) - docker login -u "$QUAY_STACKROX_IO_RW_USERNAME" --password-stdin <<<"$QUAY_STACKROX_IO_RW_PASSWORD" quay.io + ${CONTAINER_ENGINE} login -u "$QUAY_STACKROX_IO_RW_USERNAME" --password-stdin <<<"$QUAY_STACKROX_IO_RW_PASSWORD" quay.io ;; *) die "Unsupported registry of image: $image" esac - docker push "${image}" | cat + ${CONTAINER_ENGINE} push "${image}" | cat else echo "Not in CI, not pushing the new image..." fi From a5ed871a5659dd3db9f84ef528f2452e1bc8de9f Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:47:26 +0200 Subject: [PATCH 079/638] ROX-36450: VM Grafana dashboard for jitter load spreading (#22421) --- .../dashboards/vm-index-report-dashboard.json | 4151 ++++++++++++++--- 1 file changed, 3469 insertions(+), 682 deletions(-) diff --git a/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json b/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json index ea9ce35db4fc2..6a30ac0930fac 100644 --- a/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json +++ b/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json @@ -15,7 +15,7 @@ } ] }, - "description": "Monitoring dashboard for Virtual Machine Index Report processing in StackRox Sensor", + "description": "Sensor pull-mode VM scanning: Sensor dials each VM's agent when that VM is due, optionally forwards a new index report to Central, and retries on NACK. Use this board to see whether jitter and the per-tick start cap pace Central sends, and whether scrapes are failing or only unchanged.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -31,7 +31,7 @@ "x": 0, "y": 0 }, - "id": 106, + "id": 100, "panels": [ { "datasource": { @@ -39,7 +39,7 @@ "uid": "${datasource}" }, "gridPos": { - "h": 16, + "h": 12, "w": 24, "x": 0, "y": 1 @@ -51,11 +51,11 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": "```\n# How VM Index Report Processing Works in Sensor\n\n Fake Workloads (scale testing)\n │\n ▼\n┌───────┐ ┌────────────────┐ ┌─────────────────────────────┐ ┌─────────┐ ┌────────────┐\n│ Relay │──GRPC──▶ Sensor Service │──────▶ handlerImpl.indexReports │──────▶ Process │──────▶ to Central │\n└───────┘ └────────────────┘ │ (buffered channel) │ └────┬────┘ └────────────┘\n └─────────────────────────────┘ │\n ▲ │\n │ ▼\n capacity limit ┌──────────────────┐\n (blocks if full) │ Check in-memory │\n │ store if VM is │\n │ known to Sensor │\n └────────┬─────────┘\n │\n ┌──────────────┴──────────────┐\n ▼ ▼\n VM is known VM is unknown\n (outcome=success) (outcome=vm_unknown_to_sensor)\n │ │\n ▼ ▼\n Transform & send Report is dropped\n to Central\n```\n", + "content": "Sensor **pulls** index reports: VMScraper ticks, dials each due VM's roxagent over VSOCK, and forwards a successful report through `vmIndex.Handler` to Central. Central ACK/NACKs. Handler enqueue metrics are a secondary \"send to Central\" story, not the scrape scheduler.\n\n```\n roxagent (in VM) Sensor Central\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 VSOCK \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 SensorEvent \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 GetReport \u2502\u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 VMScraper \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 VM pipe \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 when due \u2502 per-VM next \u2502 \u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2518\n \u2502 AttemptAt \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518 SensorACK\u2502\n \u2502 ACK | NACK\u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n NACK: clear report_generation, shared backoff\n```\n\n**Load spreading** (read `pollInterval` from Sensor env; do not assume 5m):\n\n- First wave / Sensor restart: a newly tracked VM gets `nextAttemptAt = now + U(0, C)` where `C = min(20m, pollInterval/3)`.\n- Success and permanent failure reschedule at `pollInterval + U(0, W)`, `W = pollInterval \u00d7 ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION` (default 2/3).\n- Per-tick start cap (backstop when jitter still lines VMs up): `min(concurrency, max(1, ceil(nDue \u00d7 tick / C)))`. Leftovers stay due for the next tick. `nDue` is the eligible pile at tick start, not the tracked fleet. Concurrency (default 20) is a separate overlap cap on the errgroup.\n- Retryable pull failures and NACKs share exponential backoff (`initialBackoff` default 10s, doubled, cap `min(pollInterval, 30m)`).\n\nDefaults: tick 10s, poll 5m, C 100s, W 200s, concurrency 20. A 4h poll uses C 20m and W 2h40m. First-wave `U(0, C)` is not exported as a histogram; only cadence `U(0, W)` is.\n", "mode": "markdown" }, "pluginVersion": "10.4.17", - "title": "How this part of Sensor works", + "title": "How VMScraper pull mode works", "type": "text" } ], @@ -70,9 +70,9 @@ "x": 0, "y": 1 }, - "id": 100, + "id": 200, "panels": [], - "title": "📊 Overview", + "title": "Overview", "type": "row" }, { @@ -80,7 +80,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Total number of VM index reports successfully sent to Central", + "description": "How many VMs Sensor is currently scheduling. Poll-interval advice uses this fleet size. The per-tick start cap uses how many are due, not this number.", "fieldConfig": { "defaults": { "color": { @@ -91,7 +91,7 @@ "mode": "absolute", "steps": [ { - "color": "green", + "color": "blue", "value": null } ] @@ -102,11 +102,11 @@ }, "gridPos": { "h": 4, - "w": 6, + "w": 4, "x": 0, "y": 2 }, - "id": 1, + "id": 201, "options": { "colorMode": "value", "graphMode": "area", @@ -131,13 +131,13 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_reports_sent_total{status=\"success\"})", - "legendFormat": "Sent", + "expr": "rox_sensor_vsock_pull_tracked_vms", + "legendFormat": "Tracked", "range": true, "refId": "A" } ], - "title": "✅ Reports Sent to Central (Success)", + "title": "Tracked VMs", "type": "stat" }, { @@ -145,7 +145,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Total number of VM index reports received by Sensor", + "description": "How many VMs were eligible to scrape on the most recent tick, before the start cap. A high number with ~1 start per tick is the cap draining a backlog. Zero means the next attempts are still in the future.", "fieldConfig": { "defaults": { "color": { @@ -156,7 +156,7 @@ "mode": "absolute", "steps": [ { - "color": "blue", + "color": "yellow", "value": null } ] @@ -167,11 +167,11 @@ }, "gridPos": { "h": 4, - "w": 6, - "x": 6, + "w": 4, + "x": 4, "y": 2 }, - "id": 2, + "id": 202, "options": { "colorMode": "value", "graphMode": "area", @@ -196,13 +196,13 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_reports_received_total)", - "legendFormat": "Received", + "expr": "rox_sensor_vsock_pull_due_vms", + "legendFormat": "Due", "range": true, "refId": "A" } ], - "title": "📥 Reports Received from Relay", + "title": "Due VMs (last tick)", "type": "stat" }, { @@ -210,7 +210,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Number of times enqueue was blocked due to full channel", + "description": "Lifetime count of full index reports Sensor sent to Central. Unchanged pulls are not included. A Sensor restart resets the counter to 0.", "fieldConfig": { "defaults": { "color": { @@ -223,18 +223,136 @@ { "color": "green", "value": null - }, - { - "color": "yellow", - "value": 100 - }, + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 2 + }, + "id": 203, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"success\"})", + "legendFormat": "Success", + "range": true, + "refId": "A" + } + ], + "title": "Successful forwards to Central", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Lifetime count of scrapes where the agent had nothing newer than last time. Sensor still talked to the VM; Central did not get a new report. This is the usual case between mandatory refreshes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ { - "color": "orange", - "value": 1000 - }, + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 2 + }, + "id": 204, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"unchanged\"})", + "legendFormat": "Unchanged", + "range": true, + "refId": "A" + } + ], + "title": "Unchanged pulls", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "How many times Central rejected a VM index report. Sensor clears the cached generation and retries on a short backoff. The reason (Scanner down, and so on) is in Central logs, not in this number.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ { "color": "red", - "value": 10000 + "value": null } ] }, @@ -244,11 +362,11 @@ }, "gridPos": { "h": 4, - "w": 6, - "x": 12, + "w": 4, + "x": 16, "y": 2 }, - "id": 3, + "id": 205, "options": { "colorMode": "value", "graphMode": "area", @@ -273,13 +391,13 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_enqueue_blocked_total)", - "legendFormat": "Blocked", + "expr": "sum(rox_sensor_virtual_machine_index_report_acks_received_total{action=\"NACK\"})", + "legendFormat": "NACK", "range": true, "refId": "A" } ], - "title": "⚠️ Enqueue Blocked Count", + "title": "NACKs from Central", "type": "stat" }, { @@ -287,7 +405,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Total reports processed (sum of all outcomes)", + "description": "How often the scraper loop wakes up. Default 10s tick is about 0.1 per second. A drop to zero means the loop is not running.", "fieldConfig": { "defaults": { "color": { @@ -303,17 +421,17 @@ } ] }, - "unit": "short" + "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 4, - "w": 6, - "x": 18, + "w": 4, + "x": 20, "y": 2 }, - "id": 4, + "id": 206, "options": { "colorMode": "value", "graphMode": "area", @@ -338,13 +456,13 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count)", - "legendFormat": "Total Processed", + "expr": "rate(rox_sensor_vsock_pull_ticks_total[$__rate_interval])", + "legendFormat": "Ticks/s", "range": true, "refId": "A" } ], - "title": "🔄 Total Processed Index Reports", + "title": "Tick rate", "type": "stat" }, { @@ -355,9 +473,9 @@ "x": 0, "y": 6 }, - "id": 101, + "id": 300, "panels": [], - "title": "📈 Throughput & Rates", + "title": "Load spreading", "type": "row" }, { @@ -365,7 +483,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Rate of reports sent per second over time", + "description": "Running total of full index reports Sensor sent to Central (rox_sensor_vsock_pull_requests_total{status=\"success\"}). Each step up is one or more forwards. A paced wave is a run of +1 steps a few seconds apart. A dump is one large jump. A Sensor restart resets the counter to 0. Unchanged pulls are not counted.", "fieldConfig": { "defaults": { "color": { @@ -375,19 +493,19 @@ "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", - "axisLabel": "Reports/sec", + "axisLabel": "forwards", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 20, - "gradientMode": "opacity", + "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, - "lineInterpolation": "smooth", + "lineInterpolation": "stepAfter", "lineWidth": 2, "pointSize": 5, "scaleDistribution": { @@ -413,21 +531,37 @@ } ] }, - "unit": "reqps" + "unit": "short" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { "h": 8, - "w": 12, + "w": 16, "x": 0, "y": 7 }, - "id": 5, + "id": 301, "options": { "legend": { "calcs": [ - "mean", + "lastNotNull", "max" ], "displayMode": "table", @@ -439,7 +573,7 @@ "sort": "desc" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -447,13 +581,13 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(rox_sensor_virtual_machine_index_reports_sent_total{status=\"success\"}[$__rate_interval]))", - "legendFormat": "Sent Rate", + "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"success\"})", + "legendFormat": "success", "range": true, "refId": "A" } ], - "title": "📤 Reports Sent to Central Rate", + "title": "Successful forwards to Central", "type": "timeseries" }, { @@ -461,7 +595,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Rate of processing by outcome", + "description": "How many VMs Sensor is scheduling, as a time series. The start cap is computed from the due pile, not from this series.", "fieldConfig": { "defaults": { "color": { @@ -471,7 +605,7 @@ "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", - "axisLabel": "Reports/sec", + "axisLabel": "VMs", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", @@ -493,7 +627,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" @@ -509,34 +643,19 @@ } ] }, - "unit": "reqps" + "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", - "options": "success" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "vm_unknown_to_sensor" + "options": "tracked" }, "properties": [ { "id": "color", "value": { - "fixedColor": "orange", + "fixedColor": "blue", "mode": "fixed" } } @@ -546,11 +665,11 @@ }, "gridPos": { "h": 8, - "w": 12, - "x": 12, + "w": 8, + "x": 16, "y": 7 }, - "id": 6, + "id": 302, "options": { "legend": { "calcs": [ @@ -566,7 +685,7 @@ "sort": "desc" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -574,54 +693,90 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum by (outcome) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count[$__rate_interval]))", - "legendFormat": "{{outcome}}", + "expr": "rox_sensor_vsock_pull_tracked_vms", + "legendFormat": "tracked", "range": true, "refId": "A" } ], - "title": "🔄 Processing Rate by Outcome", + "title": "Fleet size", "type": "timeseries" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 15 - }, - "id": 102, - "panels": [], - "title": "🥧 Processing Outcome Distribution", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Distribution of processing outcomes - shows what percentage of reports succeeded vs failed", + "description": "Eligible VMs versus how many scrapes that tick actually started. Healthy catch-up with the start cap: due stays high, starts stay near 1. Jitter-only (no cap): due and starts move together. A dump: both jump toward the fleet. Starts are only recorded on ticks that had someone due; idle ticks are omitted.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "VMs", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } }, "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", - "options": "success" + "options": "due (last tick)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "avg starts / observed tick" }, "properties": [ { @@ -636,13 +791,13 @@ { "matcher": { "id": "byName", - "options": "vm_unknown_to_sensor" + "options": "P95 starts / observed tick" }, "properties": [ { "id": "color", "value": { - "fixedColor": "orange", + "fixedColor": "red", "mode": "fixed" } } @@ -652,39 +807,27 @@ }, "gridPos": { "h": 8, - "w": 8, + "w": 24, "x": 0, - "y": 16 + "y": 15 }, - "id": 7, + "id": 303, "options": { - "displayLabels": [ - "name", - "percent" - ], "legend": { - "displayMode": "table", - "placement": "right", - "showLegend": true, - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", - "reduceOptions": { "calcs": [ - "lastNotNull" + "mean", + "max" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -692,41 +835,176 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum by (outcome) (rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count)", - "legendFormat": "{{outcome}}", + "expr": "rox_sensor_vsock_pull_due_vms", + "legendFormat": "due (last tick)", "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(rox_sensor_vsock_pull_starts_per_tick_sum[$__rate_interval]) / rate(rox_sensor_vsock_pull_starts_per_tick_count[$__rate_interval])", + "legendFormat": "avg starts / observed tick", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_starts_per_tick_bucket[$__rate_interval])))", + "legendFormat": "P95 starts / observed tick", + "range": true, + "refId": "C" } ], - "title": "🎯 Processing Outcome Distribution", - "type": "piechart" + "title": "Due pile vs starts per tick", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Shows the distribution of blocking enqueue wait times across latency buckets", + "description": "Time on X, how many VMs started in that tick on Y, color = how often that happened. Only ticks that had at least one due VM are recorded, so there is no 0 row. Cap 1 should glow on Y near 1. A bright band at Y near the fleet (or 20) is a dump. Compare Tick rate if most wall-clock ticks are idle.", "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, "custom": { "hideFrom": { "legend": false, "tooltip": false, "viz": false + }, + "scaleDistribution": { + "type": "linear" } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 23 + }, + "id": 304, + "options": { + "calculate": false, + "cellGap": 1, + "cellValues": { + "unit": "short" + }, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-09 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by (le) (increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le!=\"0\"}[$__rate_interval]))", + "legendFormat": "{{le}}", + "range": true, + "refId": "A", + "format": "heatmap" + } + ], + "title": "Starts per tick heatmap", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "How many observed ticks started 1, 2, 3, ... VMs. Idle ticks (nobody due) are not sampled, so there is no 0 bar; those ticks show up on Tick rate instead. For a small fleet with start cap 1, mass should sit in 1. Mass in 8 or 20 is a dump.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" }, "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "orange", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, "unit": "short" }, "overrides": [ { "matcher": { "id": "byName", - "options": "≤1ms" + "options": "1" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "2" }, "properties": [ { @@ -741,7 +1019,7 @@ { "matcher": { "id": "byName", - "options": "1-10ms" + "options": "3" }, "properties": [ { @@ -756,7 +1034,22 @@ { "matcher": { "id": "byName", - "options": "10-100ms" + "options": "4-5" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "6-8" }, "properties": [ { @@ -771,7 +1064,37 @@ { "matcher": { "id": "byName", - "options": "100-500ms" + "options": "9-10" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "11-15" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "16-20" }, "properties": [ { @@ -786,7 +1109,22 @@ { "matcher": { "id": "byName", - "options": ">500ms" + "options": "21-30" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "31-50" }, "properties": [ { @@ -797,31 +1135,53 @@ } } ] + }, + { + "matcher": { + "id": "byName", + "options": "51-100" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": ">100" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] } ] }, "gridPos": { "h": 8, - "w": 8, - "x": 8, - "y": 16 + "w": 12, + "x": 12, + "y": 23 }, - "id": 8, + "id": 305, "options": { - "displayLabels": [ - "name", - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "showLegend": true, - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", "reduceOptions": { "calcs": [ "lastNotNull" @@ -829,12 +1189,11 @@ "fields": "", "values": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -842,10 +1201,10 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"1\"})", - "legendFormat": "≤1ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"1\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"0\"}[$__range]))", + "legendFormat": "1", "range": true, - "refId": "A" + "refId": "B" }, { "datasource": { @@ -853,10 +1212,10 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"10\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"1\"})", - "legendFormat": "1-10ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"2\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"1\"}[$__range]))", + "legendFormat": "2", "range": true, - "refId": "B" + "refId": "C" }, { "datasource": { @@ -864,10 +1223,10 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"100\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"10\"})", - "legendFormat": "10-100ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"3\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"2\"}[$__range]))", + "legendFormat": "3", "range": true, - "refId": "C" + "refId": "D" }, { "datasource": { @@ -875,10 +1234,10 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"500\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"100\"})", - "legendFormat": "100-500ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"5\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"3\"}[$__range]))", + "legendFormat": "4-5", "range": true, - "refId": "D" + "refId": "E" }, { "datasource": { @@ -886,130 +1245,65 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"+Inf\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"500\"})", - "legendFormat": ">500ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"8\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"5\"}[$__range]))", + "legendFormat": "6-8", "range": true, - "refId": "E" - } - ], - "title": "⏱️ Blocking Enqueue Duration Distribution", - "type": "piechart" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Shows the distribution of processing times for successful operations", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"10\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"8\"}[$__range]))", + "legendFormat": "9-10", + "range": true, + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" }, - "mappings": [], - "unit": "short" + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"15\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"10\"}[$__range]))", + "legendFormat": "11-15", + "range": true, + "refId": "H" }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "≤10ms" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" }, - { - "matcher": { - "id": "byName", - "options": "10-40ms" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"20\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"15\"}[$__range]))", + "legendFormat": "16-20", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" }, - { - "matcher": { - "id": "byName", - "options": ">40ms" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 16, - "y": 16 - }, - "id": 9, - "options": { - "displayLabels": [ - "name", - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "showLegend": true, - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"30\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"20\"}[$__range]))", + "legendFormat": "21-30", + "range": true, + "refId": "J" }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "10.0.0", - "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\",le=\"10\"})", - "legendFormat": "≤10ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"50\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"30\"}[$__range]))", + "legendFormat": "31-50", "range": true, - "refId": "A" + "refId": "K" }, { "datasource": { @@ -1017,10 +1311,10 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\",le=\"40\"}) - sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\",le=\"10\"})", - "legendFormat": "10-40ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"100\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"50\"}[$__range]))", + "legendFormat": "51-100", "range": true, - "refId": "B" + "refId": "L" }, { "datasource": { @@ -1028,169 +1322,82 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\",le=\"+Inf\"}) - sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\",le=\"40\"})", - "legendFormat": ">40ms", + "expr": "sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"+Inf\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_starts_per_tick_bucket{le=\"100\"}[$__range]))", + "legendFormat": ">100", "range": true, - "refId": "C" + "refId": "M" } ], - "title": "⚡ Processing Duration Distribution (Success)", - "type": "piechart" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 24 - }, - "id": 103, - "panels": [], - "title": "📊 Latency Percentiles", - "type": "row" + "title": "Starts per tick distribution", + "type": "bargauge" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "P50, P90, P95, and P99 latencies for blocking enqueue duration", + "description": "Time on X, gap between consecutive Central sends on Y (seconds), color = how often that gap occurred. This is Sensor-wide, not per VM, and only full forwards (not unchanged pulls). A paced wave is a bright band around the tick (default ~10s). A dump is bright near 0s. Long quiet stretches between waves are normal.", "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "Latency (ms)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, "scaleDistribution": { "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "ms" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "P50" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "P90" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "P95" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "orange", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "P99" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] } - ] + }, + "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 31 }, - "id": 10, + "id": 306, "options": { + "calculate": false, + "cellGap": 1, + "cellValues": { + "unit": "short" + }, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-09 + }, "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true + "show": true + }, + "rowsFrame": { + "layout": "auto" }, "tooltip": { - "mode": "multi", - "sort": "desc" + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -1198,54 +1405,22 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "histogram_quantile(0.50, sum(rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])) by (le))", - "legendFormat": "P50", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.90, sum(rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])) by (le))", - "legendFormat": "P90", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum(rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])) by (le))", - "legendFormat": "P95", - "range": true, - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum(rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])) by (le))", - "legendFormat": "P99", + "expr": "sum by (le) (increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket[$__rate_interval]))", + "legendFormat": "{{le}}", "range": true, - "refId": "D" + "refId": "A", + "format": "heatmap" } ], - "title": "⏱️ Blocking Enqueue Duration Percentiles", - "type": "timeseries" + "title": "Forward interarrival heatmap", + "type": "heatmap" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "P50, P90, P95, and P99 latencies for processing duration", + "description": "Typical (P50) and large (P90/P99) gaps between consecutive Central sends over time. With few forwards the lines jump; histogram_quantile on a sparse rate() window can pin to ~0. Prefer the heatmap and distribution for gap size.", "fieldConfig": { "defaults": { "color": { @@ -1255,7 +1430,7 @@ "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", - "axisLabel": "Latency (ms)", + "axisLabel": "gap", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", @@ -1293,7 +1468,7 @@ } ] }, - "unit": "ms" + "unit": "s" }, "overrides": [ { @@ -1362,9 +1537,9 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 31 }, - "id": 11, + "id": 307, "options": { "legend": { "calcs": [ @@ -1380,7 +1555,7 @@ "sort": "desc" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.4.17", "targets": [ { "datasource": { @@ -1388,7 +1563,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "histogram_quantile(0.50, sum(rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket[$__rate_interval])))", "legendFormat": "P50", "range": true, "refId": "A" @@ -1399,7 +1574,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "histogram_quantile(0.90, sum(rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.90, sum by (le) (rate(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket[$__rate_interval])))", "legendFormat": "P90", "range": true, "refId": "B" @@ -1410,7 +1585,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "histogram_quantile(0.95, sum(rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket[$__rate_interval])))", "legendFormat": "P95", "range": true, "refId": "C" @@ -1421,34 +1596,21 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "histogram_quantile(0.99, sum(rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket[$__rate_interval])))", "legendFormat": "P99", "range": true, "refId": "D" } ], - "title": "⚡ Processing Duration Percentiles (Success)", + "title": "Forward interarrival percentiles", "type": "timeseries" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 33 - }, - "id": 104, - "panels": [], - "title": "📉 Detailed Histogram View", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Bar gauge showing the count of operations in each latency bucket for blocking enqueue", + "description": "How many Central-send gaps fell in each size bucket over the selected range. Mass in 0.08s or less means reports left Sensor almost together (a dump). Mass around 10-41s is a paced wave at a 10s tick. Mass in minutes-or-more is idle time between waves, which is normal when guests are unchanged.", "fieldConfig": { "defaults": { "color": { @@ -1464,15 +1626,15 @@ }, { "color": "yellow", - "value": 1000 + "value": 10 }, { "color": "orange", - "value": 5000 + "value": 50 }, { "color": "red", - "value": 10000 + "value": 200 } ] }, @@ -1482,13 +1644,13 @@ { "matcher": { "id": "byName", - "options": "≤1ms" + "options": "\u22640.08s (dump)" }, "properties": [ { "id": "color", "value": { - "fixedColor": "green", + "fixedColor": "red", "mode": "fixed" } } @@ -1497,13 +1659,13 @@ { "matcher": { "id": "byName", - "options": "1-5ms" + "options": "0.08-1.3s" }, "properties": [ { "id": "color", "value": { - "fixedColor": "super-light-green", + "fixedColor": "orange", "mode": "fixed" } } @@ -1512,13 +1674,13 @@ { "matcher": { "id": "byName", - "options": "5-10ms" + "options": "1.3-10s" }, "properties": [ { "id": "color", "value": { - "fixedColor": "light-green", + "fixedColor": "yellow", "mode": "fixed" } } @@ -1527,13 +1689,13 @@ { "matcher": { "id": "byName", - "options": "10-50ms" + "options": "10-41s" }, "properties": [ { "id": "color", "value": { - "fixedColor": "yellow", + "fixedColor": "green", "mode": "fixed" } } @@ -1542,13 +1704,13 @@ { "matcher": { "id": "byName", - "options": "50-100ms" + "options": "41s-2.7m" }, "properties": [ { "id": "color", "value": { - "fixedColor": "light-orange", + "fixedColor": "green", "mode": "fixed" } } @@ -1557,13 +1719,13 @@ { "matcher": { "id": "byName", - "options": "100-500ms" + "options": "2.7-5.5m" }, "properties": [ { "id": "color", "value": { - "fixedColor": "orange", + "fixedColor": "blue", "mode": "fixed" } } @@ -1572,13 +1734,28 @@ { "matcher": { "id": "byName", - "options": ">500ms" + "options": "5.5-11m" }, "properties": [ { "id": "color", "value": { - "fixedColor": "red", + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": ">11m" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", "mode": "fixed" } } @@ -1588,11 +1765,11 @@ }, "gridPos": { "h": 8, - "w": 24, + "w": 12, "x": 0, - "y": 34 + "y": 39 }, - "id": 12, + "id": 308, "options": { "displayMode": "gradient", "maxVizHeight": 300, @@ -1619,8 +1796,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"1\"})", - "legendFormat": "≤1ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"0.08\"}[$__range]))", + "legendFormat": "\u22640.08s (dump)", "range": true, "refId": "A" }, @@ -1630,8 +1807,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"5\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"1\"})", - "legendFormat": "1-5ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"1.28\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"0.08\"}[$__range]))", + "legendFormat": "0.08-1.3s", "range": true, "refId": "B" }, @@ -1641,8 +1818,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"10\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"5\"})", - "legendFormat": "5-10ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"10.24\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"1.28\"}[$__range]))", + "legendFormat": "1.3-10s", "range": true, "refId": "C" }, @@ -1652,8 +1829,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"50\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"10\"})", - "legendFormat": "10-50ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"40.96\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"10.24\"}[$__range]))", + "legendFormat": "10-41s", "range": true, "refId": "D" }, @@ -1663,8 +1840,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"100\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"50\"})", - "legendFormat": "50-100ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"163.84\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"40.96\"}[$__range]))", + "legendFormat": "41s-2.7m", "range": true, "refId": "E" }, @@ -1674,8 +1851,8 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"500\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"100\"})", - "legendFormat": "100-500ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"327.68\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"163.84\"}[$__range]))", + "legendFormat": "2.7-5.5m", "range": true, "refId": "F" }, @@ -1685,87 +1862,135 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"+Inf\"}) - sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket{le=\"500\"})", - "legendFormat": ">500ms", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"655.36\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"327.68\"}[$__range]))", + "legendFormat": "5.5-11m", "range": true, "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"1310.72\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"655.36\"}[$__range]))", + "legendFormat": "11-22m", + "range": true, + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"2621.44\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"1310.72\"}[$__range]))", + "legendFormat": "22-44m", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"5242.88\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"2621.44\"}[$__range]))", + "legendFormat": "44m-1.5h", + "range": true, + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"10485.76\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"5242.88\"}[$__range]))", + "legendFormat": "1.5-2.9h", + "range": true, + "refId": "K" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"+Inf\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_forward_interarrival_seconds_bucket{le=\"10485.76\"}[$__range]))", + "legendFormat": ">2.9h", + "range": true, + "refId": "L" } ], - "title": "📊 Blocking Enqueue Duration - Histogram Buckets", + "title": "Forward interarrival distribution", "type": "bargauge" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 42 - }, - "id": 105, - "panels": [], - "title": "📈 Average Latencies", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Average blocking enqueue duration in milliseconds", + "description": "Time on X, extra random delay added on top of the poll interval on Y. Recorded only when a VM returns to cadence after a successful scrape or a permanent failure. First-wave U(0, C) jitter and NACK/retry backoff are not on this chart. Empty after Sensor start until the first cadence reschedule. A uniform draw on [0, W] should fill the band, not pile on 0.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 50 - }, - { - "color": "orange", - "value": 100 - }, - { - "color": "red", - "value": 500 - } - ] - }, - "unit": "ms" + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 43 + "h": 8, + "w": 12, + "x": 12, + "y": 39 }, - "id": 13, + "id": 309, "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "calculate": false, + "cellGap": 1, + "cellValues": { + "unit": "short" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-09 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } }, "pluginVersion": "10.4.17", "targets": [ @@ -1775,25 +2000,58 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_sum) / sum(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_count)", - "legendFormat": "Avg", + "expr": "sum by (le) (increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket[$__rate_interval]))", + "legendFormat": "{{le}}", "range": true, - "refId": "A" + "refId": "A", + "format": "heatmap" } ], - "title": "⏱️ Avg Blocking Enqueue Duration", - "type": "stat" + "title": "Schedule offset heatmap", + "type": "heatmap" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Average processing duration for successful operations", + "description": "Typical extra delay on return-to-cadence, over time. Empty until the first success or permanent-failure reschedule. NACK retries will not move this chart. P50 should sit near W/2 (2/3 of poll / 2).", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "offset", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -1802,47 +2060,95 @@ { "color": "green", "value": null - }, + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ { - "color": "yellow", - "value": 20 - }, + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P90" + }, + "properties": [ { - "color": "orange", - "value": 50 - }, + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ { - "color": "red", - "value": 100 + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } } ] }, - "unit": "ms" - }, - "overrides": [] + { + "matcher": { + "id": "byName", + "options": "P99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 43 + "h": 8, + "w": 12, + "x": 0, + "y": 47 }, - "id": 14, + "id": 310, "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "mean", + "max" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "mode": "multi", + "sort": "desc" + } }, "pluginVersion": "10.4.17", "targets": [ @@ -1852,29 +2158,60 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_sum{outcome=\"success\"}) / sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count{outcome=\"success\"})", - "legendFormat": "Avg", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_schedule_offset_seconds_bucket[$__rate_interval])))", + "legendFormat": "P50", "range": true, "refId": "A" - } + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.90, sum by (le) (rate(rox_sensor_vsock_pull_schedule_offset_seconds_bucket[$__rate_interval])))", + "legendFormat": "P90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_schedule_offset_seconds_bucket[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_vsock_pull_schedule_offset_seconds_bucket[$__rate_interval])))", + "legendFormat": "P99", + "range": true, + "refId": "D" + } ], - "title": "⚡ Avg Processing Duration (Success)", - "type": "stat" + "title": "Schedule offset percentiles", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of reports that encountered blocking during enqueue", + "description": "Totals of those extra delays over the selected range. Default poll 5m piles around 0-200s. A 4h poll piles under ~2h40m (W). This is the random band on the next poll, not the gap between Central forwards.", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], - "max": 100, - "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -1888,29 +2225,153 @@ }, { "color": "orange", - "value": 30 + "value": 50 }, { "color": "red", - "value": 50 + "value": 200 } ] }, - "unit": "percent" + "unit": "short" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "\u226432s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "32-128s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "128-256s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "256-512s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "512-1024s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "1024-2048s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "2048-4096s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": ">4096s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { - "h": 5, - "w": 6, + "h": 8, + "w": 12, "x": 12, - "y": 43 + "y": 47 }, - "id": 15, + "id": 311, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", "reduceOptions": { "calcs": [ "lastNotNull" @@ -1918,9 +2379,9 @@ "fields": "", "values": false }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" }, "pluginVersion": "10.4.17", "targets": [ @@ -1930,75 +2391,356 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "(sum(rox_sensor_virtual_machine_index_report_enqueue_blocked_total) / sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count)) * 100", - "legendFormat": "Block Rate", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"32\"}[$__range]))", + "legendFormat": "\u226432s", "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"128\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"32\"}[$__range]))", + "legendFormat": "32-128s", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"256\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"128\"}[$__range]))", + "legendFormat": "128-256s", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"512\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"256\"}[$__range]))", + "legendFormat": "256-512s", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"1024\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"512\"}[$__range]))", + "legendFormat": "512-1024s", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"2048\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"1024\"}[$__range]))", + "legendFormat": "1024-2048s", + "range": true, + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"4096\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"2048\"}[$__range]))", + "legendFormat": "2048-4096s", + "range": true, + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"8192\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"4096\"}[$__range]))", + "legendFormat": "4096-8192s", + "range": true, + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"16384\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"8192\"}[$__range]))", + "legendFormat": "8192-16384s", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"+Inf\"}[$__range])) - sum(increase(rox_sensor_vsock_pull_schedule_offset_seconds_bucket{le=\"16384\"}[$__range]))", + "legendFormat": ">16384s (~4.5h)", + "range": true, + "refId": "J" } ], - "title": "🚫 Enqueue Block Rate", - "type": "gauge" + "title": "Schedule offset distribution", + "type": "bargauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 400, + "panels": [], + "title": "Retries and NACKs", + "type": "row" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of processed reports where VM was unknown to sensor", + "description": "Rate of scrape results. success = new report sent to Central. unchanged = agent had nothing new. The rest are errors (dial, timeout, not_ready, busy, and so on). Retryable errors should continue at backoff, not at 1 per tick for the whole fleet.", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "pulls/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", "value": null - }, + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ { - "color": "yellow", - "value": 5 - }, + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "unchanged" + }, + "properties": [ { - "color": "orange", - "value": 10 - }, + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "dial_error" + }, + "properties": [ { - "color": "red", - "value": 20 + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } } ] }, - "unit": "percent" - }, - "overrides": [] + { + "matcher": { + "id": "byName", + "options": "timeout" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "read_error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "not_ready" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "busy" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "send_error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "invalid_report" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "unknown_method" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 43 + "h": 8, + "w": 16, + "x": 0, + "y": 56 }, - "id": 16, + "id": 401, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { + "legend": { "calcs": [ - "lastNotNull" + "mean", + "max" ], - "fields": "", - "values": false + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" + "tooltip": { + "mode": "multi", + "sort": "desc" + } }, "pluginVersion": "10.4.17", "targets": [ @@ -2008,14 +2750,2058 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "(sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count{outcome=\"vm_unknown_to_sensor\"}) / sum(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count)) * 100", - "legendFormat": "Unknown VM Rate", + "expr": "sum by (status) (rate(rox_sensor_vsock_pull_requests_total[$__rate_interval]))", + "legendFormat": "{{status}}", "range": true, "refId": "A" } ], - "title": "❓ Unknown VM Rate", - "type": "gauge" + "title": "Pull outcomes by status", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Rate of Central acknowledgements. NACKs with ongoing success means Sensor is resending full reports after a reject. The NACK reason is in Central logs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "acks/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "ACK" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "NACK" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 56 + }, + "id": 402, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by (action) (rate(rox_sensor_virtual_machine_index_report_acks_received_total[$__rate_interval]))", + "legendFormat": "{{action}}", + "range": true, + "refId": "A" + } + ], + "title": "ACK vs NACK", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Good pulls (success + unchanged) versus retryable errors versus permanent failures. A retryable stack as large as fleet times 1/tick is a tight loop, not backoff.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "pulls/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success+unchanged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "retryable errors" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "permanent failures" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 0, + "y": 64 + }, + "id": 403, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"success|unchanged\"}[$__rate_interval]))", + "legendFormat": "success+unchanged", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"dial_error|timeout|read_error|not_ready|busy|send_error\"}[$__rate_interval]))", + "legendFormat": "retryable errors", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"invalid_report|unknown_method\"}[$__rate_interval]))", + "legendFormat": "permanent failures", + "range": true, + "refId": "C" + } + ], + "title": "Retryable errors vs success+unchanged", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Running totals of Central ACK vs NACK. A climbing NACK line plus continued success is retry-after-reject, not a silent stall.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "ACK" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "NACK" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 64 + }, + "id": 404, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rox_sensor_virtual_machine_index_report_acks_received_total{action=\"ACK\"})", + "legendFormat": "ACK", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rox_sensor_virtual_machine_index_report_acks_received_total{action=\"NACK\"})", + "legendFormat": "NACK", + "range": true, + "refId": "B" + } + ], + "title": "ACK / NACK cumulative", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 500, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Time to connect to the agent, time to read the report, and end-to-end (dial + read + send). A high read P99 with a healthy dial points at a slow agent, not a VSOCK connect problem. Total is only recorded on successful forwards.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "seconds", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 0, + "y": 73 + }, + "id": 501, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_dial_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "dial P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_dial_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "dial P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_read_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "read P50", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_read_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "read P95", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_total_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "total P50", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_total_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "total P95", + "range": true, + "refId": "F" + } + ], + "title": "Pull latency: dial / read / total", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "How long a tick spent waiting on the scrapes it started. If this sits well above the configured tick (default 10s), the ticker is falling behind and the due pile can grow even when the start cap is 1 per tick.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "tick", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 73 + }, + "id": 502, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_tick_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_tick_duration_seconds_bucket[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "B" + } + ], + "title": "Tick duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Size of forwarded reports (not unchanged pulls). Watch this against the 16MB response ceiling. Climbing multi-MB P95 is early warning before rejects.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "bytes", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 81 + }, + "id": 503, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_report_bytes_bucket[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_report_bytes_bucket[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_vsock_pull_report_bytes_bucket[$__rate_interval])))", + "legendFormat": "P99", + "range": true, + "refId": "C" + } + ], + "title": "Report payload bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Package count in forwarded reports. Use with payload bytes when a report is oversized.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "packages", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 81 + }, + "id": 504, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_vsock_pull_report_packages_bucket[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_vsock_pull_report_packages_bucket[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_vsock_pull_report_packages_bucket[$__rate_interval])))", + "legendFormat": "P99", + "range": true, + "refId": "C" + } + ], + "title": "Packages per report", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Tick rate should stay about 1 per tick interval. Starts-per-tick samples are fewer because idle ticks are omitted. A large gap between the two means most ticks had an empty due pile (steady state), not a broken histogram.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "ticks" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "starts-per-tick samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 81 + }, + "id": 505, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(rox_sensor_vsock_pull_ticks_total[$__rate_interval])", + "legendFormat": "ticks", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(rox_sensor_vsock_pull_starts_per_tick_count[$__rate_interval])", + "legendFormat": "starts-per-tick samples", + "range": true, + "refId": "B" + } + ], + "title": "Ticks vs starts-per-tick samples", + "type": "timeseries" + } + ], + "title": "Pull latency and payload", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 73 + }, + "id": 600, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "After a successful pull, Handler sends to Central. This is dequeue/send outcomes (success, unknown VM, and so on), not the scrape scheduler.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 40, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vm_unknown_to_sensor" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "invalid_vsock_cid" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "nil_report" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 74 + }, + "id": 601, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by (outcome) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_count[$__rate_interval]))", + "legendFormat": "{{outcome}}", + "range": true, + "refId": "A" + } + ], + "title": "Handler processing rate by outcome", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Handler's send counter. If this diverges from pull success, the scrape worked but enqueue/send did not.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 74 + }, + "id": 602, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by (status) (rate(rox_sensor_virtual_machine_index_reports_sent_total[$__rate_interval]))", + "legendFormat": "{{status}}", + "range": true, + "refId": "A" + } + ], + "title": "Handler reports sent to Central", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Time spent waiting because the Handler channel was full. Spreading should keep this near zero. Rising P95 means Central/Handler cannot drain as fast as VMScraper forwards.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 82 + }, + "id": 603, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.90, sum by (le) (rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])))", + "legendFormat": "P90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_virtual_machine_index_report_blocking_enqueue_duration_milliseconds_bucket[$__rate_interval])))", + "legendFormat": "P99", + "range": true, + "refId": "D" + } + ], + "title": "Handler blocking enqueue percentiles", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "How often that channel was full. A climb is Handler/Central backpressure, not a due-pile dump by itself.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "blocked/s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "blocked total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 82 + }, + "id": 604, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(rox_sensor_virtual_machine_index_report_enqueue_blocked_total[$__rate_interval]))", + "legendFormat": "blocked/s", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rox_sensor_virtual_machine_index_report_enqueue_blocked_total)", + "legendFormat": "blocked total", + "range": true, + "refId": "B" + } + ], + "title": "Enqueue blocked count", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Time from dequeue to a successful Central send. Spikes here can fill the enqueue channel even when scrape spreading is healthy.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "P50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P95" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "P99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 90 + }, + "id": 605, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.4.17", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.90, sum by (le) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])))", + "legendFormat": "P90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(rox_sensor_virtual_machine_index_report_processing_duration_milliseconds_bucket{outcome=\"success\"}[$__rate_interval])))", + "legendFormat": "P99", + "range": true, + "refId": "D" + } + ], + "title": "Handler processing duration (success)", + "type": "timeseries" + } + ], + "title": "Forward to Central (handler)", + "type": "row" } ], "refresh": "30s", @@ -2024,7 +4810,8 @@ "stackrox", "sensor", "virtual-machine", - "index-report" + "index-report", + "vmscraper" ], "templating": { "list": [ @@ -2050,13 +4837,13 @@ ] }, "time": { - "from": "now-15m", + "from": "now-6h", "to": "now" }, "timepicker": {}, "timezone": "browser", "title": "VM Index Report - Sensor Metrics", "uid": "vm-index-report-sensor", - "version": 8, + "version": 9, "weekStart": "" -} \ No newline at end of file +} From 00bb41d7862a4b20d22de2a75dc2d166f86a18ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:16:27 +0200 Subject: [PATCH 080/638] chore(deps): bump github.com/stackrox/image-prefetcher/deploy from 0.5.1 to 0.5.2 in /tools/test (#22466) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/test/go.mod | 2 +- tools/test/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/test/go.mod b/tools/test/go.mod index 0250c0b26ad96..507306861087c 100644 --- a/tools/test/go.mod +++ b/tools/test/go.mod @@ -4,7 +4,7 @@ go 1.26.3 require ( github.com/jstemmer/go-junit-report/v2 v2.1.0 - github.com/stackrox/image-prefetcher/deploy v0.5.1 + github.com/stackrox/image-prefetcher/deploy v0.5.2 github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5 ) diff --git a/tools/test/go.sum b/tools/test/go.sum index e42424d0c4d2d..f7ff091e5b80e 100644 --- a/tools/test/go.sum +++ b/tools/test/go.sum @@ -94,8 +94,8 @@ github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stackrox/image-prefetcher/deploy v0.5.1 h1:M0XBgUbmTgR4y3lTXxfL0Drd0tncHSZHWtsQqnXmCEA= -github.com/stackrox/image-prefetcher/deploy v0.5.1/go.mod h1:bAhl/GuKJnAchWpAYkEaoqfaJHqCkbBKhLDWHm1GXLs= +github.com/stackrox/image-prefetcher/deploy v0.5.2 h1:b8VP/i6xD46aAxMHq9nLgyq4vMYqOxrkssUkxGMskh0= +github.com/stackrox/image-prefetcher/deploy v0.5.2/go.mod h1:bAhl/GuKJnAchWpAYkEaoqfaJHqCkbBKhLDWHm1GXLs= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5 h1:gAg8XyBlWRq8Ia4uTPmhNUnVYtiDN4eiqviJ+tD87xw= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5/go.mod h1:y4d4bwvOygvByIxM/g0XCzBKMryqXilCk4Lj+mMBsVM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 2520b9037242d6293ade225795c0feaa13a534ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:34:00 +0200 Subject: [PATCH 081/638] chore(deps): bump k8s.io/client-go from 0.36.3 to 0.36.4 in /tests/performance/scale (#22468) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/scale/go.mod | 6 +++--- tests/performance/scale/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/performance/scale/go.mod b/tests/performance/scale/go.mod index 5f1e9d10747f4..67cb702d7e08f 100644 --- a/tests/performance/scale/go.mod +++ b/tests/performance/scale/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/spf13/cobra v1.10.2 go.yaml.in/yaml/v3 v3.0.5 - k8s.io/client-go v0.36.3 + k8s.io/client-go v0.36.4 ) require ( @@ -60,8 +60,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.3 // indirect - k8s.io/apimachinery v0.36.3 // indirect + k8s.io/api v0.36.4 // indirect + k8s.io/apimachinery v0.36.4 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect diff --git a/tests/performance/scale/go.sum b/tests/performance/scale/go.sum index 70bebf92c5e24..3ef00b9025f19 100644 --- a/tests/performance/scale/go.sum +++ b/tests/performance/scale/go.sum @@ -179,12 +179,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= -k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= -k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= -k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= +k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= +k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= +k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= +k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= +k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= +k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= From 8358ad98c29ae4ddfe2a75d9b4aa45fe104c371c Mon Sep 17 00:00:00 2001 From: Johannes Malsam Date: Wed, 26 Aug 2026 13:51:02 +0200 Subject: [PATCH 082/638] fix: adjust central-db conn limit for central-worker (#22445) --- .../stackrox-central/config/centraldb/postgresql.conf.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image/templates/helm/stackrox-central/config/centraldb/postgresql.conf.default b/image/templates/helm/stackrox-central/config/centraldb/postgresql.conf.default index 6f9ab796c5950..dc29c9af950b7 100644 --- a/image/templates/helm/stackrox-central/config/centraldb/postgresql.conf.default +++ b/image/templates/helm/stackrox-central/config/centraldb/postgresql.conf.default @@ -1,6 +1,6 @@ hba_file = '/etc/stackrox.d/config/pg_hba.conf' listen_addresses = '*' -max_connections = 100 +max_connections = 120 password_encryption = scram-sha-256 ssl = on From 8eddeb88d3ec158f560a1bc18fee5460f59927d6 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 26 Aug 2026 08:32:20 -0400 Subject: [PATCH 083/638] ROX-36493: Move validation schema to same folder as wizard step (#22409) --- .../Reports/Step/validationSchemaDelivery.ts | 54 +++++++++++++++++++ .../Reports/Step/validationSchemaDetails.ts | 8 +++ 2 files changed, 62 insertions(+) create mode 100644 ui/apps/platform/src/Components/Reports/Step/validationSchemaDelivery.ts create mode 100644 ui/apps/platform/src/Components/Reports/Step/validationSchemaDetails.ts diff --git a/ui/apps/platform/src/Components/Reports/Step/validationSchemaDelivery.ts b/ui/apps/platform/src/Components/Reports/Step/validationSchemaDelivery.ts new file mode 100644 index 0000000000000..e5b46817efd9b --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/Step/validationSchemaDelivery.ts @@ -0,0 +1,54 @@ +import * as yup from 'yup'; + +import type { Schedule } from 'types/schedule.proto'; + +import type { DeliveryType } from '../reports.types'; + +export const validationSchemaDelivery: yup.ObjectSchema = yup.object().shape({ + notifiers: yup + .array() + .of( + yup.object().shape({ + emailConfig: yup.object().shape({ + notifierId: yup.string().required('Email notifier is required'), + mailingLists: yup + .array() + .of(yup.string().required()) + .min(1, 'At least one distribution list email is required') + .required(), + customSubject: yup.string().defined(), + customBody: yup.string().defined(), + }), + notifierName: yup.string().defined(), + }) + ) + .defined(), + schedule: yup + .object() + .nullable() + .defined() + .test('schedule-days-required', 'At least one day must be selected', function test(value) { + if (value == null) { + return true; + } + // Schedule is a union type that .shape() can't express. + // Cast so property access is type-checked. + const schedule = value as Schedule; + if (schedule.intervalType === 'UNSET' || schedule.intervalType === 'DAILY') { + return true; + } + if ('daysOfWeek' in schedule && schedule.daysOfWeek.days.length === 0) { + return this.createError({ + path: 'schedule.daysOfWeek.days', + message: 'At least one day must be selected', + }); + } + if ('daysOfMonth' in schedule && schedule.daysOfMonth.days.length === 0) { + return this.createError({ + path: 'schedule.daysOfMonth.days', + message: 'At least one day must be selected', + }); + } + return true; + }) as unknown as yup.Schema, +}); diff --git a/ui/apps/platform/src/Components/Reports/Step/validationSchemaDetails.ts b/ui/apps/platform/src/Components/Reports/Step/validationSchemaDetails.ts new file mode 100644 index 0000000000000..d2581af95678e --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/Step/validationSchemaDetails.ts @@ -0,0 +1,8 @@ +import * as yup from 'yup'; + +import type { DetailsType } from '../reports.types'; + +export const validationSchemaDetails: yup.ObjectSchema = yup.object().shape({ + name: yup.string().trim().required('Report name is required'), + description: yup.string().ensure(), +}); From 6aec7462e4c6153c6488f4142b9005aaf034901e Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:51:44 +0200 Subject: [PATCH 084/638] ROX-32849: record discovered VM facts for pull-mode scrapes (#21515) --- .../common/virtualmachine/vmscraper/facts.go | 29 ++++++++++++++++++ .../virtualmachine/vmscraper/facts_test.go | 30 +++++++++++++++++++ .../virtualmachine/vmscraper/scraper.go | 12 +------- 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 sensor/common/virtualmachine/vmscraper/facts.go create mode 100644 sensor/common/virtualmachine/vmscraper/facts_test.go diff --git a/sensor/common/virtualmachine/vmscraper/facts.go b/sensor/common/virtualmachine/vmscraper/facts.go new file mode 100644 index 0000000000000..6c05c550e8381 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/facts.go @@ -0,0 +1,29 @@ +package vmscraper + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" +) + +// logAndRecordDiscoveredFacts logs and records metrics for the VM facts +// (detected OS, activation status, DNF metadata status) that roxagent reports +// alongside every pulled index report. +func logAndRecordDiscoveredFacts(key string, facts map[string]string) { + if len(facts) == 0 { + return + } + + detectedOS := facts["detected_os"] + osVersion := facts["os_version"] + activationStatus := facts["activation_status"] + dnfMetadataStatus := facts["dnf_metadata_status"] + + log.Debugf("VMScraper: VM discovered data for %q: detected_os=%s, os_version=%q, activation_status=%s, dnf_metadata_status=%s", + key, detectedOS, osVersion, activationStatus, dnfMetadataStatus) + + metrics.VMDiscoveredData.With(prometheus.Labels{ + "detected_os": detectedOS, + "activation_status": activationStatus, + "dnf_metadata_status": dnfMetadataStatus, + }).Inc() +} diff --git a/sensor/common/virtualmachine/vmscraper/facts_test.go b/sensor/common/virtualmachine/vmscraper/facts_test.go new file mode 100644 index 0000000000000..9738af4dbe9c8 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/facts_test.go @@ -0,0 +1,30 @@ +package vmscraper + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" + "github.com/stretchr/testify/assert" +) + +func TestLogAndRecordDiscoveredFacts(t *testing.T) { + t.Run("empty facts map records nothing", func(t *testing.T) { + before := testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE")) + logAndRecordDiscoveredFacts("ns/vm-empty", nil) + assert.Equal(t, before, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) + }) + + t.Run("populated facts increment discovered data metric", func(t *testing.T) { + facts := map[string]string{ + "detected_os": "RHEL", + "os_version": "9.7", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + } + + before := testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE")) + logAndRecordDiscoveredFacts("ns/vm-1", facts) + assert.Equal(t, before+1, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) + }) +} diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index a74781846bb07..e0357d1059600 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -449,7 +449,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool reportSize := proto.Size(result.IndexReport) metrics.PullReportBytes.Observe(float64(reportSize)) metrics.PullReportPackages.Observe(float64(len(result.IndexReport.GetContents().GetPackages()))) - recordVMDiscoveredData(result.Meta.GetFacts()) + logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) if err := s.sender.Send(vmCtx, vm, result.IndexReport); err != nil { log.Errorf("VMScraper: sending %q report to Central failed: %v", key, err) @@ -644,13 +644,3 @@ func (s *VMScraper) commitVMState(key string, newGen, newEpoch uint32) { state.lastForwardedAt = s.now() }) } - -// recordVMDiscoveredData increments VMDiscoveredData from ResponseMeta.facts -// keys written by roxagent (detected_os, activation_status, dnf_metadata_status). -func recordVMDiscoveredData(facts map[string]string) { - metrics.VMDiscoveredData.WithLabelValues( - facts["detected_os"], - facts["activation_status"], - facts["dnf_metadata_status"], - ).Inc() -} From 5f0a4432f74f5d4bb6afe3781367e25a51202ffd Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Wed, 26 Aug 2026 08:09:48 -0500 Subject: [PATCH 085/638] ROX-35987: Add CVE origin to generated reports (#22428) --- .../scheduler/v2/reportgenerator/csv_gen.go | 25 +++++++++++++++++++ .../v2/reportgenerator/report_gen_impl.go | 2 ++ .../scheduler/v2/reportgenerator/types.go | 8 ++++++ 3 files changed, 35 insertions(+) diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 42b115b6776b1..451ce5e985b25 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -10,6 +10,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/csv" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/stringutils" @@ -34,9 +35,32 @@ var ( "Reference", "Advisory Name", "Advisory Link", + "CVE Origin", + } + + // originDisplayNames maps VulnOrigin enum values to human-readable names for the + // CSV report. This must be kept in sync with the equivalent UI map. + originDisplayNames = map[storage.VulnOrigin]string{ + storage.VulnOrigin_VULN_ORIGIN_ALPINE: "Alpine Linux", + storage.VulnOrigin_VULN_ORIGIN_AMAZON: "Amazon Linux", + storage.VulnOrigin_VULN_ORIGIN_DEBIAN: "Debian", + storage.VulnOrigin_VULN_ORIGIN_ORACLE: "Oracle Linux", + storage.VulnOrigin_VULN_ORIGIN_OSV: "OSV.dev", + storage.VulnOrigin_VULN_ORIGIN_PHOTON: "Photon OS", + storage.VulnOrigin_VULN_ORIGIN_RED_HAT: "Red Hat", + storage.VulnOrigin_VULN_ORIGIN_SUSE: "SUSE", + storage.VulnOrigin_VULN_ORIGIN_UBUNTU: "Ubuntu", + storage.VulnOrigin_VULN_ORIGIN_OTHER: "Other", } ) +func originDisplayName(origin storage.VulnOrigin) string { + if name, ok := originDisplayNames[origin]; ok { + return name + } + return origin.String() +} + // GenerateCSV takes in the results of vuln report query, converts to CSV and returns zipped data func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*bytes.Buffer, error) { // add header for component version @@ -84,6 +108,7 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt r.Link, r.GetAdvisoryName(), r.GetAdvisoryLink(), + originDisplayName(r.GetOrigin()), ) csvWriter.AddValue(row) } diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index 3a6bd2c511160..5ac86ba86be47 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -558,6 +558,7 @@ func getSelectsWatchedImages() []*v1.QuerySelect { search.NewQuerySelect(search.CisaKev).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), + search.NewQuerySelect(search.CVEOrigin).Proto(), } return ret } @@ -582,6 +583,7 @@ func getSelectsDeployedImages() []*v1.QuerySelect { search.NewQuerySelect(search.CisaKev).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), + search.NewQuerySelect(search.CVEOrigin).Proto(), } return ret } diff --git a/central/reports/scheduler/v2/reportgenerator/types.go b/central/reports/scheduler/v2/reportgenerator/types.go index 5f0fce8f81582..ff2450ffbe716 100644 --- a/central/reports/scheduler/v2/reportgenerator/types.go +++ b/central/reports/scheduler/v2/reportgenerator/types.go @@ -49,6 +49,7 @@ type ImageCVEQueryResponse struct { DiscoveredAtImage *time.Time `db:"first_image_occurrence_timestamp"` AdvisoryName *string `db:"advisory_name"` AdvisoryLink *string `db:"advisory_link"` + Origin *storage.VulnOrigin `db:"cve_origin"` Link string } @@ -137,6 +138,13 @@ func (res *ImageCVEQueryResponse) GetCVSS() float64 { return *res.CVSS } +func (res *ImageCVEQueryResponse) GetOrigin() storage.VulnOrigin { + if res.Origin == nil { + return storage.VulnOrigin_VULN_ORIGIN_OTHER + } + return *res.Origin +} + func (res *ImageCVEQueryResponse) GetNVDCVSS() float64 { if res.NVDCVSS == nil { return 0.0 From 3784502e628f8c93553c2120bc98bb6732e8305b Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:10:23 +0200 Subject: [PATCH 086/638] ROX-35849: Cap VMScraper starts per tick (#22403) --- sensor/common/virtualmachine/ACK_MODEL.md | 24 ++-- .../common/virtualmachine/vmscraper/budget.go | 17 +++ .../virtualmachine/vmscraper/budget_test.go | 48 +++++++ .../virtualmachine/vmscraper/scraper.go | 12 +- .../virtualmachine/vmscraper/scraper_test.go | 24 +++- .../vmscraper/spreading_test.go | 131 ++++++++++++++++++ 6 files changed, 241 insertions(+), 15 deletions(-) create mode 100644 sensor/common/virtualmachine/vmscraper/budget.go create mode 100644 sensor/common/virtualmachine/vmscraper/budget_test.go diff --git a/sensor/common/virtualmachine/ACK_MODEL.md b/sensor/common/virtualmachine/ACK_MODEL.md index ca9318eeed1c1..f606901279cad 100644 --- a/sensor/common/virtualmachine/ACK_MODEL.md +++ b/sensor/common/virtualmachine/ACK_MODEL.md @@ -2,11 +2,17 @@ VM index reports flow directly from Sensor to Central; Compliance is not involved (see `compliance/ACK_MODEL.md` for the separate node-scanning ACK model, which does go through Compliance). -`VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central through `vmIndex.Handler`, the same handler used by every other producer of `v1.IndexReport`s. +`VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central through `vmIndex.Handler`, which wraps the Scanner V4 payload as a `v1.IndexReport`. -A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks the per-VM schedule. Each VM is due at its own `nextAttemptAt`. Success and permanent failures reschedule at pollInterval + U(0, W), where W is `ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION` times the poll interval (default 2/3). Retryable pull failures and NACKs share one exponential backoff (`ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF`, default 10s, doubled each time, capped at `min(poll interval, 30m)`). +A successful forward already returns the VM to poll cadence. Central's ACK is counted (`IndexReportAcksReceived`) and otherwise ignored. A NACK clears cached `report_generation` and applies the shared retry backoff so the next attempt pulls a full report. There is no payload cache and no ACK timeout. -Central's `SensorACK` is delivered to Sensor components whose `Accepts()` matches. Only `VMScraper` accepts `SensorACK_VM_INDEX_REPORT`; `Handler` does not. +A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks the per-VM schedule. Each VM is due at its own `nextAttemptAt`. Success and permanent failures reschedule at `pollInterval + U(0, W)`, where `pollInterval` is `ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL` (default 4h) and `W` is `ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION` times that interval (default 2/3). + +`C` is `newVMIndexReportWindow`: `min(20m, pollInterval / 3)` (20m at the default 4h poll interval). Newly tracked VMs are first due at `now + U(0, C)`. A tick starts at most `startBudget` of the due set (`ceil(nTracked × tick / C)`, at least 1), then capped by concurrency. `nTracked` is the fleet in `vmState`, not the leftover due pile, so the drain rate stays stable as due VMs are started. + +Retryable pull failures and NACKs share one exponential backoff (`ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF`, default 10s, doubled each time, capped at `min(pollInterval, 30m)`). + +Central's `SensorACK` is delivered to Sensor components whose `Accepts()` matches. `Handler` advertises `SensorACKSupport` so Central sends these ACKs; only `VMScraper` accepts `SensorACK_VM_INDEX_REPORT`. ```mermaid sequenceDiagram @@ -21,18 +27,20 @@ sequenceDiagram H->>X: SensorEvent(vmID, index report) X-->>Sc: SensorACK(vmID:vsockCID, ACK|NACK, reason) - alt NACK + alt ACK + Note over Sc: recorded; schedule unchanged + else NACK Sc->>Sc: clear cached generation; apply retry backoff Note over Sc,A: next due attempt requests a full report end ``` -`VMScraper` records ACK/NACK volume (`IndexReportAcksReceived`) and, on NACK, resets its cached `report_generation` so the next due attempt re-sends a full report instead of an "unchanged" delta. There is no payload cache. - ## Why the resource ID is `vmID:vsockCID` Using only the vsock CID is unsafe because a CID can be reused by a different VM later, so a stale ACK could otherwise get attributed to the wrong VM's report. Pairing it with `vmID` removes that ambiguity when parsing logs or correlating an ACK back to a specific VM and connection. -`VMScraper`'s NACK handling only needs the `vmID` half - it extracts that portion (`vmIDFromResourceID`) and looks the VM up by ID in its own store; the CID is not used for VSOCK routing on this path. It stays in the pair purely for debugging/observability, not because pull mode's retry logic requires it. +`VMScraper`'s NACK handling only needs the `vmID` half: it extracts that portion (`vmIDFromResourceID`) and looks the VM up by ID in its own store. The CID is not used for VSOCK routing on this path; it stays in the pair for debugging. + +The pair is not a per-report unique ID: multiple in-flight reports from the same VM with the same CID are still not fully distinguishable, so a stale ACK can match the latest entry instead of the one it was actually for. -One limitation remains: `vmID:vsockCID` is not a per-report unique ID - multiple in-flight reports from the same VM with the same CID are still not fully distinguishable, so a stale ACK can match the latest entry instead of the one it was actually for. +A NACK can also lose a race with the post-`Send` commit of `lastGeneration`, in which case the next poll may still see an "unchanged" delta. diff --git a/sensor/common/virtualmachine/vmscraper/budget.go b/sensor/common/virtualmachine/vmscraper/budget.go new file mode 100644 index 0000000000000..a0c04e907cd22 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/budget.go @@ -0,0 +1,17 @@ +package vmscraper + +import ( + "time" +) + +// startBudget is max(1, ceil(n × tick / window)) for positive n, tick, and +// window. n is tracked fleet size so a leftover due pile keeps a stable drain +// rate. Zero otherwise so a missing window does not start a scrape. +func startBudget(n int, tick, window time.Duration) int { + if n <= 0 || tick <= 0 || window <= 0 { + return 0 + } + num := int64(n) * int64(tick) + den := int64(window) + return max(1, int((num+den-1)/den)) +} diff --git a/sensor/common/virtualmachine/vmscraper/budget_test.go b/sensor/common/virtualmachine/vmscraper/budget_test.go new file mode 100644 index 0000000000000..f9ad40acda9f9 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/budget_test.go @@ -0,0 +1,48 @@ +package vmscraper + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestStartBudget(t *testing.T) { + t.Parallel() + + tick := 10 * time.Second + cases := map[string]struct { + n int + tick time.Duration + window time.Duration + want int + }{ + "n=100 catchUp=20m -> 1": { + n: 100, tick: tick, window: 20 * time.Minute, want: 1, + }, + "n=100 steadyWidth=40m -> 1": { + n: 100, tick: tick, window: 40 * time.Minute, want: 1, + }, + "n=100 window=5m -> ceil(100*10/300)=4": { + n: 100, tick: tick, window: 5 * time.Minute, want: 4, + }, + "n=1 catchUp=20m -> max(1, ceil)": { + n: 1, tick: tick, window: 20 * time.Minute, want: 1, + }, + "n=0 -> 0": { + n: 0, tick: tick, window: 20 * time.Minute, want: 0, + }, + "zero window -> 0": { + n: 10, tick: tick, window: 0, want: 0, + }, + "zero tick -> 0": { + n: 10, tick: 0, window: 20 * time.Minute, want: 0, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, startBudget(tc.n, tc.tick, tc.window)) + }) + } +} diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index e0357d1059600..66655c3866334 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -267,12 +267,18 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { } due := s.dueKeys() + nTracked := concurrency.WithLock1(&s.mu, func() int { + n := len(s.vmState) + metrics.PullTrackedVMs.Set(float64(n)) + return n + }) metrics.PullDueVMs.Set(float64(len(due))) + capN := min(s.concurrency, startBudget(nTracked, s.tickInterval, newVMIndexReportWindow(s.interval))) + if capN < len(due) { + due = due[:capN] + } log.Debugf("VMScraper: tick: %d due VMs %v (concurrency=%d, reconcile=%v)", len(due), due, s.concurrency, reconcile) metrics.PullTicksTotal.Inc() - concurrency.WithLock(&s.mu, func() { - metrics.PullTrackedVMs.Set(float64(len(s.vmState))) - }) // A tick launches every due VM, so this is the start count. started := len(due) diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 2ebe838f1cf3d..f13ac283b636a 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -87,6 +87,7 @@ func (nopCloser) Write([]byte) (int, error) { return 0, nil } func (nopCloser) Close() error { return nil } type mockProtocolClient struct { + mu sync.Mutex resultQueue []*vsockclient.GetReportResult errQueue []error calls []protocolCall @@ -99,6 +100,8 @@ type protocolCall struct { } func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, ifNewerThan uint32, knownEpoch uint32) (*vsockclient.GetReportResult, error) { + m.mu.Lock() + defer m.mu.Unlock() m.calls = append(m.calls, protocolCall{ifNewerThan: ifNewerThan, knownEpoch: knownEpoch}) idx := m.callIdx m.callIdx++ @@ -112,16 +115,21 @@ func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, } func (m *mockProtocolClient) reset() { + m.mu.Lock() + defer m.mu.Unlock() m.calls = nil m.callIdx = 0 } type mockSender struct { + mu sync.Mutex sent []*v4.IndexReport err error } func (m *mockSender) Send(_ context.Context, _ *virtualmachine.Info, report *v4.IndexReport) error { + m.mu.Lock() + defer m.mu.Unlock() if m.err != nil { return m.err } @@ -749,7 +757,7 @@ func cachedGeneration(t *testing.T, s *VMScraper, key string) uint32 { func newTestScraper(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { clock := newTestClock() interval := 5 * time.Minute - return &VMScraper{ + s := &VMScraper{ store: store, sender: sender, dialer: dialer, @@ -760,7 +768,7 @@ func newTestScraper(store RunningVMStore, sender IndexReportSender, dialer VMDia reconcileEvery: reconcilePeriod(interval), perVMTimeout: 10 * time.Second, mandatoryRefreshAfter: 4 * time.Hour, - concurrency: 1, + concurrency: 20, // Half of the 16MiB default pull response-size ceiling — same // derivation New() uses from env.VirtualMachinesPullMaxResponseSizeKB. warnMaxBytes: 8 << 20, @@ -769,14 +777,22 @@ func newTestScraper(store RunningVMStore, sender IndexReportSender, dialer VMDia inFlight: set.NewStringSet(), now: clock.Now, randFloat64: func() float64 { return 0 }, - }, clock + } + setTickToDrain(s) + return s, clock } -// pollOnce forces a reconcile and scrapes every due slot. +// pollOnce forces a reconcile and scrapes due slots (subject to the per-tick start cap). func (s *VMScraper) pollOnce(ctx context.Context) { s.tick(ctx, true) } +// setTickToDrain sets tickInterval to the new-VM index report window so one +// tick can start every never-scraped due VM under concurrency. +func setTickToDrain(s *VMScraper) { + s.tickInterval = newVMIndexReportWindow(s.interval) +} + // --- Thread-safe mocks for concurrent tests --- // delayDialer sleeps for delay on every Dial and tracks the highest number diff --git a/sensor/common/virtualmachine/vmscraper/spreading_test.go b/sensor/common/virtualmachine/vmscraper/spreading_test.go index adf8718224130..2bc12766bdf70 100644 --- a/sensor/common/virtualmachine/vmscraper/spreading_test.go +++ b/sensor/common/virtualmachine/vmscraper/spreading_test.go @@ -3,6 +3,7 @@ package vmscraper import ( "context" "fmt" + "io" "sync/atomic" "testing" "time" @@ -21,6 +22,16 @@ import ( "go.uber.org/zap/zaptest/observer" ) +// recordingDialer counts Dial calls so tests can assert how many scrapes a tick started. +type recordingDialer struct { + calls atomic.Int32 +} + +func (d *recordingDialer) Dial(_ context.Context, _, _ string, _ uint32, _ bool) (io.ReadWriteCloser, error) { + d.calls.Add(1) + return nopCloser{}, nil +} + // sequencedRand returns successive values from seq, then the last value. func sequencedRand(seq []float64) func() float64 { var i atomic.Int32 @@ -62,6 +73,7 @@ func TestVMScraper_ManyCadencedSuccessesSpanSteadyBand(t *testing.T) { s.concurrency = numVMs s.interval = time.Hour s.spreadFraction = 2.0 / 3 + s.tickInterval = newVMIndexReportWindow(s.interval) s.reconcileEvery = reconcilePeriod(s.interval) s.randFloat64 = sequencedRand([]float64{0, 0.25, 0.5, 0.75, 1}) @@ -194,6 +206,125 @@ func TestVMScraper_NACKPathDoesNotUseSteadyBand(t *testing.T) { assert.Equal(t, initialBackoff, cachedBackoff(t, s, "ns1/vm-a")) } +func TestVMScraper_DuePileIsPacedByStartBudget(t *testing.T) { + const numVMs = 10 + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) + } + dialer := &recordingDialer{} + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.interval = time.Hour + s.tickInterval = defaultTickInterval + s.reconcileEvery = reconcilePeriod(s.interval) + + now := s.now() + concurrency.WithLock(&s.mu, func() { + for _, vm := range vms { + s.vmState[vm.Key()] = &vmState{ + vmID: vm.ID, + nextAttemptAt: now, + } + } + s.lastReconcile = now + }) + + window := newVMIndexReportWindow(s.interval) + want := startBudget(numVMs, s.tickInterval, window) + require.Equal(t, 1, want) + require.Greater(t, want, 0) + require.Less(t, want, numVMs) + require.LessOrEqual(t, want, s.concurrency) + + s.tick(context.Background(), false) + assert.Equal(t, int32(want), dialer.calls.Load()) + + dialer.calls.Store(0) + s.tick(context.Background(), false) + assert.Equal(t, int32(want), dialer.calls.Load(), "leftovers stay due for later ticks") +} + +// TestVMScraper_AllDueClumpDrainsWithinIndexWindow covers a 100-VM all-due +// clump: fleet-sized budget drains it in one index window. +func TestVMScraper_AllDueClumpDrainsWithinIndexWindow(t *testing.T) { + const numVMs = 100 + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) + } + dialer := &recordingDialer{} + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.tickInterval = defaultTickInterval + s.reconcileEvery = reconcilePeriod(s.interval) + + now := s.now() + concurrency.WithLock(&s.mu, func() { + for _, vm := range vms { + s.vmState[vm.Key()] = &vmState{ + vmID: vm.ID, + nextAttemptAt: now, + } + } + s.lastReconcile = now + }) + + window := newVMIndexReportWindow(s.interval) + require.Equal(t, 100*time.Second, window) + perTick := startBudget(numVMs, s.tickInterval, window) + require.Equal(t, 10, perTick, "ceil(100 × 10s / 100s) = 10") + ticks := (numVMs + perTick - 1) / perTick + require.Equal(t, 10, ticks) + + for range ticks { + s.tick(context.Background(), false) + } + assert.Equal(t, int32(numVMs), dialer.calls.Load(), + "a shrinking-n budget would start only 55 of 100 in 10 ticks (10+9+…+1)") + assert.Empty(t, s.dueKeys(), "the clump should have left the due pile within the index window") +} + +// TestVMScraper_SpreadDuePileStartsAtFleetRate covers VMs already spread by +// nextAttemptAt: the due snapshot is smaller than the fleet, but the rate +// must still be the fleet cap so every currently-due VM can start. +func TestVMScraper_SpreadDuePileStartsAtFleetRate(t *testing.T) { + const ( + numVMs = 100 + nDue = 10 + ) + vms := make([]*virtualmachine.Info, 0, numVMs) + for i := range numVMs { + vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) + } + dialer := &recordingDialer{} + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s.concurrency = numVMs + s.tickInterval = defaultTickInterval + s.reconcileEvery = reconcilePeriod(s.interval) + + now := s.now() + concurrency.WithLock(&s.mu, func() { + for i, vm := range vms { + st := &vmState{ + vmID: vm.ID, + nextAttemptAt: now.Add(time.Hour), + } + if i < nDue { + st.nextAttemptAt = now + } + s.vmState[vm.Key()] = st + } + s.lastReconcile = now + }) + require.Len(t, s.dueKeys(), nDue) + + s.tick(context.Background(), false) + assert.Equal(t, int32(nDue), dialer.calls.Load(), + "a due-pile budget would start 1 (ceil(10 × 10s / 100s)), not all 10 due") + assert.Empty(t, s.dueKeys()) +} + func TestSpreadFractionEnvDefault(t *testing.T) { assert.InDelta(t, 2.0/3, env.VirtualMachinesScraperSteadySpreadFraction.DefaultValue(), 1e-9) assert.InDelta(t, 2.0/3, env.VirtualMachinesScraperSteadySpreadFraction.FloatSetting(), 1e-9) From ad349abbc0b089c569456ed46faceadf9225b6c8 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:12:09 +0200 Subject: [PATCH 087/638] ROX-35191: replace RESTConfig revert with kubevirt client-go dialer (#22245) --- go.mod | 36 ++-- go.sum | 130 +++++++++---- sensor/debugger/certs/cert_fetcher_test.go | 5 - sensor/debugger/k8s/k8sclient.go | 10 +- sensor/kubernetes/client/client.go | 9 - .../kubernetes/clusterstatus/updater_test.go | 5 - sensor/kubernetes/fake/fake.go | 17 -- sensor/kubernetes/fake/rest_config_test.go | 28 --- sensor/kubernetes/sensor/sensor.go | 10 +- .../virtualmachine/vsockdialer/dialer.go | 174 +++--------------- .../virtualmachine/vsockdialer/dialer_test.go | 50 ----- 11 files changed, 147 insertions(+), 327 deletions(-) delete mode 100644 sensor/kubernetes/fake/rest_config_test.go delete mode 100644 sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go diff --git a/go.mod b/go.mod index bd702bba8ee2b..b6fba624d881b 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,6 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.23.0 github.com/gorilla/schema v1.4.1 - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/pyroscope-go v1.4.2 github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 @@ -168,16 +167,17 @@ require ( gorm.io/driver/postgres v1.6.2 gorm.io/gorm v1.31.2 helm.sh/helm/v3 v3.21.3 - k8s.io/api v0.36.3 - k8s.io/apiextensions-apiserver v0.36.3 - k8s.io/apimachinery v0.36.3 - k8s.io/apiserver v0.36.3 - k8s.io/cli-runtime v0.36.3 - k8s.io/client-go v0.36.3 - k8s.io/kubectl v0.36.3 + k8s.io/api v0.36.4 + k8s.io/apiextensions-apiserver v0.36.4 + k8s.io/apimachinery v0.36.4 + k8s.io/apiserver v0.36.4 + k8s.io/cli-runtime v0.36.4 + k8s.io/client-go v0.36.4 + k8s.io/kubectl v0.36.4 k8s.io/kubelet v0.32.13 k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 kubevirt.io/api v1.9.0 + kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/controller-tools v0.21.0 sigs.k8s.io/e2e-framework v0.7.0 @@ -280,6 +280,8 @@ require ( github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-git/go-git/v5 v5.19.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.25.2 // indirect github.com/go-openapi/errors v0.22.8 // indirect @@ -318,6 +320,7 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -463,13 +466,13 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/code-generator v0.36.3 // indirect - k8s.io/component-base v0.36.3 // indirect - k8s.io/component-helpers v0.36.3 // indirect + k8s.io/code-generator v0.36.4 // indirect + k8s.io/component-base v0.36.4 // indirect + k8s.io/component-helpers v0.36.4 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect - k8s.io/streaming v0.36.3 // indirect + k8s.io/kube-openapi v0.31.0 // indirect + k8s.io/streaming v0.36.4 // indirect kubevirt.io/containerized-data-importer-api v1.64.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect modernc.org/libc v1.73.4 // indirect @@ -538,6 +541,13 @@ replace ( gopkg.in/yaml.v3 => github.com/stackrox/yaml/v3 v3.0.0 ) +// No tagged kubevirt.io/client-go includes kubevirt#18315, required for +// kubevirt client-go against k8s.io/client-go v0.36. Pinned to +// kubevirt/client-go@f38626e. + +// kubevirt.io/client-go requires the nonexistent k8s.io/kube-openapi@v0.31.0 tag. +replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 + // @stackrox/scanner replace ( github.com/facebookincubator/nvdtools => github.com/stackrox/nvdtools v0.0.0-20231111002313-57e262e4797e diff --git a/go.sum b/go.sum index 566bd71101b29..d614a270d12a4 100644 --- a/go.sum +++ b/go.sum @@ -158,7 +158,6 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -212,7 +211,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.43.4 h1:b9FTvbRwy+JCsfp2Wp6wV/KbOx3Aj7nkoFb2cRX0IhE= @@ -412,9 +410,9 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -473,7 +471,6 @@ github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+r github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/georgysavva/scany/v2 v2.1.4 h1:nrzHEJ4oQVRoiKmocRqA1IyGOmM/GQOEsg9UjMR5Ip4= github.com/georgysavva/scany/v2 v2.1.4/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4= github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9 h1:r5GgOLGbza2wVHRzK7aAj6lWZjfbAwiu/RDCVOKjRyM= @@ -510,12 +507,18 @@ github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -527,12 +530,13 @@ github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5 github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= github.com/go-openapi/loads v0.24.0 h1:4LLorXRPTzIN9V6ngMUZbAscsBOUBk3Oa8cClu/bFrQ= @@ -548,6 +552,8 @@ github.com/go-openapi/strfmt v0.26.4/go.mod h1:hNJi6nb5ETD6i7A1yRo03M9S6ZoTPPoWf github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= github.com/go-openapi/swag/cmdutils v0.26.1 h1:f2iE1ijYaJ3nuu5PaEMx3zpEhzhZFgivCJObWEObLIQ= @@ -664,6 +670,7 @@ github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -736,13 +743,11 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE= github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= @@ -912,6 +917,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1025,7 +1031,6 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1 github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1056,7 +1061,6 @@ github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -1067,7 +1071,6 @@ github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -1194,6 +1197,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= @@ -1344,6 +1348,7 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -1517,8 +1522,10 @@ go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= @@ -1538,10 +1545,15 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1585,9 +1597,15 @@ golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1647,10 +1665,15 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1667,9 +1690,13 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ 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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1762,23 +1789,34 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1796,6 +1834,10 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1867,9 +1909,16 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.34.1-0.20250613162507-3f93fece84c7/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -2042,6 +2091,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -2087,52 +2137,55 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= -k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= -k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= +k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= +k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= +k8s.io/apiextensions-apiserver v0.36.4 h1:SfvCVt+4CqKWvzuVytYDT5g9hyb9MztoiYELIkPVrFc= +k8s.io/apiextensions-apiserver v0.36.4/go.mod h1:JT9V2Ju7ys1FY4zbSpmX9XOvKB3/BwsODc4hFQEa+Xo= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= -k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/apiserver v0.36.3 h1:MGSg2SkdfuytiDEcRylT5mQFmmSsbx90XFUO67Y4bsQ= -k8s.io/apiserver v0.36.3/go.mod h1:fVH7zv9EUNUA7Fl7LtDKh8aB9W7u1VQPSGtWV5SjUxg= -k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U= -k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE= -k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= -k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= +k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= +k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= +k8s.io/apiserver v0.36.4 h1:AtKjaf2eUiX5G6TfF2IOlhuUuvMsHh49Ivr1+4fZ2gA= +k8s.io/apiserver v0.36.4/go.mod h1:RyiGghXP67hb0Ll+7iLJ6GGv2JpEzCn7ljbiA+L3cJ0= +k8s.io/cli-runtime v0.36.4 h1:OHvManCwP1k9GiC5tXRFxHhzZIQQFCsrHlt7OspKo3w= +k8s.io/cli-runtime v0.36.4/go.mod h1:qQSj2FJgQos6GHpS/ge7wTdQMZm9XFWlesWgV6h7qZY= +k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= +k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/code-generator v0.36.3 h1:tsiHI6NepXQncnexlTAf52w5VxZ4HYDU4ZqCNLFb9tA= -k8s.io/code-generator v0.36.3/go.mod h1:Unn13Mp8X+H803jgZi4f4ExxK11aj0llXcSsl++UTkE= -k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= -k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= -k8s.io/component-helpers v0.36.3 h1:hya22S0Mto0SlHaiD4kMIi817f/tK7uTMsShxrDKQaY= -k8s.io/component-helpers v0.36.3/go.mod h1:QjREK1lOFXR+jxTqzrtHgOtzUc2s9sm8zuFSiK+TW+c= +k8s.io/code-generator v0.36.4 h1:KNW2Alv5LVoGiooEYh1GY982CGyg53u1inHu6Zi4bIA= +k8s.io/code-generator v0.36.4/go.mod h1:JmRrlYwTOn2glEuGOfNnlXTGbz3FfMg7t7Dq/8lHcc4= +k8s.io/component-base v0.36.4 h1:tz75yC2xgq3kd7vPdBtR8do5iMx0OHf6Zd1kuaxDB84= +k8s.io/component-base v0.36.4/go.mod h1:DCwb306U8ou89NNAp45Csuy8ok+1rp1ELDVPhzN5AWc= +k8s.io/component-helpers v0.36.4 h1:B5+SnptAlwTVJO0MW1JLsZVp4KevlADRg0CX5YFzjbU= +k8s.io/component-helpers v0.36.4/go.mod h1:RECUDkRdVuxcNOAl9/EK7lmy1TIjoWkBHs6f0uOyoCs= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI= -k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E= +k8s.io/kubectl v0.36.4 h1:xZd9g1bFBd7hpb1oKjK8lT9jRL18dtgr4DAPQG1Oksk= +k8s.io/kubectl v0.36.4/go.mod h1:STWlr78cdEa1hHpr55wpcboaqchvfDueKRNDa1zOd1w= k8s.io/kubelet v0.32.13 h1:pGSrLTytcmuIlq4yvuRFnF4RdQjQh1FfsmPeRZXDKTo= k8s.io/kubelet v0.32.13/go.mod h1:XrwKgyKhVUE8TO/w9Lqq93UTM5W6t7ePItIgjvSmV/4= -k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= -k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/streaming v0.36.4 h1:RS5YlhrdBN2pKGVjgygGntdu6SNdsduyjGWGe3cX0vo= +k8s.io/streaming v0.36.4/go.mod h1:tJ6S2bZa2HxIBauguBbCWSCYyd93Grfz1+z3tcOvlDE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= kubevirt.io/api v1.9.0 h1:FWqx9yGk/GHUP5o4IthGXZ8FeAlgVNv2It48wD86UUo= kubevirt.io/api v1.9.0/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY= +kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426 h1:W7o8PnS4K+fLbaDSB8QhK2J18pcnRDs4QlfrxNspMng= +kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426/go.mod h1:bXmbwFVCJsUnFGg1lgVSB2Jc180N7IzlQRHXJxlrG6U= kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc= kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= @@ -2179,6 +2232,7 @@ sigs.k8s.io/controller-tools v0.21.0/go.mod h1:DLIypi3Q2+azVAP8jr/mHXJgveYYHFjhn sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM= sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= @@ -2187,14 +2241,16 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/network-policy-api v0.1.5 h1:xyS7VAaM9EfyB428oFk7WjWaCK6B129i+ILUF4C8l6E= sigs.k8s.io/network-policy-api v0.1.5/go.mod h1:D7Nkr43VLNd7iYryemnj8qf0N/WjBzTZDxYA+g4u1/Y= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= diff --git a/sensor/debugger/certs/cert_fetcher_test.go b/sensor/debugger/certs/cert_fetcher_test.go index c29d1543208d9..46a30fa58244e 100644 --- a/sensor/debugger/certs/cert_fetcher_test.go +++ b/sensor/debugger/certs/cert_fetcher_test.go @@ -18,7 +18,6 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/rest" ) func createTestSecret(name, namespace string, data map[string][]byte) *v1.Secret { @@ -35,10 +34,6 @@ type testClient struct { k8s kubernetes.Interface } -func (t *testClient) RESTConfig() *rest.Config { - return nil -} - func (t *testClient) Kubernetes() kubernetes.Interface { return t.k8s } diff --git a/sensor/debugger/k8s/k8sclient.go b/sensor/debugger/k8s/k8sclient.go index 776cbed85a014..c6cf88ec4371f 100644 --- a/sensor/debugger/k8s/k8sclient.go +++ b/sensor/debugger/k8s/k8sclient.go @@ -40,14 +40,12 @@ func MakeFakeClientFromRest(restConfig *rest.Config) *ClientSet { } return &ClientSet{ - restConfig: restConfig, - k8s: client, + k8s: client, } } // ClientSet is a test version of kubernetes.ClientSet type ClientSet struct { - restConfig *rest.Config dynamic dynamic.Interface k8s kubernetes.Interface openshiftApps appVersioned.Interface @@ -70,7 +68,6 @@ func MakeOutOfClusterClient() (*ClientSet, error) { } return &ClientSet{ - restConfig: config, k8s: k8sClient, dynamic: mustCreateDynamicClient(config), openshiftApps: mustCreateOpenshiftAppsClient(config), @@ -132,11 +129,6 @@ func mustCreateDynamicClient(config *rest.Config) dynamic.Interface { return client } -// RESTConfig returns the REST config used to create this client -func (c *ClientSet) RESTConfig() *rest.Config { - return c.restConfig -} - // Kubernetes returns the kubernetes interface func (c *ClientSet) Kubernetes() kubernetes.Interface { return c.k8s diff --git a/sensor/kubernetes/client/client.go b/sensor/kubernetes/client/client.go index c7f2b86903fb9..95f8cb60a5f4c 100644 --- a/sensor/kubernetes/client/client.go +++ b/sensor/kubernetes/client/client.go @@ -19,9 +19,6 @@ var ( // Interface implements an interface that bridges Kubernetes and Openshift type Interface interface { - // RESTConfig returns the underlying REST configuration. - // Callers must not modify the returned value; use rest.CopyConfig first if mutation is needed. - RESTConfig() *rest.Config Kubernetes() kubernetes.Interface Dynamic() dynamic.Interface OpenshiftApps() appVersioned.Interface @@ -31,7 +28,6 @@ type Interface interface { } type clientSet struct { - restConfig *rest.Config dynamic dynamic.Interface k8s kubernetes.Interface openshiftApps appVersioned.Interface @@ -95,7 +91,6 @@ func mustCreateDynamicClient(config *rest.Config) dynamic.Interface { // MustCreateInterfaceFromRest creates a client interface using a rest config as a parameter func MustCreateInterfaceFromRest(config *rest.Config) Interface { return &clientSet{ - restConfig: config, dynamic: mustCreateDynamicClient(config), k8s: k8sutil.MustCreateK8sClient(config), openshiftApps: mustCreateOpenshiftAppsClient(config), @@ -114,10 +109,6 @@ func MustCreateInterface() Interface { return MustCreateInterfaceFromRest(config) } -func (c *clientSet) RESTConfig() *rest.Config { - return c.restConfig -} - func (c *clientSet) Kubernetes() kubernetes.Interface { return c.k8s } diff --git a/sensor/kubernetes/clusterstatus/updater_test.go b/sensor/kubernetes/clusterstatus/updater_test.go index 4d8f6a31bd48e..41904afaf3a6c 100644 --- a/sensor/kubernetes/clusterstatus/updater_test.go +++ b/sensor/kubernetes/clusterstatus/updater_test.go @@ -22,7 +22,6 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/rest" ) type updaterSuite struct { @@ -39,10 +38,6 @@ type fakeClientSet struct { config configVersioned.Interface } -func (c *fakeClientSet) RESTConfig() *rest.Config { - return nil -} - func (c *fakeClientSet) Kubernetes() kubernetes.Interface { return c.k8s } diff --git a/sensor/kubernetes/fake/fake.go b/sensor/kubernetes/fake/fake.go index 13256a6875e7a..000e4e63a69bc 100644 --- a/sensor/kubernetes/fake/fake.go +++ b/sensor/kubernetes/fake/fake.go @@ -36,7 +36,6 @@ import ( fakeDynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/rest" ) const ( @@ -102,7 +101,6 @@ func init() { // clientSetImpl implements our client.Interface type clientSetImpl struct { - restConfig *rest.Config kubernetes kubernetes.Interface dynamic dynamic.Interface openshiftApps appVersioned.Interface @@ -111,11 +109,6 @@ type clientSetImpl struct { openshiftOperator operatorVersioned.Interface } -// RESTConfig returns the REST configuration injected via WithRESTConfig, or nil if none was set. -func (c *clientSetImpl) RESTConfig() *rest.Config { - return c.restConfig -} - // Kubernetes returns the fake Kubernetes clientset func (c *clientSetImpl) Kubernetes() kubernetes.Interface { return c.kubernetes @@ -152,7 +145,6 @@ type WorkloadManager struct { fakeClient *fake.Clientset dynamicClient *fakeDynamic.FakeDynamicClient client client.Interface - restConfig *rest.Config processPool *ProcessPool labelsPool *labelsPoolPerNamespace endpointPool *EndpointPool @@ -191,7 +183,6 @@ type WorkloadManagerConfig struct { externalIpPool *pool containerPool *pool storagePath string - restConfig *rest.Config } // ConfigDefaults default configuration @@ -256,12 +247,6 @@ func (c *WorkloadManagerConfig) WithStoragePath(path string) *WorkloadManagerCon return c } -// WithRESTConfig configures the WorkloadManagerConfig's REST config. -func (c *WorkloadManagerConfig) WithRESTConfig(restConfig *rest.Config) *WorkloadManagerConfig { - c.restConfig = restConfig - return c -} - // Client returns the mock client func (w *WorkloadManager) Client() client.Interface { return w.client @@ -299,7 +284,6 @@ func NewWorkloadManager(config *WorkloadManagerConfig) *WorkloadManager { db: db, workload: &workload, originatorCache: NewOriginatorCache(), - restConfig: config.restConfig, labelsPool: config.labelsPool, endpointPool: config.endpointPool, ipPool: config.ipPool, @@ -509,7 +493,6 @@ func (w *WorkloadManager) initializePreexistingResources() { dynClient := fakeDynamic.NewSimpleDynamicClientWithCustomListKinds(scheme, customListKinds) clientSet := &clientSetImpl{ - restConfig: w.restConfig, kubernetes: w.fakeClient, dynamic: dynClient, } diff --git a/sensor/kubernetes/fake/rest_config_test.go b/sensor/kubernetes/fake/rest_config_test.go deleted file mode 100644 index ae0d9e1419f21..0000000000000 --- a/sensor/kubernetes/fake/rest_config_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package fake - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/client-go/rest" -) - -func TestWorkloadManagerClientRESTConfigReturnsConfiguredValue(t *testing.T) { - workloadFile := filepath.Join(t.TempDir(), "workload.yaml") - require.NoError(t, os.WriteFile(workloadFile, []byte("{}\n"), 0o600)) - - wantConfig := &rest.Config{Host: "https://example.stackrox.invalid"} - config := ConfigDefaults(). - WithWorkloadFile(workloadFile). - WithStoragePath(""). - WithRESTConfig(wantConfig) - - manager := NewWorkloadManager(config) - require.NotNil(t, manager) - t.Cleanup(manager.Stop) - - assert.Same(t, wantConfig, manager.Client().RESTConfig()) -} diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index 93778b0189040..006543e04fbbf 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -204,14 +204,14 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { virtualMachineHandler = vmIndex.NewHandler(storeProvider.VirtualMachines()) components = append(components, virtualMachineHandler) - if kvConfig := cfg.k8sClient.RESTConfig(); kvConfig != nil { - pullMaxBytes := int64(env.VirtualMachinesPullMaxResponseSizeKB.IntegerSetting()) * 1024 - vmDial := vsockdialer.NewMultiDialer(kvConfig, pullMaxBytes) + pullMaxBytes := int64(env.VirtualMachinesPullMaxResponseSizeKB.IntegerSetting()) * 1024 + vmDial, err := vsockdialer.NewMultiDialer() + if err != nil { + log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) + } else { vmProtoClient := vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) scraper := vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient) components = append(components, scraper) - } else { - log.Warn("VSOCK pull mode disabled (no REST config available)") } } diff --git a/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go b/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go index 1842b2e42dce9..0161e4a61fc7a 100644 --- a/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go +++ b/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go @@ -1,181 +1,57 @@ // Package vsockdialer dials KubeVirt VM VSOCK ports via the Kubernetes API. // -// This replaces kubevirt.io/client-go's AsyncSubresourceHelper with a -// self-contained websocket dialer built on k8s.io/client-go/transport/websocket -// (the same building block client-go itself uses for `kubectl exec`/ -// `port-forward` over WebSocket) plus gorilla/websocket for the connection -// type. We cannot import kubevirt.io/client-go directly yet — kubevirt#16951 -// is no longer a blocker, but kubevirt#18382 and kubevirt#18408 still are. -// See https://github.com/stackrox/stackrox/pull/21587 for tracking; re-evaluate -// once both land in a tagged release. Do not switch to a personal fork for -// production use. +// It uses kubevirt client-go's AsyncSubresourceHelper so Sensor can keep a +// small Dial(namespace, name, port) surface for the pull-mode scraper. package vsockdialer import ( "context" - "errors" "fmt" "io" - "net/http" "net/url" "strconv" - "github.com/gorilla/websocket" + "github.com/stackrox/rox/pkg/k8sutil" "k8s.io/client-go/rest" - wstransport "k8s.io/client-go/transport/websocket" -) - -const ( - subresourceAPIGroup = "subresources.kubevirt.io" - apiVersion = "v1" - wsSubprotocol = "plain.kubevirt.io" + kvcorev1 "kubevirt.io/client-go/kubevirt/typed/core/v1" ) // MultiDialer dials VMs across namespaces via the KubeVirt subresource API. type MultiDialer struct { - config *rest.Config - wsReadLimit int64 + config *rest.Config +} + +// NewMultiDialer creates a dialer from in-cluster Kubernetes config. +func NewMultiDialer() (*MultiDialer, error) { + config, err := k8sutil.GetK8sInClusterConfig() + if err != nil { + return nil, fmt.Errorf("loading in-cluster config: %w", err) + } + return NewMultiDialerFromConfig(config), nil } -// NewMultiDialer creates a dialer from in-cluster (or kubeconfig) REST config. -// wsReadLimit sets the maximum WebSocket message size in bytes. -func NewMultiDialer(config *rest.Config, wsReadLimit int64) *MultiDialer { - return &MultiDialer{config: config, wsReadLimit: wsReadLimit} +// NewMultiDialerFromConfig builds a dialer from an existing REST config so +// tests and local runs can skip in-cluster service-account lookup. +func NewMultiDialerFromConfig(config *rest.Config) *MultiDialer { + return &MultiDialer{config: config} } // Dial connects to the named VMI's VSOCK port in the given namespace. -// The context controls dial timeout and, if it carries a deadline, that -// deadline is propagated to the connection's read/write deadlines so the -// per-VM timeout budget covers dial + request + response. -// -// Deadline propagation alone does not abort an in-flight read when the -// parent context is cancelled without a nearer deadline (e.g. Sensor -// shutdown). GetReport closes the stream on ctx cancel for that case. +// ctx's deadline is applied to the established connection only: the +// WebSocket handshake does not take ctx, so it cannot be cancelled. func (d *MultiDialer) Dial(ctx context.Context, namespace, name string, port uint32, useTLS bool) (io.ReadWriteCloser, error) { params := url.Values{} params.Set("port", strconv.FormatUint(uint64(port), 10)) params.Set("tls", strconv.FormatBool(useTLS)) - - wsURL, err := buildWSURL(d.config, "virtualmachineinstances", namespace, name, "vsock", params) - if err != nil { - return nil, fmt.Errorf("VSOCK dial %s/%s:%d: %w", namespace, name, port, err) - } - - conn, err := dialSubresource(ctx, d.config, wsURL) + stream, err := kvcorev1.AsyncSubresourceHelper( + d.config, "virtualmachineinstances", namespace, name, "vsock", params, + ) if err != nil { return nil, fmt.Errorf("VSOCK dial %s/%s:%d: %w", namespace, name, port, err) } - conn.SetReadLimit(d.wsReadLimit) + conn := stream.AsConn() if deadline, ok := ctx.Deadline(); ok { - _ = conn.SetReadDeadline(deadline) - _ = conn.SetWriteDeadline(deadline) - } - return &wsStream{conn: conn}, nil -} - -// dialSubresource negotiates a WebSocket connection to wsURL using -// k8s.io/client-go/transport/websocket. That package builds the -// RoundTripper straight from the REST config — TLS, proxying, and auth -// (bearer token, client certs, impersonation, exec/OIDC credential -// plugins) — the same way a real apiserver request would, so we don't -// have to reimplement auth-header extraction here. -// -// Trade-off: the negotiated connection's I/O buffers are fixed at ~33KiB by -// client-go's RoundTripper, smaller than a hand-rolled dialer could use. -// This only affects how many read/write syscalls a large message takes — -// messages are chunked across frames regardless of buffer size — so it -// doesn't affect correctness. -func dialSubresource(ctx context.Context, config *rest.Config, wsURL string) (*websocket.Conn, error) { - rt, holder, err := wstransport.RoundTripperFor(config) - if err != nil { - return nil, fmt.Errorf("building websocket round tripper: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, wsURL, nil) - if err != nil { - return nil, fmt.Errorf("building request: %w", err) - } - - conn, err := wstransport.Negotiate(rt, holder, req, wsSubprotocol) - if err != nil { - return nil, fmt.Errorf("websocket dial: %w", err) + _ = conn.SetDeadline(deadline) } return conn, nil } - -func buildWSURL(config *rest.Config, resource, namespace, name, subresource string, queryParams url.Values) (string, error) { - u, err := url.Parse(config.Host) - if err != nil { - return "", fmt.Errorf("parsing host: %w", err) - } - - // Keep http/https: client-go's websocket RoundTripper rewrites to ws/wss - // itself and rejects a pre-rewritten wss/ws scheme with "unknown url scheme". - switch u.Scheme { - case "https", "http": - default: - return "", fmt.Errorf("unsupported scheme %q", u.Scheme) - } - - u = u.JoinPath("apis", subresourceAPIGroup, apiVersion, "namespaces", namespace, resource, name, subresource) - if len(queryParams) > 0 { - u.RawQuery = queryParams.Encode() - } - return u.String(), nil -} - -// wsStream adapts a websocket.Conn into an io.ReadWriteCloser that reads -// across websocket binary message boundaries and writes binary messages. -type wsStream struct { - conn *websocket.Conn - reader io.Reader -} - -func (s *wsStream) Read(p []byte) (int, error) { - for { - if s.reader == nil { - msgType, rd, err := s.conn.NextReader() - if err != nil { - if isWSClose(err) { - return 0, io.EOF - } - return 0, err //nolint:wrapcheck // implements io.Reader - } - if msgType == websocket.CloseMessage { - return 0, io.EOF - } - s.reader = rd - } - - n, err := s.reader.Read(p) - if err == io.EOF { - s.reader = nil - if n > 0 { - return n, nil - } - continue - } - return n, err //nolint:wrapcheck // implements io.Reader - } -} - -func (s *wsStream) Write(p []byte) (int, error) { - if err := s.conn.WriteMessage(websocket.BinaryMessage, p); err != nil { - return 0, fmt.Errorf("writing websocket message: %w", err) - } - return len(p), nil -} - -func (s *wsStream) Close() error { - if err := s.conn.Close(); err != nil { - return fmt.Errorf("closing websocket: %w", err) - } - return nil -} - -func isWSClose(err error) bool { - if _, ok := errors.AsType[*websocket.CloseError](err); ok { - return true - } - return errors.Is(err, io.EOF) -} diff --git a/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go b/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go deleted file mode 100644 index ef2f5f60f9765..0000000000000 --- a/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package vsockdialer - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/client-go/rest" -) - -func TestBuildWSURL(t *testing.T) { - params := url.Values{} - params.Set("port", "1024") - params.Set("tls", "true") - - cases := map[string]struct { - host string - wantURL string - wantErr string // substring to match against the error; empty means no error expected - }{ - "should keep https for client-go websocket RoundTripper": { - host: "https://api.example.com:6443", - wantURL: "https://api.example.com:6443/apis/subresources.kubevirt.io/v1/namespaces/ns1/virtualmachineinstances/vm-a/vsock?port=1024&tls=true", - }, - "should keep http for client-go websocket RoundTripper": { - host: "http://localhost:8080", - wantURL: "http://localhost:8080/apis/subresources.kubevirt.io/v1/namespaces/ns1/virtualmachineinstances/vm-a/vsock?port=1024&tls=true", - }, - "should reject unsupported scheme": { - host: "ftp://api.example.com", - wantErr: "unsupported scheme", - }, - "should reject unparseable host": { - host: "https://api.example.com:abc", - wantErr: "parsing host", - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - got, err := buildWSURL(&rest.Config{Host: tc.host}, "virtualmachineinstances", "ns1", "vm-a", "vsock", params) - if tc.wantErr != "" { - require.ErrorContains(t, err, tc.wantErr) - return - } - require.NoError(t, err) - assert.Equal(t, tc.wantURL, got) - }) - } -} From 1727aafb864d85a604c4f16d68fde2275a5c5848 Mon Sep 17 00:00:00 2001 From: David Vail Date: Wed, 26 Aug 2026 11:05:54 -0400 Subject: [PATCH 088/638] chore(ui): Clean up Tailwind classes under Clusters/ (#22410) --- ui/apps/platform/eslint.config.js | 1 - .../Containers/Clusters/ClusterDeployment.tsx | 53 ++++++++++--------- .../Clusters/ClustersTablePanel.tsx | 2 +- .../AdmissionControlStatus.tsx | 2 +- .../Clusters/Components/ClusterStatus.tsx | 2 +- .../Components/Collector/CollectorStatus.tsx | 2 +- .../Collector/CollectorStatusTotals.tsx | 4 +- .../Collector/CollectorUnavailableStatus.tsx | 2 +- .../Components/CredentialExpiration.tsx | 8 +-- .../Components/HealthLabelWithDelayed.tsx | 5 +- .../Clusters/Components/HealthStatus.tsx | 10 +++- .../Components/HealthStatusNotApplicable.tsx | 2 +- .../Clusters/Components/HelmIndicator.tsx | 4 +- .../Clusters/Components/OperatorIndicator.tsx | 4 +- .../Components/Scanner/ScannerStatus.tsx | 2 +- .../Clusters/Components/SensorStatus.tsx | 2 +- .../Containers/Clusters/cluster.helpers.tsx | 10 +--- 17 files changed, 57 insertions(+), 58 deletions(-) diff --git a/ui/apps/platform/eslint.config.js b/ui/apps/platform/eslint.config.js index a93beaaf797a9..a6f13c2547d36 100644 --- a/ui/apps/platform/eslint.config.js +++ b/ui/apps/platform/eslint.config.js @@ -913,7 +913,6 @@ module.exports = [ 'src/Components/visuals/**', // deprecated 'src/Components/workflow/**', // deprecated - 'src/Containers/Clusters/**', // fix errors, and then delete; also in tailwind.config.js file 'src/Containers/Compliance/**', // deprecated 'src/Containers/ConfigManagement/**', 'src/Containers/Images/**', // deprecated diff --git a/ui/apps/platform/src/Containers/Clusters/ClusterDeployment.tsx b/ui/apps/platform/src/Containers/Clusters/ClusterDeployment.tsx index 09ced24eb9003..7b1901b3c4ad2 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClusterDeployment.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClusterDeployment.tsx @@ -1,8 +1,15 @@ import type { ReactElement } from 'react'; -import { Alert, Button, Content, Flex, FlexItem, Switch, Title } from '@patternfly/react-core'; +import { + Alert, + Button, + Content, + Flex, + FlexItem, + Spinner, + Switch, + Title, +} from '@patternfly/react-core'; import { DownloadIcon } from '@patternfly/react-icons'; -import { CheckCircle } from 'react-feather'; -import { ClipLoader } from 'react-spinners'; import type { ClusterManagerType } from 'types/cluster.proto'; import useAnalytics, { LEGACY_CLUSTER_DOWNLOAD_YAML } from 'hooks/useAnalytics'; @@ -110,29 +117,23 @@ function ClusterDeployment({ )} - {(!editing || !clusterCheckedIn) && ( -
- {clusterCheckedIn ? ( -
-
- -
-
- Success! The cluster has been recognized. -
-
- ) : ( -
-
- -
-
- Waiting for the cluster to check in successfully... -
-
- )} -
- )} + {(!editing || !clusterCheckedIn) && + (clusterCheckedIn ? ( + + ) : ( + } + /> + ))} ); } diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx index fff2044ba3faa..c8d61e46f520d 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersTablePanel.tsx @@ -355,7 +355,7 @@ function ClustersTablePanel({ selectedClusterId }: ClustersTablePanelProps) { /> ; + const icon = ; const healthLabelElement = ( ; + const icon = ; return ( diff --git a/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorStatus.tsx b/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorStatus.tsx index 168de2999f802..e849efff40397 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorStatus.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorStatus.tsx @@ -26,7 +26,7 @@ function CollectorStatus({ healthStatus }: CollectorStatusProps): ReactElement { const { Icon, fgColor } = isDelayed ? delayedCollectorStatusStyle : healthStatusStyles[collectorHealthStatus]; - const icon = ; + const icon = ; const statusElement = ( Collector version: - + {version || notAvailable} @@ -63,7 +63,7 @@ function CollectorStatusTotals({ collectorHealthInfo }: CollectorStatusTotalsPro {statusErrors.map((err) => (
  • {err} diff --git a/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorUnavailableStatus.tsx b/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorUnavailableStatus.tsx index 6b9d815bbcb79..7562a3b939037 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorUnavailableStatus.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/Collector/CollectorUnavailableStatus.tsx @@ -24,7 +24,7 @@ function CollectorUnavailableStatus({ return isList ? ( -
    +
    {statusElement} diff --git a/ui/apps/platform/src/Containers/Clusters/Components/CredentialExpiration.tsx b/ui/apps/platform/src/Containers/Clusters/Components/CredentialExpiration.tsx index e43105b688cbe..c2e2821073e4e 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/CredentialExpiration.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/CredentialExpiration.tsx @@ -36,13 +36,13 @@ function CredentialExpiration({ // Adapt health status categories to certificate expiration. const healthStatus = getCredentialExpirationStatus(certExpiryStatus, currentDatetime); const { Icon, fgColor } = healthStatusStylesLegacy[healthStatus]; - const icon = ; + const icon = ; // Order arguments according to date-fns@2 convention: // If sensorCertExpiry > currentDateTime: in X units // If sensorCertExpiry <= currentDateTime: X units ago const distanceElement = ( - + {getDistanceStrictAsPhrase(sensorCertExpiry, currentDatetime)} ); @@ -68,7 +68,7 @@ function CredentialExpiration({ expirationElement = (
    {distanceElement}{' '} - {`on ${ + {`on ${ diffInDays > 0 && diffInDays < 7 ? getDayOfWeek(sensorCertExpiry) : getDate(sensorCertExpiry) @@ -88,7 +88,7 @@ function CredentialExpiration({
    {expirationElement} {version && ( -
    +
    {healthLabelText}; + const healthLabelElement = {capitalize(healthLabelText)}; if (isDelayed) { return (
    {healthLabelElement} - {` ${delayedText}`} + {` ${delayedText}`}
    ); } diff --git a/ui/apps/platform/src/Containers/Clusters/Components/HealthStatus.tsx b/ui/apps/platform/src/Containers/Clusters/Components/HealthStatus.tsx index f6170bde73d5e..9696c16472412 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/HealthStatus.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/HealthStatus.tsx @@ -25,8 +25,14 @@ function HealthStatus({ }: HealthStatusProps): ReactElement { return ( // flex-row assumes a child element wraps multiple grandchildren -
    - {icon} +
    + {icon} {children}
    ); diff --git a/ui/apps/platform/src/Containers/Clusters/Components/HealthStatusNotApplicable.tsx b/ui/apps/platform/src/Containers/Clusters/Components/HealthStatusNotApplicable.tsx index 56ef23748f367..8459a896e6736 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/HealthStatusNotApplicable.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/HealthStatusNotApplicable.tsx @@ -7,7 +7,7 @@ function HealthStatusNotApplicable({ isList = false, }: HealthStatusNotApplicableProps): ReactElement { return ( -
    +
    Not applicable
    ); diff --git a/ui/apps/platform/src/Containers/Clusters/Components/HelmIndicator.tsx b/ui/apps/platform/src/Containers/Clusters/Components/HelmIndicator.tsx index a893e8ad2d588..1b7c017aa060c 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/HelmIndicator.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/HelmIndicator.tsx @@ -6,8 +6,8 @@ import HelmLogo from 'images/helm.svg?react'; function HelmIndicator(): ReactElement { return ( - - + + ); diff --git a/ui/apps/platform/src/Containers/Clusters/Components/OperatorIndicator.tsx b/ui/apps/platform/src/Containers/Clusters/Components/OperatorIndicator.tsx index e8bff9412465f..4c058253db91a 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/OperatorIndicator.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/OperatorIndicator.tsx @@ -6,9 +6,9 @@ import operatorLogo from 'images/operator-logo.png'; function OperatorIndicator(): ReactElement { return ( - + Managed by a Kubernetes Operator diff --git a/ui/apps/platform/src/Containers/Clusters/Components/Scanner/ScannerStatus.tsx b/ui/apps/platform/src/Containers/Clusters/Components/Scanner/ScannerStatus.tsx index 61042f763b8d9..f51397e2fbafb 100644 --- a/ui/apps/platform/src/Containers/Clusters/Components/Scanner/ScannerStatus.tsx +++ b/ui/apps/platform/src/Containers/Clusters/Components/Scanner/ScannerStatus.tsx @@ -22,7 +22,7 @@ const ScannerStatus = ({ healthStatus }: ScannerStatusProps) => { const { Icon, fgColor } = isDelayed ? delayedScannerStatusStyle : healthStatusStyles[scannerHealthStatus]; - const icon = ; + const icon = ; const healthLabelElement = ( ; + const icon = ; const statusElement = ( ( - -); - const styleUninitializedLegacy = { - Icon: MinusCircleRotate45, + Icon: MinusCircleIcon, fgColor: '', }; From f463d9cc4ea598664fbbccbb59a68f57f074f3b0 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:37:54 +0200 Subject: [PATCH 089/638] ROX-35756: replace GetReport generation+epoch with a per-scan token (#22454) --- compliance/virtualmachines/roxagent/README.md | 6 +- .../roxagent/vsockserver/protocol.go | 97 +++---- .../roxagent/vsockserver/protocol_test.go | 145 ++++------ .../virtualmachine/v1/vm_service.pb.go | 90 ++----- .../v1/vm_service_vtproto.pb.go | 226 +++++++--------- .../virtualmachine/v1/vm_service.proto | 41 +-- sensor/common/virtualmachine/ACK_MODEL.md | 6 +- .../virtualmachine/vmscraper/scraper.go | 54 ++-- .../virtualmachine/vmscraper/scraper_test.go | 252 +++++++----------- .../vmscraper/spreading_test.go | 20 +- .../virtualmachine/vsockclient/client.go | 13 +- .../vsockclient/client_integration_test.go | 136 ++++------ .../virtualmachine/vsockclient/client_test.go | 25 +- 13 files changed, 444 insertions(+), 667 deletions(-) diff --git a/compliance/virtualmachines/roxagent/README.md b/compliance/virtualmachines/roxagent/README.md index f76efe476d689..4574c8b91ad9d 100644 --- a/compliance/virtualmachines/roxagent/README.md +++ b/compliance/virtualmachines/roxagent/README.md @@ -9,7 +9,7 @@ The sources live under `compliance/` because the agent reuses the Scanner V4 nod ## What it does 1. Fetches the repository-to-CPE mapping file (required before the first scan). -2. Scans the VM for installed packages and caches the index report with a generation counter. +2. Scans the VM for installed packages and caches the index report with a content-hash token. 3. Listens on a VSOCK port with mandatory mTLS. 4. On each Sensor connection, handles a framed `VMServiceRequest` / `VMServiceResponse` (for example `get_report`). 5. Periodically rescans and atomically swaps the cached report. @@ -40,8 +40,8 @@ sudo ./roxagent serve --port 818 --host-path / --rescan-interval 4h The default `--repo-cpe-url` is `https://security.access.redhat.com/data/metrics/repository-to-cpe.json`, so the VM needs outbound HTTPS to that host (or a proxy that can reach it). On isolated networks, host a copy of `repository-to-cpe.json` somewhere the VM can reach and point `--repo-cpe-url` at that URL. 2. **Initial scan:** Indexes `--host-path`, stores the report and discovered VM facts in an in-memory cache, then starts the VSOCK server and the rescan loop. -3. **Serving reports:** Sensor connects and the agent serves the cached report. If Sensor already has the current generation, the agent omits the payload. -4. **Rescan:** On `--rescan-interval`, re-indexes the rpm/dnf database and swaps the cache, bumping the generation. +3. **Serving reports:** Sensor connects and the agent serves the cached report. If Sensor already has the current content-hash token, the agent omits the payload. Sensor can still force a full report by sending an empty token (first request, NACK retry, or the 4h refresh). +4. **Rescan:** On `--rescan-interval`, re-indexes the rpm/dnf database and swaps the cache. The token stays the same when the report and facts are unchanged. ### TLS (mandatory) diff --git a/compliance/virtualmachines/roxagent/vsockserver/protocol.go b/compliance/virtualmachines/roxagent/vsockserver/protocol.go index d6342f11b521b..f37bd92841eb9 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/protocol.go +++ b/compliance/virtualmachines/roxagent/vsockserver/protocol.go @@ -6,9 +6,11 @@ import ( "fmt" "maps" "net" + "slices" "sync/atomic" "time" + "github.com/cespare/xxhash/v2" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" "github.com/stackrox/rox/pkg/vsockframing" @@ -21,12 +23,12 @@ const maxRequestSize = 1 << 20 // 1 MiB // reportSnapshot is an immutable point-in-time view of the cached report state. type reportSnapshot struct { report *v4.IndexReport - generation uint32 + token string generatedAt time.Time facts map[string]string } -// ReportCache holds the cached scan report with its generation counter. +// ReportCache holds the cached scan report with its content-hash token. // Invariant: exactly one goroutine (the rescan loop) calls SetReport; multiple // HandleConn goroutines read via snap.Load(). This single-writer/multi-reader // pattern is safe with atomic.Pointer without CAS. @@ -34,27 +36,43 @@ type ReportCache struct { snap atomic.Pointer[reportSnapshot] } -// SetReport atomically publishes a new report with updated facts in a single -// store, incrementing the generation counter. Readers never observe a partial -// (new report, stale facts) state. +// SetReport atomically publishes a new report and facts. The token is an +// XXH64 of that content, so identical rescans stay unchanged for Sensor. // // r and facts are defensively copied so that a caller mutating its own copy // after this call (or reusing the same facts map across scans) can never // mutate the published, supposedly-immutable snapshot out from under // concurrent readers. func (c *ReportCache) SetReport(r *v4.IndexReport, facts map[string]string) { - var counter uint32 - if old := c.snap.Load(); old != nil { - counter = old.generation - } + cloned := cloneIndexReport(r) + clonedFacts := cloneFacts(facts) c.snap.Store(&reportSnapshot{ - report: cloneIndexReport(r), - generation: counter + 1, + report: cloned, + token: reportToken(cloned, clonedFacts), generatedAt: time.Now(), - facts: cloneFacts(facts), + facts: clonedFacts, }) } +// reportToken is XXH64 of the IndexReport and facts, as 16 lowercase hex +// digits. Deterministic marshal keeps identical content stable across rescans. +func reportToken(r *v4.IndexReport, facts map[string]string) string { + h := xxhash.New() + if r != nil { + b, err := proto.MarshalOptions{Deterministic: true}.Marshal(r) + if err == nil { + _, _ = h.Write(b) + } + } + for _, k := range slices.Sorted(maps.Keys(facts)) { + _, _ = h.Write([]byte(k)) + _, _ = h.Write([]byte{0}) + _, _ = h.Write([]byte(facts[k])) + _, _ = h.Write([]byte{0}) + } + return fmt.Sprintf("%016x", h.Sum64()) +} + // cloneIndexReport returns a deep copy of r, or nil if r is nil. func cloneIndexReport(r *v4.IndexReport) *v4.IndexReport { if r == nil { @@ -78,32 +96,11 @@ func cloneFacts(in map[string]string) map[string]string { type Handler struct { cache *ReportCache agentVersion string - // epoch is seeded once per process lifetime and never persisted to VM - // disk. It lets Sensor (and this handler itself, via GetReportRequest's - // known_epoch) distinguish "this agent restarted" from "this agent's - // generation counter coincidentally reset to a value Sensor already has - // cached" without changing report_generation's own sequential, - // human-readable semantics. - epoch uint32 } // NewHandler creates a protocol handler. func NewHandler(cache *ReportCache, agentVersion string) *Handler { - return &Handler{cache: cache, agentVersion: agentVersion, epoch: newEpoch()} -} - -// newEpoch derives a process-lifetime epoch value. Time-derived rather than -// cryptographically random: epoch only needs to differ across restarts with -// overwhelming probability, not resist an adversary. Seconds (not -// nanoseconds) since the Unix epoch, so the value only wraps every ~136 -// years instead of every ~4.3 seconds when truncated to uint32. 0 is -// reserved to mean "agent predates this field" (see ResponseMeta.epoch doc), -// so it's excluded. -func newEpoch() uint32 { - if e := uint32(time.Now().Unix()); e != 0 { - return e - } - return 1 + return &Handler{cache: cache, agentVersion: agentVersion} } // HandleConn reads a framed request from conn, processes it, writes a framed response, and closes conn. @@ -160,32 +157,19 @@ func (h *Handler) handleGetReport(req *pb.GetReportRequest) *pb.VMServiceRespons return h.errorResponseFromSnap(snap, pb.ErrorCode_ERROR_CODE_NOT_READY, "initial scan in progress, try again later") } - // Strict equality (not >=) so that after an agent restart — when the generation - // counter resets to 1 — a sensor still holding a higher generation from the - // previous instance will receive the full report instead of a false "unchanged". - // - // known_epoch guards against the opposite false positive: a restarted agent - // whose reset generation coincidentally re-matches last_known_generation. - // 0 means Sensor doesn't know our epoch yet (first-ever request for this VM, - // or a Sensor build that predates the field), so fall back to generation-only - // comparison exactly as before this field existed. - generationMatches := req.GetLastKnownGeneration() == snap.generation - knownEpoch := req.GetKnownEpoch() - epochMatches := knownEpoch == 0 || knownEpoch == h.epoch - if generationMatches && epochMatches { - log.Infof("GetReport: unchanged (generation=%d, last_known_generation=%d)", snap.generation, req.GetLastKnownGeneration()) + // Empty last_known_token forces a full report (first request or Sensor + // refresh). A matching token is identical content; any other value + // means the published report or facts changed. + if lastKnown := req.GetLastKnownToken(); lastKnown != "" && lastKnown == snap.token { + log.Infof("GetReport: unchanged (token=%s)", snap.token) resp := h.newResponseFromSnap(snap) resp.Result = &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{Unchanged: true}, } return resp } - if generationMatches && !epochMatches { - log.Infof("GetReport: generation matches (%d) but epoch changed (known=%d, current=%d) — agent restarted, serving full report in this round trip", - snap.generation, knownEpoch, h.epoch) - } - log.Infof("GetReport: serving report (generation=%d, packages=%d)", snap.generation, len(snap.report.GetContents().GetPackages())) + log.Infof("GetReport: serving report (token=%s, packages=%d)", snap.token, len(snap.report.GetContents().GetPackages())) resp := h.newResponseFromSnap(snap) resp.Result = &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{IndexReport: snap.report}, @@ -195,17 +179,16 @@ func (h *Handler) handleGetReport(req *pb.GetReportRequest) *pb.VMServiceRespons func (h *Handler) newResponseFromSnap(snap *reportSnapshot) *pb.VMServiceResponse { var facts map[string]string - var gen uint32 + var token string if snap != nil { facts = cloneFacts(snap.facts) - gen = snap.generation + token = snap.token } meta := &pb.ResponseMeta{ AgentVersion: h.agentVersion, - ReportGeneration: gen, + ReportToken: token, SupportedMethods: []string{"get_report"}, Facts: facts, - Epoch: h.epoch, } if snap != nil && !snap.generatedAt.IsZero() { meta.ReportGeneratedAt = timestamppb.New(snap.generatedAt) diff --git a/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go b/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go index 8b92f646015ee..e8372f4e85710 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go +++ b/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go @@ -30,15 +30,20 @@ func sendAndReceive(t *testing.T, handler *Handler, req *pb.VMServiceRequest) *p return &resp } +func getReportRequest(requestID, lastKnownToken string) *pb.VMServiceRequest { + return &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: requestID}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{LastKnownToken: lastKnownToken}}, + } +} + func TestHandleRequest_GetReport(t *testing.T) { cache := &ReportCache{} cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) handler := NewHandler(cache, "test-1.0.0") - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-1", Capabilities: []string{"report_v1"}}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{LastKnownGeneration: 0}}, - } + req := getReportRequest("req-1", "") + req.Meta.Capabilities = []string{"report_v1"} resp := sendAndReceive(t, handler, req) @@ -49,10 +54,9 @@ func TestHandleRequest_GetReport(t *testing.T) { meta := resp.GetMeta() require.NotNil(t, meta) assert.Equal(t, "test-1.0.0", meta.GetAgentVersion()) - assert.Equal(t, uint32(1), meta.GetReportGeneration()) + assert.NotEmpty(t, meta.GetReportToken()) assert.NotNil(t, meta.GetReportGeneratedAt()) assert.Contains(t, meta.GetSupportedMethods(), "get_report") - assert.NotZero(t, meta.GetEpoch(), "epoch should be seeded on handler creation") } func TestHandleRequest_GetReport_Unchanged(t *testing.T) { @@ -60,121 +64,84 @@ func TestHandleRequest_GetReport_Unchanged(t *testing.T) { cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) handler := NewHandler(cache, "test-1.0.0") - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-2"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{LastKnownGeneration: 1}}, - } + first := sendAndReceive(t, handler, getReportRequest("req-learn-token", "")) + token := first.GetMeta().GetReportToken() + require.NotEmpty(t, token) - resp := sendAndReceive(t, handler, req) + resp := sendAndReceive(t, handler, getReportRequest("req-2", token)) assert.NotNil(t, resp.GetGetReport()) assert.True(t, resp.GetGetReport().GetUnchanged()) assert.Nil(t, resp.GetGetReport().GetIndexReport()) + assert.Equal(t, token, resp.GetMeta().GetReportToken()) } -func TestHandleRequest_GetReport_UnchangedWhenKnownEpochMatches(t *testing.T) { - cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) - - handler := NewHandler(cache, "test-1.0.0") - // Learn the handler's epoch from a first exchange (known_epoch=0, so - // Sensor has no cached epoch yet — falls back to generation-only). - firstResp := sendAndReceive(t, handler, &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-learn-epoch"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{LastKnownGeneration: 0}}, - }) - epoch := firstResp.GetMeta().GetEpoch() - require.NotZero(t, epoch) - - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-epoch-match"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{ - LastKnownGeneration: 1, - KnownEpoch: epoch, - }}, - } - - resp := sendAndReceive(t, handler, req) - - assert.NotNil(t, resp.GetGetReport()) - assert.True(t, resp.GetGetReport().GetUnchanged(), "matching generation and epoch should report unchanged") - assert.Nil(t, resp.GetGetReport().GetIndexReport()) -} - -// TestHandleRequest_GetReport_ServesFullReportOnKnownEpochMismatch covers -// the case report_generation alone cannot distinguish: report_generation -// resets to 1 on every roxagent restart, so a restarted agent can -// coincidentally match a generation Sensor already has cached for a -// previous instance. known_epoch lets the agent detect this itself, in a -// single round trip, by comparing Sensor's last-seen epoch against its own -// current one. -func TestHandleRequest_GetReport_ServesFullReportOnKnownEpochMismatch(t *testing.T) { +// TestHandleRequest_GetReport_TokenMismatch covers a Sensor-cached token +// that does not match the agent's current content. +func TestHandleRequest_GetReport_TokenMismatch(t *testing.T) { cache := &ReportCache{} cache.SetReport(&v4.IndexReport{HashId: "post-restart-hash"}, nil) handler := NewHandler(cache, "test-1.0.0") - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-epoch-mismatch"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{ - // Generation matches (both are 1), but Sensor's cached epoch is - // from a previous agent process instance. - LastKnownGeneration: 1, - KnownEpoch: 12345, - }}, - } - - resp := sendAndReceive(t, handler, req) + resp := sendAndReceive(t, handler, getReportRequest("req-mismatch", "stale-token")) assert.NotNil(t, resp.GetGetReport()) - assert.False(t, resp.GetGetReport().GetUnchanged(), "epoch mismatch must serve the full report despite matching generation") + assert.False(t, resp.GetGetReport().GetUnchanged()) require.NotNil(t, resp.GetGetReport().GetIndexReport()) assert.Equal(t, "post-restart-hash", resp.GetGetReport().GetIndexReport().GetHashId()) - assert.NotEqual(t, uint32(12345), resp.GetMeta().GetEpoch(), "response should carry the agent's real current epoch") + assert.NotEmpty(t, resp.GetMeta().GetReportToken()) + assert.NotEqual(t, "stale-token", resp.GetMeta().GetReportToken()) } -// TestHandleRequest_GetReport_UnchangedWhenKnownEpochZero pins down backward -// compatibility: known_epoch=0 means Sensor has no epoch to compare (first -// request for this VM, or a Sensor build that predates the field), so the -// agent must fall back to generation-only comparison exactly as before this -// field existed. -func TestHandleRequest_GetReport_UnchangedWhenKnownEpochZero(t *testing.T) { +// TestHandleRequest_GetReport_IdenticalRescanUnchanged covers a rescan +// whose report and facts did not change: the token is stable, so Sensor +// gets unchanged unless it sends an empty last_known_token. +func TestHandleRequest_GetReport_IdenticalRescanUnchanged(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil) handler := NewHandler(cache, "test-1.0.0") - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-epoch-zero"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{ - LastKnownGeneration: 1, - KnownEpoch: 0, - }}, - } + first := sendAndReceive(t, handler, getReportRequest("req-first-scan", "")) + token := first.GetMeta().GetReportToken() + require.NotEmpty(t, token) - resp := sendAndReceive(t, handler, req) + cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil) + resp := sendAndReceive(t, handler, getReportRequest("req-after-rescan", token)) assert.NotNil(t, resp.GetGetReport()) - assert.True(t, resp.GetGetReport().GetUnchanged(), "known_epoch=0 should fall back to generation-only comparison") + assert.True(t, resp.GetGetReport().GetUnchanged()) assert.Nil(t, resp.GetGetReport().GetIndexReport()) + assert.Equal(t, token, resp.GetMeta().GetReportToken()) } -func TestHandleRequest_GetReport_GenerationRegression(t *testing.T) { +// TestHandleRequest_GetReport_ContentChangeServesReport covers a rescan +// whose content changed: the token changes and the full report is served. +func TestHandleRequest_GetReport_ContentChangeServesReport(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "post-restart-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "before"}, nil) handler := NewHandler(cache, "test-1.0.0") - req := &pb.VMServiceRequest{ - Meta: &pb.RequestMeta{RequestId: "req-regression"}, - Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{ - LastKnownGeneration: 5, - }}, - } + first := sendAndReceive(t, handler, getReportRequest("req-before", "")) + token := first.GetMeta().GetReportToken() + require.NotEmpty(t, token) - resp := sendAndReceive(t, handler, req) + cache.SetReport(&v4.IndexReport{HashId: "after"}, nil) + resp := sendAndReceive(t, handler, getReportRequest("req-after", token)) assert.NotNil(t, resp.GetGetReport()) - assert.False(t, resp.GetGetReport().GetUnchanged(), "agent restarted (gen=1 < requested=5), must serve full report") - assert.Equal(t, "post-restart-hash", resp.GetGetReport().GetIndexReport().GetHashId()) - assert.Equal(t, uint32(1), resp.GetMeta().GetReportGeneration()) + assert.False(t, resp.GetGetReport().GetUnchanged()) + assert.Equal(t, "after", resp.GetGetReport().GetIndexReport().GetHashId()) + assert.NotEmpty(t, resp.GetMeta().GetReportToken()) + assert.NotEqual(t, token, resp.GetMeta().GetReportToken()) +} + +func TestReportToken(t *testing.T) { + report := &v4.IndexReport{HashId: "a"} + assert.Equal(t, reportToken(report, nil), reportToken(&v4.IndexReport{HashId: "a"}, map[string]string{})) + assert.NotEqual(t, reportToken(report, nil), reportToken(&v4.IndexReport{HashId: "b"}, nil)) + assert.NotEqual(t, reportToken(report, nil), reportToken(report, map[string]string{"os": "rhel"})) + assert.Equal(t, reportToken(nil, nil), reportToken(nil, map[string]string{})) + assert.Len(t, reportToken(report, nil), 16) } func TestHandleRequest_NotReady(t *testing.T) { diff --git a/generated/internalapi/virtualmachine/v1/vm_service.pb.go b/generated/internalapi/virtualmachine/v1/vm_service.pb.go index 074fa178915c0..0944d64b27b20 100644 --- a/generated/internalapi/virtualmachine/v1/vm_service.pb.go +++ b/generated/internalapi/virtualmachine/v1/vm_service.pb.go @@ -419,28 +419,20 @@ type ResponseMeta struct { AgentVersion string `protobuf:"bytes,1,opt,name=agent_version,json=agentVersion,proto3" json:"agent_version,omitempty"` // When the cached scan report was produced by the scanner. ReportGeneratedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=report_generated_at,json=reportGeneratedAt,proto3" json:"report_generated_at,omitempty"` - // Monotonic counter incremented on each rescan; resets to 1 on agent restart. - // Sensor uses this for deduplication (see ADR-0006 §2, "Generation counter"). - ReportGeneration uint32 `protobuf:"varint,3,opt,name=report_generation,json=reportGeneration,proto3" json:"report_generation,omitempty"` + // XXH64 of the cached IndexReport and facts. Identical content keeps the + // same token; Sensor still forces a full report with an empty last_known_token. + ReportToken string `protobuf:"bytes,3,opt,name=report_token,json=reportToken,proto3" json:"report_token,omitempty"` // Methods this agent accepts (e.g. ["get_report"]). Doubles as capability // discovery — no separate handshake needed. See ADR-0006 §2. SupportedMethods []string `protobuf:"bytes,4,rep,name=supported_methods,json=supportedMethods,proto3" json:"supported_methods,omitempty"` // VM metadata from the scan (detected_os, os_version, activation_status, etc.). // Future: may also carry roxagent operational metrics. Facts map[string]string `protobuf:"bytes,5,rep,name=facts,proto3" json:"facts,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // Opaque value seeded once per agent process lifetime (random/time-derived), - // held in memory only, never persisted to VM disk. Changes on every agent - // restart, unlike report_generation which resets to 1. Sensor should treat - // an epoch mismatch as "changed" regardless of report_generation, to avoid - // false-negative dedup when a restarted agent's reset generation coincides - // with Sensor's cached value for that VM. Zero means the agent predates this - // field; Sensor falls back to generation-only comparison in that case. - Epoch uint32 `protobuf:"varint,6,opt,name=epoch,proto3" json:"epoch,omitempty"` // XXH64 hex of active mapping; empty string means none. optional so absence // means an older agent that does not report mapping metadata. - RepoCpeMappingHash *string `protobuf:"bytes,7,opt,name=repo_cpe_mapping_hash,json=repoCpeMappingHash,proto3,oneof" json:"repo_cpe_mapping_hash,omitempty"` + RepoCpeMappingHash *string `protobuf:"bytes,6,opt,name=repo_cpe_mapping_hash,json=repoCpeMappingHash,proto3,oneof" json:"repo_cpe_mapping_hash,omitempty"` // SENSOR or URL only; UNSPECIFIED means Sensor must not sync. - RepoCpeMappingUpdatePath *RepoCPEMappingUpdatePath `protobuf:"varint,8,opt,name=repo_cpe_mapping_update_path,json=repoCpeMappingUpdatePath,proto3,enum=virtualmachine.v1.RepoCPEMappingUpdatePath,oneof" json:"repo_cpe_mapping_update_path,omitempty"` + RepoCpeMappingUpdatePath *RepoCPEMappingUpdatePath `protobuf:"varint,7,opt,name=repo_cpe_mapping_update_path,json=repoCpeMappingUpdatePath,proto3,enum=virtualmachine.v1.RepoCPEMappingUpdatePath,oneof" json:"repo_cpe_mapping_update_path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -489,11 +481,11 @@ func (x *ResponseMeta) GetReportGeneratedAt() *timestamppb.Timestamp { return nil } -func (x *ResponseMeta) GetReportGeneration() uint32 { +func (x *ResponseMeta) GetReportToken() string { if x != nil { - return x.ReportGeneration + return x.ReportToken } - return 0 + return "" } func (x *ResponseMeta) GetSupportedMethods() []string { @@ -510,13 +502,6 @@ func (x *ResponseMeta) GetFacts() map[string]string { return nil } -func (x *ResponseMeta) GetEpoch() uint32 { - if x != nil { - return x.Epoch - } - return 0 -} - func (x *ResponseMeta) GetRepoCpeMappingHash() string { if x != nil && x.RepoCpeMappingHash != nil { return *x.RepoCpeMappingHash @@ -533,20 +518,11 @@ func (x *ResponseMeta) GetRepoCpeMappingUpdatePath() RepoCPEMappingUpdatePath { type GetReportRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Generation last observed by Sensor for this VM (see ResponseMeta.report_generation). - // Use 0 when no prior generation is known, which forces a full report. See - // ADR-0006 §2, "Generation counter", for how the agent uses this value. - LastKnownGeneration uint32 `protobuf:"varint,1,opt,name=last_known_generation,json=lastKnownGeneration,proto3" json:"last_known_generation,omitempty"` - // Epoch last observed by Sensor for this VM (see ResponseMeta.epoch). 0 means - // unknown (first-ever request for this VM, or a Sensor build that predates - // this field) -- the agent should ignore epoch and fall back to - // generation-only comparison, exactly as if this field did not exist. This - // lets the agent detect a restart-coincidence false match (last_known_generation - // happens to equal the post-restart generation) and serve the full report in - // the same round trip, instead of Sensor needing a second, forced request. - KnownEpoch uint32 `protobuf:"varint,2,opt,name=known_epoch,json=knownEpoch,proto3" json:"known_epoch,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Token last observed by Sensor for this VM (see ResponseMeta.report_token). + // Empty when none is known, which forces a full report. + LastKnownToken string `protobuf:"bytes,1,opt,name=last_known_token,json=lastKnownToken,proto3" json:"last_known_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetReportRequest) Reset() { @@ -579,26 +555,19 @@ func (*GetReportRequest) Descriptor() ([]byte, []int) { return file_internalapi_virtualmachine_v1_vm_service_proto_rawDescGZIP(), []int{4} } -func (x *GetReportRequest) GetLastKnownGeneration() uint32 { - if x != nil { - return x.LastKnownGeneration - } - return 0 -} - -func (x *GetReportRequest) GetKnownEpoch() uint32 { +func (x *GetReportRequest) GetLastKnownToken() string { if x != nil { - return x.KnownEpoch + return x.LastKnownToken } - return 0 + return "" } type GetReportResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // Full scan report; set when generation changed or forced (generation 0). + // Full scan report; set when the content hash changed or last_known_token is empty. IndexReport *v4.IndexReport `protobuf:"bytes,1,opt,name=index_report,json=indexReport,proto3" json:"index_report,omitempty"` - // True when agent's generation matches last_known_generation; index_report - // is nil in this case. Sensor may still force a refresh after 4 h (ADR-0006 §2). + // True when the agent's token matches last_known_token; index_report is nil. + // Sensor may still force a refresh after 4 h with an empty last_known_token. Unchanged bool `protobuf:"varint,2,opt,name=unchanged,proto3" json:"unchanged,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -825,26 +794,23 @@ const file_internalapi_virtualmachine_v1_vm_service_proto_rawDesc = "" + "\n" + "FactsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd0\x04\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb0\x04\n" + "\fResponseMeta\x12#\n" + "\ragent_version\x18\x01 \x01(\tR\fagentVersion\x12J\n" + - "\x13report_generated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x11reportGeneratedAt\x12+\n" + - "\x11report_generation\x18\x03 \x01(\rR\x10reportGeneration\x12+\n" + + "\x13report_generated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x11reportGeneratedAt\x12!\n" + + "\freport_token\x18\x03 \x01(\tR\vreportToken\x12+\n" + "\x11supported_methods\x18\x04 \x03(\tR\x10supportedMethods\x12@\n" + - "\x05facts\x18\x05 \x03(\v2*.virtualmachine.v1.ResponseMeta.FactsEntryR\x05facts\x12\x14\n" + - "\x05epoch\x18\x06 \x01(\rR\x05epoch\x126\n" + - "\x15repo_cpe_mapping_hash\x18\a \x01(\tH\x00R\x12repoCpeMappingHash\x88\x01\x01\x12p\n" + - "\x1crepo_cpe_mapping_update_path\x18\b \x01(\x0e2+.virtualmachine.v1.RepoCPEMappingUpdatePathH\x01R\x18repoCpeMappingUpdatePath\x88\x01\x01\x1a8\n" + + "\x05facts\x18\x05 \x03(\v2*.virtualmachine.v1.ResponseMeta.FactsEntryR\x05facts\x126\n" + + "\x15repo_cpe_mapping_hash\x18\x06 \x01(\tH\x00R\x12repoCpeMappingHash\x88\x01\x01\x12p\n" + + "\x1crepo_cpe_mapping_update_path\x18\a \x01(\x0e2+.virtualmachine.v1.RepoCPEMappingUpdatePathH\x01R\x18repoCpeMappingUpdatePath\x88\x01\x01\x1a8\n" + "\n" + "FactsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x18\n" + "\x16_repo_cpe_mapping_hashB\x1f\n" + - "\x1d_repo_cpe_mapping_update_path\"g\n" + - "\x10GetReportRequest\x122\n" + - "\x15last_known_generation\x18\x01 \x01(\rR\x13lastKnownGeneration\x12\x1f\n" + - "\vknown_epoch\x18\x02 \x01(\rR\n" + - "knownEpoch\"m\n" + + "\x1d_repo_cpe_mapping_update_path\"<\n" + + "\x10GetReportRequest\x12(\n" + + "\x10last_known_token\x18\x01 \x01(\tR\x0elastKnownToken\"m\n" + "\x11GetReportResponse\x12:\n" + "\findex_report\x18\x01 \x01(\v2\x17.scanner.v4.IndexReportR\vindexReport\x12\x1c\n" + "\tunchanged\x18\x02 \x01(\bR\tunchanged\"5\n" + diff --git a/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go b/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go index 36afa3c8ec7aa..71813d28e9c49 100644 --- a/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go +++ b/generated/internalapi/virtualmachine/v1/vm_service_vtproto.pb.go @@ -148,8 +148,7 @@ func (m *ResponseMeta) CloneVT() *ResponseMeta { r := new(ResponseMeta) r.AgentVersion = m.AgentVersion r.ReportGeneratedAt = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.ReportGeneratedAt).CloneVT()) - r.ReportGeneration = m.ReportGeneration - r.Epoch = m.Epoch + r.ReportToken = m.ReportToken if rhs := m.SupportedMethods; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -186,8 +185,7 @@ func (m *GetReportRequest) CloneVT() *GetReportRequest { return (*GetReportRequest)(nil) } r := new(GetReportRequest) - r.LastKnownGeneration = m.LastKnownGeneration - r.KnownEpoch = m.KnownEpoch + r.LastKnownToken = m.LastKnownToken if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -525,7 +523,7 @@ func (this *ResponseMeta) EqualVT(that *ResponseMeta) bool { if !(*timestamppb1.Timestamp)(this.ReportGeneratedAt).EqualVT((*timestamppb1.Timestamp)(that.ReportGeneratedAt)) { return false } - if this.ReportGeneration != that.ReportGeneration { + if this.ReportToken != that.ReportToken { return false } if len(this.SupportedMethods) != len(that.SupportedMethods) { @@ -549,9 +547,6 @@ func (this *ResponseMeta) EqualVT(that *ResponseMeta) bool { return false } } - if this.Epoch != that.Epoch { - return false - } if p, q := this.RepoCpeMappingHash, that.RepoCpeMappingHash; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } @@ -574,10 +569,7 @@ func (this *GetReportRequest) EqualVT(that *GetReportRequest) bool { } else if this == nil || that == nil { return false } - if this.LastKnownGeneration != that.LastKnownGeneration { - return false - } - if this.KnownEpoch != that.KnownEpoch { + if this.LastKnownToken != that.LastKnownToken { return false } return string(this.unknownFields) == string(that.unknownFields) @@ -1008,19 +1000,14 @@ func (m *ResponseMeta) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m.RepoCpeMappingUpdatePath != nil { i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.RepoCpeMappingUpdatePath)) i-- - dAtA[i] = 0x40 + dAtA[i] = 0x38 } if m.RepoCpeMappingHash != nil { i -= len(*m.RepoCpeMappingHash) copy(dAtA[i:], *m.RepoCpeMappingHash) i = protohelpers.EncodeVarint(dAtA, i, uint64(len(*m.RepoCpeMappingHash))) i-- - dAtA[i] = 0x3a - } - if m.Epoch != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Epoch)) - i-- - dAtA[i] = 0x30 + dAtA[i] = 0x32 } if len(m.Facts) > 0 { for k := range m.Facts { @@ -1050,10 +1037,12 @@ func (m *ResponseMeta) MarshalToSizedBufferVT(dAtA []byte) (int, error) { dAtA[i] = 0x22 } } - if m.ReportGeneration != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ReportGeneration)) + if len(m.ReportToken) > 0 { + i -= len(m.ReportToken) + copy(dAtA[i:], m.ReportToken) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ReportToken))) i-- - dAtA[i] = 0x18 + dAtA[i] = 0x1a } if m.ReportGeneratedAt != nil { size, err := (*timestamppb1.Timestamp)(m.ReportGeneratedAt).MarshalToSizedBufferVT(dAtA[:i]) @@ -1105,15 +1094,12 @@ func (m *GetReportRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.KnownEpoch != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.KnownEpoch)) - i-- - dAtA[i] = 0x10 - } - if m.LastKnownGeneration != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.LastKnownGeneration)) + if len(m.LastKnownToken) > 0 { + i -= len(m.LastKnownToken) + copy(dAtA[i:], m.LastKnownToken) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LastKnownToken))) i-- - dAtA[i] = 0x8 + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -1476,8 +1462,9 @@ func (m *ResponseMeta) SizeVT() (n int) { l = (*timestamppb1.Timestamp)(m.ReportGeneratedAt).SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.ReportGeneration != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.ReportGeneration)) + l = len(m.ReportToken) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } if len(m.SupportedMethods) > 0 { for _, s := range m.SupportedMethods { @@ -1493,9 +1480,6 @@ func (m *ResponseMeta) SizeVT() (n int) { n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) } } - if m.Epoch != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Epoch)) - } if m.RepoCpeMappingHash != nil { l = len(*m.RepoCpeMappingHash) n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) @@ -1513,11 +1497,9 @@ func (m *GetReportRequest) SizeVT() (n int) { } var l int _ = l - if m.LastKnownGeneration != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.LastKnownGeneration)) - } - if m.KnownEpoch != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.KnownEpoch)) + l = len(m.LastKnownToken) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } n += len(m.unknownFields) return n @@ -2317,10 +2299,10 @@ func (m *ResponseMeta) UnmarshalVT(dAtA []byte) error { } iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ReportGeneration", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportToken", wireType) } - m.ReportGeneration = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -2330,11 +2312,24 @@ func (m *ResponseMeta) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ReportGeneration |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReportToken = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SupportedMethods", wireType) @@ -2495,25 +2490,6 @@ func (m *ResponseMeta) UnmarshalVT(dAtA []byte) error { m.Facts[mapkey] = mapvalue iNdEx = postIndex case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Epoch", wireType) - } - m.Epoch = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Epoch |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingHash", wireType) } @@ -2546,7 +2522,7 @@ func (m *ResponseMeta) UnmarshalVT(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.RepoCpeMappingHash = &s iNdEx = postIndex - case 8: + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingUpdatePath", wireType) } @@ -2618,10 +2594,10 @@ func (m *GetReportRequest) UnmarshalVT(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field LastKnownGeneration", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastKnownToken", wireType) } - m.LastKnownGeneration = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -2631,30 +2607,24 @@ func (m *GetReportRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.LastKnownGeneration |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field KnownEpoch", wireType) + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength } - m.KnownEpoch = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.KnownEpoch |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastKnownToken = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -3916,10 +3886,10 @@ func (m *ResponseMeta) UnmarshalVTUnsafe(dAtA []byte) error { } iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ReportGeneration", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportToken", wireType) } - m.ReportGeneration = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -3929,11 +3899,28 @@ func (m *ResponseMeta) UnmarshalVTUnsafe(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ReportGeneration |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.ReportToken = stringValue + iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SupportedMethods", wireType) @@ -4106,25 +4093,6 @@ func (m *ResponseMeta) UnmarshalVTUnsafe(dAtA []byte) error { m.Facts[mapkey] = mapvalue iNdEx = postIndex case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Epoch", wireType) - } - m.Epoch = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Epoch |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingHash", wireType) } @@ -4161,7 +4129,7 @@ func (m *ResponseMeta) UnmarshalVTUnsafe(dAtA []byte) error { s := stringValue m.RepoCpeMappingHash = &s iNdEx = postIndex - case 8: + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field RepoCpeMappingUpdatePath", wireType) } @@ -4233,10 +4201,10 @@ func (m *GetReportRequest) UnmarshalVTUnsafe(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field LastKnownGeneration", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastKnownToken", wireType) } - m.LastKnownGeneration = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4246,30 +4214,28 @@ func (m *GetReportRequest) UnmarshalVTUnsafe(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.LastKnownGeneration |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field KnownEpoch", wireType) + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength } - m.KnownEpoch = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.KnownEpoch |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.LastKnownToken = stringValue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/proto/internalapi/virtualmachine/v1/vm_service.proto b/proto/internalapi/virtualmachine/v1/vm_service.proto index 52d22597a003b..b2b9317742acd 100644 --- a/proto/internalapi/virtualmachine/v1/vm_service.proto +++ b/proto/internalapi/virtualmachine/v1/vm_service.proto @@ -9,7 +9,7 @@ option go_package = "./internalapi/virtualmachine/v1;v1"; // Protocol for pull-mode VM scanning over VSOCK between Sensor and roxagent. // See ADR-0006 §2 (Protocol) for the full design, exchange examples, and -// generation-based deduplication semantics. +// token-based deduplication semantics. // --- Envelope --- @@ -51,52 +51,35 @@ message ResponseMeta { string agent_version = 1; // When the cached scan report was produced by the scanner. google.protobuf.Timestamp report_generated_at = 2; - // Monotonic counter incremented on each rescan; resets to 1 on agent restart. - // Sensor uses this for deduplication (see ADR-0006 §2, "Generation counter"). - uint32 report_generation = 3; + // XXH64 of the cached IndexReport and facts. Identical content keeps the + // same token; Sensor still forces a full report with an empty last_known_token. + string report_token = 3; // Methods this agent accepts (e.g. ["get_report"]). Doubles as capability // discovery — no separate handshake needed. See ADR-0006 §2. repeated string supported_methods = 4; // VM metadata from the scan (detected_os, os_version, activation_status, etc.). // Future: may also carry roxagent operational metrics. map facts = 5; - // Opaque value seeded once per agent process lifetime (random/time-derived), - // held in memory only, never persisted to VM disk. Changes on every agent - // restart, unlike report_generation which resets to 1. Sensor should treat - // an epoch mismatch as "changed" regardless of report_generation, to avoid - // false-negative dedup when a restarted agent's reset generation coincides - // with Sensor's cached value for that VM. Zero means the agent predates this - // field; Sensor falls back to generation-only comparison in that case. - uint32 epoch = 6; // XXH64 hex of active mapping; empty string means none. optional so absence // means an older agent that does not report mapping metadata. - optional string repo_cpe_mapping_hash = 7; + optional string repo_cpe_mapping_hash = 6; // SENSOR or URL only; UNSPECIFIED means Sensor must not sync. - optional RepoCPEMappingUpdatePath repo_cpe_mapping_update_path = 8; + optional RepoCPEMappingUpdatePath repo_cpe_mapping_update_path = 7; } // --- Methods --- message GetReportRequest { - // Generation last observed by Sensor for this VM (see ResponseMeta.report_generation). - // Use 0 when no prior generation is known, which forces a full report. See - // ADR-0006 §2, "Generation counter", for how the agent uses this value. - uint32 last_known_generation = 1; - // Epoch last observed by Sensor for this VM (see ResponseMeta.epoch). 0 means - // unknown (first-ever request for this VM, or a Sensor build that predates - // this field) -- the agent should ignore epoch and fall back to - // generation-only comparison, exactly as if this field did not exist. This - // lets the agent detect a restart-coincidence false match (last_known_generation - // happens to equal the post-restart generation) and serve the full report in - // the same round trip, instead of Sensor needing a second, forced request. - uint32 known_epoch = 2; + // Token last observed by Sensor for this VM (see ResponseMeta.report_token). + // Empty when none is known, which forces a full report. + string last_known_token = 1; } message GetReportResponse { - // Full scan report; set when generation changed or forced (generation 0). + // Full scan report; set when the content hash changed or last_known_token is empty. scanner.v4.IndexReport index_report = 1; - // True when agent's generation matches last_known_generation; index_report - // is nil in this case. Sensor may still force a refresh after 4 h (ADR-0006 §2). + // True when the agent's token matches last_known_token; index_report is nil. + // Sensor may still force a refresh after 4 h with an empty last_known_token. bool unchanged = 2; } diff --git a/sensor/common/virtualmachine/ACK_MODEL.md b/sensor/common/virtualmachine/ACK_MODEL.md index f606901279cad..bdb638f84fce6 100644 --- a/sensor/common/virtualmachine/ACK_MODEL.md +++ b/sensor/common/virtualmachine/ACK_MODEL.md @@ -4,7 +4,7 @@ VM index reports flow directly from Sensor to Central; Compliance is not involve `VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central through `vmIndex.Handler`, which wraps the Scanner V4 payload as a `v1.IndexReport`. -A successful forward already returns the VM to poll cadence. Central's ACK is counted (`IndexReportAcksReceived`) and otherwise ignored. A NACK clears cached `report_generation` and applies the shared retry backoff so the next attempt pulls a full report. There is no payload cache and no ACK timeout. +A successful forward already returns the VM to poll cadence. Central's ACK is counted (`IndexReportAcksReceived`) and otherwise ignored. A NACK clears cached `report_token` and applies the shared retry backoff so the next attempt pulls a full report. There is no payload cache and no ACK timeout. A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks the per-VM schedule. Each VM is due at its own `nextAttemptAt`. Success and permanent failures reschedule at `pollInterval + U(0, W)`, where `pollInterval` is `ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL` (default 4h) and `W` is `ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION` times that interval (default 2/3). @@ -30,7 +30,7 @@ sequenceDiagram alt ACK Note over Sc: recorded; schedule unchanged else NACK - Sc->>Sc: clear cached generation; apply retry backoff + Sc->>Sc: clear cached token; apply retry backoff Note over Sc,A: next due attempt requests a full report end ``` @@ -43,4 +43,4 @@ Using only the vsock CID is unsafe because a CID can be reused by a different VM The pair is not a per-report unique ID: multiple in-flight reports from the same VM with the same CID are still not fully distinguishable, so a stale ACK can match the latest entry instead of the one it was actually for. -A NACK can also lose a race with the post-`Send` commit of `lastGeneration`, in which case the next poll may still see an "unchanged" delta. +A NACK can also lose a race with the post-`Send` commit of `lastToken`, in which case the next poll may still see an "unchanged" delta. diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 66655c3866334..70d150f1f9994 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -67,7 +67,7 @@ type IndexReportSender interface { // ProtocolClient performs the request/response protocol over a stream. type ProtocolClient interface { - GetReport(ctx context.Context, stream io.ReadWriteCloser, ifNewerThan uint32, knownEpoch uint32) (*vsockclient.GetReportResult, error) + GetReport(ctx context.Context, stream io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) } // VMScraper polls running VMs and pulls their scan reports via VSOCK. @@ -100,8 +100,7 @@ type VMScraper struct { } type vmState struct { - lastGeneration uint32 - lastEpoch uint32 + lastToken string lastForwardedAt time.Time nextAttemptAt time.Time backoff time.Duration @@ -183,11 +182,11 @@ func (s *VMScraper) ProcessMessage(_ context.Context, msg *central.MsgToSensor) return nil } -// handleNACK clears the cached generation and applies the shared backoff so +// handleNACK clears the cached token and applies the shared backoff so // the next tick resends a full report without tight-looping on persistent NACKs. // -// Race with commitVMState after Send is accepted (same class as today's -// lastGeneration race): a fast NACK may be overwritten by a late success commit. +// Race with commitVMState after Send is accepted: a fast NACK may be +// overwritten by a late success commit. func (s *VMScraper) handleNACK(resourceID string) { key := s.findKeyByVMID(vmIDFromResourceID(resourceID)) if key == "" { @@ -201,14 +200,14 @@ func (s *VMScraper) handleNACK(resourceID string) { if !exists { return false } - state.lastGeneration = 0 + state.lastToken = "" state.backoff = nextBackoff(state.backoff, s.interval, s.initialBackoff) state.nextAttemptAt = s.now().Add(state.backoff) backoff = state.backoff return true }) if ok { - log.Infof("VMScraper: NACK for %q; cleared generation, next attempt in %s", key, backoff) + log.Infof("VMScraper: NACK for %q; cleared token, next attempt in %s", key, backoff) } } @@ -415,14 +414,14 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool // The mandatory refresh is a deterministic, time-based decision Sensor // can make before dialing at all, so request the full report on this // round trip instead of asking "anything newer?" and dialing again. - ifNewerThan, knownEpoch := snap.lastGeneration, snap.lastEpoch + lastKnownToken := snap.lastToken mandatoryRefreshDue := s.now().Sub(snap.lastForwardedAt) > s.mandatoryRefreshAfter if mandatoryRefreshDue { - ifNewerThan, knownEpoch = 0, 0 + lastKnownToken = "" } log.Debugf("VMScraper: dialing roxagent on %q with TLS", key) - result, outcome := s.dialAndGetReport(vmCtx, vm, key, port, ifNewerThan, knownEpoch) + result, outcome := s.dialAndGetReport(vmCtx, vm, key, port, lastKnownToken) if outcome != scrapeOK { next := s.scheduleAfterAttempt(key, outcome) kind := "retryable" @@ -436,7 +435,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool if result.Unchanged { next := s.scheduleAfterAttempt(key, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=unchanged next=%s", key, next) - log.Debugf("VMScraper: unchanged report from roxagent on %q (generation=%d)", key, snap.lastGeneration) + log.Debugf("VMScraper: unchanged report from roxagent on %q (token=%s)", key, snap.lastToken) metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusUnchanged).Inc() return true } @@ -467,15 +466,15 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool return false } - newGen := result.Meta.GetReportGeneration() - s.commitVMState(key, newGen, result.Meta.GetEpoch()) + newToken := result.Meta.GetReportToken() + s.commitVMState(key, newToken) s.observeForwardInterarrival() next := s.scheduleAfterAttempt(key, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=forwarded next=%s", key, next) totalElapsed := s.now().Sub(totalStart) - log.Debugf("VMScraper: successfully pulled report for %q: generation=%d, packages=%d, size=%d bytes, total=%s", - key, newGen, len(result.IndexReport.GetContents().GetPackages()), reportSize, + log.Debugf("VMScraper: successfully pulled report for %q: token=%s, packages=%d, size=%d bytes, total=%s", + key, newToken, len(result.IndexReport.GetContents().GetPackages()), reportSize, totalElapsed.Truncate(time.Millisecond)) metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusSuccess).Inc() metrics.PullTotalDurationSeconds.Observe(totalElapsed.Seconds()) @@ -532,7 +531,7 @@ func (s *VMScraper) scheduleAfterAttempt(key string, outcome scrapeOutcome) time // dialAndGetReport dials the VM and issues a single GetReport request, // recording dial/read latency metrics and classifying failures. -func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Info, key string, port, ifNewerThan, knownEpoch uint32) (*vsockclient.GetReportResult, scrapeOutcome) { +func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, lastKnownToken string) (*vsockclient.GetReportResult, scrapeOutcome) { dialStart := s.now() stream, err := s.dialer.Dial(ctx, vm.Namespace, vm.Name, port, true) metrics.PullDialDurationSeconds.Observe(s.now().Sub(dialStart).Seconds()) @@ -553,7 +552,7 @@ func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Inf defer func() { _ = stream.Close() }() readStart := s.now() - result, err := s.client.GetReport(ctx, stream, ifNewerThan, knownEpoch) + result, err := s.client.GetReport(ctx, stream, lastKnownToken) metrics.PullReadDurationSeconds.Observe(s.now().Sub(readStart).Seconds()) if err != nil { return nil, s.handleGetReportError(ctx, key, err) @@ -604,8 +603,7 @@ func (s *VMScraper) handleGetReportError(ctx context.Context, key string, err er } type vmStateSnapshot struct { - lastGeneration uint32 - lastEpoch uint32 + lastToken string lastForwardedAt time.Time backoff time.Duration } @@ -620,15 +618,14 @@ func (s *VMScraper) snapshotVMState(key string) vmStateSnapshot { return vmStateSnapshot{} } return vmStateSnapshot{ - lastGeneration: st.lastGeneration, - lastEpoch: st.lastEpoch, + lastToken: st.lastToken, lastForwardedAt: st.lastForwardedAt, backoff: st.backoff, } }) } -// commitVMState records the generation/epoch from a just-sent report as key's cached scrape state. +// commitVMState records the token from a just-sent report as key's cached scrape state. // // Race: If a NACK arrives from Central faster than this function completes, for example, due to: // - a scheduling delay on the caller's goroutine between Send returning and this function @@ -636,17 +633,16 @@ func (s *VMScraper) snapshotVMState(key string) vmStateSnapshot { // - contention on `s.mu` itself, from other concurrent scrapes or NACKs delaying this // function's lock acquisition, // -// then the NACK will overwrite lastGeneration / backoff / nextAttemptAt, and then this code -// will set generation (and a later scheduleAfterAttempt(scrapeOK) will reset backoff schedule). -// This race is accepted for v1 (same class as the historical lastGeneration-only race). -func (s *VMScraper) commitVMState(key string, newGen, newEpoch uint32) { +// then the NACK will overwrite lastToken / backoff / nextAttemptAt, and then this code +// will set the token (and a later scheduleAfterAttempt(scrapeOK) will reset backoff schedule). +// This race is accepted for v1. +func (s *VMScraper) commitVMState(key string, newToken string) { concurrency.WithLock(&s.mu, func() { state, ok := s.vmState[key] if !ok { return } - state.lastGeneration = newGen - state.lastEpoch = newEpoch + state.lastToken = newToken state.lastForwardedAt = s.now() }) } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index f13ac283b636a..f3374229ccb5f 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -95,14 +95,13 @@ type mockProtocolClient struct { } type protocolCall struct { - ifNewerThan uint32 - knownEpoch uint32 + lastKnownToken string } -func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, ifNewerThan uint32, knownEpoch uint32) (*vsockclient.GetReportResult, error) { +func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) { m.mu.Lock() defer m.mu.Unlock() - m.calls = append(m.calls, protocolCall{ifNewerThan: ifNewerThan, knownEpoch: knownEpoch}) + m.calls = append(m.calls, protocolCall{lastKnownToken: lastKnownToken}) idx := m.callIdx m.callIdx++ if idx < len(m.errQueue) && m.errQueue[idx] != nil { @@ -171,13 +170,13 @@ func (c *testClock) Advance(d time.Duration) { c.t = c.t.Add(d) } -func makeReport(gen uint32) *vsockclient.GetReportResult { +func makeReport(token string) *vsockclient.GetReportResult { return &vsockclient.GetReportResult{ IndexReport: &v4.IndexReport{ State: "IndexFinished", }, Meta: &pb.ResponseMeta{ - ReportGeneration: gen, + ReportToken: token, Facts: map[string]string{ "detected_os": "RHEL", "activation_status": "ACTIVE", @@ -190,26 +189,7 @@ func makeReport(gen uint32) *vsockclient.GetReportResult { func unchangedResult() *vsockclient.GetReportResult { return &vsockclient.GetReportResult{ Unchanged: true, - Meta: &pb.ResponseMeta{ReportGeneration: 1}, - } -} - -func makeReportWithEpoch(gen, epoch uint32) *vsockclient.GetReportResult { - return &vsockclient.GetReportResult{ - IndexReport: &v4.IndexReport{ - State: "IndexFinished", - }, - Meta: &pb.ResponseMeta{ - ReportGeneration: gen, - Epoch: epoch, - }, - } -} - -func unchangedResultWithEpoch(gen, epoch uint32) *vsockclient.GetReportResult { - return &vsockclient.GetReportResult{ - Unchanged: true, - Meta: &pb.ResponseMeta{ReportGeneration: gen, Epoch: epoch}, + Meta: &pb.ResponseMeta{ReportToken: "1"}, } } @@ -223,7 +203,7 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1), makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, errQueue: []error{nil, nil}, } @@ -236,14 +216,14 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { assert.Equal(t, discoveredBefore+2, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) } -func TestVMScraper_SkipsUnchangedGeneration(t *testing.T) { +func TestVMScraper_SkipsUnchangedToken(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) @@ -266,7 +246,7 @@ func TestVMScraper_RemainsScheduledAcrossUnchangedPolls(t *testing.T) { sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) @@ -290,7 +270,7 @@ func TestVMScraper_ForwardsAfter4Hours(t *testing.T) { sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) @@ -302,143 +282,113 @@ func TestVMScraper_ForwardsAfter4Hours(t *testing.T) { clock.Advance(s.mandatoryRefreshAfter + time.Second) // The mandatory refresh is known before dialing, so a single call - // requesting the full report (ifNewerThan=0) is enough. + // requesting the full report (empty last_known_token) is enough. client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport(1)} + client.resultQueue = []*vsockclient.GetReportResult{makeReport("1")} s.pollOnce(context.Background()) require.Len(t, client.calls, 1, "mandatory refresh should resolve in a single round trip") - assert.Equal(t, uint32(0), client.calls[0].ifNewerThan, "mandatory refresh forces the full report on the only call") - assert.Equal(t, uint32(0), client.calls[0].knownEpoch, "mandatory refresh forces the full report on the only call") + assert.Empty(t, client.calls[0].lastKnownToken, "mandatory refresh forces the full report on the only call") assert.Len(t, sender.sent, 2, "should forward after 4h even if unchanged") } -// TestVMScraper_ForwardsOnEpochChangeInSingleDial covers a current agent that -// resolves restart-coincidence in one response (full report, new epoch). -func TestVMScraper_ForwardsOnEpochChangeInSingleDial(t *testing.T) { - store := &mockStore{vms: []*virtualmachine.Info{ - makeVM("ns1", "vm-a", 100), - }} - sender := &mockSender{} - dialer := &mockDialer{} - client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReportWithEpoch(5, 100)}, - } - - s, clock := newTestScraper(store, sender, dialer, client) - s.pollOnce(context.Background()) - require.Len(t, sender.sent, 1) - - client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReportWithEpoch(5, 200)} - clock.Advance(s.interval) - s.pollOnce(context.Background()) - - require.Len(t, client.calls, 1, "current agent serves the full report in one dial") - assert.Equal(t, uint32(5), client.calls[0].ifNewerThan) - assert.Equal(t, uint32(100), client.calls[0].knownEpoch) - assert.Len(t, sender.sent, 2) -} - -// TestVMScraper_SendsKnownEpochOnRequest verifies Sensor sends its -// last-cached epoch on every request, letting a current roxagent resolve a -// restart-coincidence false match in a single round trip. -func TestVMScraper_SendsKnownEpochOnRequest(t *testing.T) { +// TestVMScraper_SendsLastKnownTokenOnRequest verifies Sensor sends its +// last-cached token on every request so a matching scan reports unchanged. +func TestVMScraper_SendsLastKnownTokenOnRequest(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReportWithEpoch(1, 100)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("tok-100")}, } s, clock := newTestScraper(store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, client.calls, 1) - assert.Equal(t, uint32(0), client.calls[0].knownEpoch, "first-ever request for a VM has no cached epoch") + assert.Empty(t, client.calls[0].lastKnownToken, "first-ever request for a VM has no cached token") client.reset() - client.resultQueue = []*vsockclient.GetReportResult{unchangedResultWithEpoch(1, 100)} + client.resultQueue = []*vsockclient.GetReportResult{unchangedResult()} clock.Advance(s.interval) s.pollOnce(context.Background()) - require.Len(t, client.calls, 1, "matching epoch and generation should resolve in a single round trip") - assert.Equal(t, uint32(100), client.calls[0].knownEpoch, "subsequent requests send the cached epoch") + require.Len(t, client.calls, 1, "matching token should resolve in a single round trip") + assert.Equal(t, "tok-100", client.calls[0].lastKnownToken, "subsequent requests send the cached token") } -func TestVMScraper_ForwardsOnGenerationChange(t *testing.T) { +func TestVMScraper_ForwardsOnTokenChange(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, sender.sent, 1) - // New generation + // New token client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport(2)} + client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} clock.Advance(s.interval) s.pollOnce(context.Background()) - assert.Len(t, sender.sent, 2, "should forward on generation change") + assert.Len(t, sender.sent, 2, "should forward on token change") } // TestVMScraper_NACK reproduces a scenario where Central NACKs a report // (e.g. Scanner was still starting up). Without resetting the cached -// generation, the next poll would see roxagent report "unchanged" -// (report_generation didn't change) and skip resending, stranding the VM -// until mandatoryRefreshAfter (4h) instead of retrying on the next poll -// interval. It also verifies that a NACK is a no-op when it doesn't resolve -// to a currently-running, known VM (unrelated VM ID, malformed resource ID, -// or a VM that stopped running), and that an ACK never touches the cached -// generation. +// token, the next poll would see roxagent report "unchanged" and skip +// resending, stranding the VM until mandatoryRefreshAfter (4h) instead of +// retrying on the next poll interval. It also verifies that a NACK is a +// no-op when it doesn't resolve to a currently-running, known VM (unrelated +// VM ID, malformed resource ID, or a VM that stopped running), and that an +// ACK never touches the cached token. func TestVMScraper_NACK(t *testing.T) { cases := map[string]struct { - ackAction central.SensorACK_Action - nackResourceID string - vmRunning bool - pollResultAfterNack *vsockclient.GetReportResult - advanceAfterAck time.Duration - wantCalls int - wantIfNewerThanOnRetryPoll uint32 - wantTotalSent int + ackAction central.SensorACK_Action + nackResourceID string + vmRunning bool + pollResultAfterNack *vsockclient.GetReportResult + advanceAfterAck time.Duration + wantCalls int + wantLastKnownTokenOnRetryPoll string + wantTotalSent int }{ - "resets generation and resends after backoff when NACK matches the running VM": { - ackAction: central.SensorACK_NACK, - nackResourceID: "vm-a-id:100", - vmRunning: true, - pollResultAfterNack: makeReport(1), - advanceAfterAck: initialBackoff, - wantCalls: 1, - wantIfNewerThanOnRetryPoll: 0, - wantTotalSent: 2, + "resets token and resends after backoff when NACK matches the running VM": { + ackAction: central.SensorACK_NACK, + nackResourceID: "vm-a-id:100", + vmRunning: true, + pollResultAfterNack: makeReport("1"), + advanceAfterAck: initialBackoff, + wantCalls: 1, + wantLastKnownTokenOnRetryPoll: "", + wantTotalSent: 2, }, "is a no-op when NACK references an unrelated VM ID": { - ackAction: central.SensorACK_NACK, - nackResourceID: "unknown-vm-id:999", - vmRunning: true, - pollResultAfterNack: unchangedResult(), - advanceAfterAck: 5 * time.Minute, - wantCalls: 1, - wantIfNewerThanOnRetryPoll: 1, - wantTotalSent: 1, + ackAction: central.SensorACK_NACK, + nackResourceID: "unknown-vm-id:999", + vmRunning: true, + pollResultAfterNack: unchangedResult(), + advanceAfterAck: 5 * time.Minute, + wantCalls: 1, + wantLastKnownTokenOnRetryPoll: "1", + wantTotalSent: 1, }, "is a no-op when the resource ID has no vsockCID suffix": { - ackAction: central.SensorACK_NACK, - nackResourceID: "vm-a-id-with-no-colon", - vmRunning: true, - pollResultAfterNack: unchangedResult(), - advanceAfterAck: 5 * time.Minute, - wantCalls: 1, - wantIfNewerThanOnRetryPoll: 1, - wantTotalSent: 1, + ackAction: central.SensorACK_NACK, + nackResourceID: "vm-a-id-with-no-colon", + vmRunning: true, + pollResultAfterNack: unchangedResult(), + advanceAfterAck: 5 * time.Minute, + wantCalls: 1, + wantLastKnownTokenOnRetryPoll: "1", + wantTotalSent: 1, }, "is a no-op when the NACKed VM is no longer running": { ackAction: central.SensorACK_NACK, @@ -450,14 +400,14 @@ func TestVMScraper_NACK(t *testing.T) { wantTotalSent: 1, }, "is a no-op for an ACK": { - ackAction: central.SensorACK_ACK, - nackResourceID: "vm-a-id:100", - vmRunning: true, - pollResultAfterNack: unchangedResult(), - advanceAfterAck: 5 * time.Minute, - wantCalls: 1, - wantIfNewerThanOnRetryPoll: 1, - wantTotalSent: 1, + ackAction: central.SensorACK_ACK, + nackResourceID: "vm-a-id:100", + vmRunning: true, + pollResultAfterNack: unchangedResult(), + advanceAfterAck: 5 * time.Minute, + wantCalls: 1, + wantLastKnownTokenOnRetryPoll: "1", + wantTotalSent: 1, }, } @@ -469,7 +419,7 @@ func TestVMScraper_NACK(t *testing.T) { sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) @@ -500,8 +450,8 @@ func TestVMScraper_NACK(t *testing.T) { require.Len(t, client.calls, tc.wantCalls) if tc.wantCalls > 0 { - assert.Equal(t, tc.wantIfNewerThanOnRetryPoll, client.calls[0].ifNewerThan, - "generation the scraper requests on the poll following the NACK") + assert.Equal(t, tc.wantLastKnownTokenOnRetryPoll, client.calls[0].lastKnownToken, + "token the scraper requests on the poll following the NACK") } assert.Len(t, sender.sent, tc.wantTotalSent, "total reports handed to the sender across both polls") }) @@ -536,11 +486,11 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { vmA := makeVM("ns1", "vm-a", 100) vmA.ID = "vm-a-id" store := &mockStore{vms: []*virtualmachine.Info{vmA}} - client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport(1)}} + client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}} s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) s.pollOnce(t.Context()) - require.Equal(t, uint32(1), cachedGeneration(t, s, "ns1/vm-a")) + require.Equal(t, "1", cachedToken(t, s, "ns1/vm-a")) gate := &gateSender{ blockAt: 1, @@ -548,7 +498,7 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { } s.sender = gate client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport(2)} + client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} clock.Advance(s.interval) done := make(chan struct{}) @@ -570,12 +520,12 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { }, }, })) - assert.Equal(t, uint32(0), cachedGeneration(t, s, "ns1/vm-a"), + assert.Empty(t, cachedToken(t, s, "ns1/vm-a"), "NACK applies immediately while the send it targets is still in flight") close(gate.release) <-done - assert.Equal(t, uint32(2), cachedGeneration(t, s, "ns1/vm-a"), + assert.Equal(t, "2", cachedToken(t, s, "ns1/vm-a"), "the in-flight send's commit runs after the NACK reset and overwrites it unconditionally") }) } @@ -596,7 +546,7 @@ func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { }{ "should still send for vm-b when vm-a hits a protocol error": { dialer: &mockDialer{}, - resultQueue: []*vsockclient.GetReportResult{nil, makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{nil, makeReport("1")}, errQueue: []error{errors.New("connection refused"), nil}, wantCalls: 2, wantSent: 1, @@ -605,7 +555,7 @@ func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { dialer: &mockDialer{ errQueue: []error{errors.New("dial failed"), nil}, }, - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, wantCalls: 1, wantSent: 1, }, @@ -659,7 +609,7 @@ func TestVMScraper_GetReportTimeoutClassified(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - _, outcome := s.dialAndGetReport(ctx, vm, "ns1/vm-a", 1, 0, 0) + _, outcome := s.dialAndGetReport(ctx, vm, "ns1/vm-a", 1, "") assert.Equal(t, scrapeNonRetryable, outcome, "parent cancellation must not schedule a retry on the short tick") @@ -681,7 +631,7 @@ func TestVMScraper_GetReportDeadlineExceededClassified(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) defer cancel() <-ctx.Done() - _, outcome := s.dialAndGetReport(ctx, vm, "ns1/vm-a", 1, 0, 0) + _, outcome := s.dialAndGetReport(ctx, vm, "ns1/vm-a", 1, "") assert.Equal(t, scrapeRetryable, outcome, "a per-VM deadline is retried on the short tick") } @@ -698,7 +648,7 @@ func TestVMScraper_GetReportBusyClassified(t *testing.T) { busyBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy)) readErrBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)) - _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, 0, 0) + _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") assert.Equal(t, scrapeRetryable, outcome) assert.Equal(t, busyBefore+1, testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy))) @@ -714,7 +664,7 @@ func TestVMScraper_PrunesStaleState(t *testing.T) { sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1), makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } s, clock := newTestScraper(store, sender, dialer, client) @@ -725,7 +675,7 @@ func TestVMScraper_PrunesStaleState(t *testing.T) { // Remove vm-a from running set store.vms = []*virtualmachine.Info{makeVM("ns2", "vm-b", 200)} client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport(2)} + client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} clock.Advance(s.interval) s.pollOnce(context.Background()) @@ -743,14 +693,14 @@ func hasScheduleSlot(t *testing.T, s *VMScraper, key string) bool { }) } -// cachedGeneration reads a VM's cached generation under s.mu, so the read is +// cachedToken reads a VM's cached token under s.mu, so the read is // race-safe regardless of what locking handleNACK or commitVMState use internally. -func cachedGeneration(t *testing.T, s *VMScraper, key string) uint32 { +func cachedToken(t *testing.T, s *VMScraper, key string) string { t.Helper() - return concurrency.WithLock1(&s.mu, func() uint32 { + return concurrency.WithLock1(&s.mu, func() string { st, ok := s.vmState[key] require.True(t, ok, "no cached state for %q", key) - return st.lastGeneration + return st.lastToken }) } @@ -820,15 +770,15 @@ func (d *delayDialer) Dial(_ context.Context, _, _ string, _ uint32, _ bool) (io type safeProtocolClient struct { mu sync.Mutex - gen uint32 + token string calls int } -func (c *safeProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, _ uint32, _ uint32) (*vsockclient.GetReportResult, error) { +func (c *safeProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, _ string) (*vsockclient.GetReportResult, error) { c.mu.Lock() c.calls++ c.mu.Unlock() - return makeReport(c.gen), nil + return makeReport(c.token), nil } type safeSender struct { @@ -858,7 +808,7 @@ func TestVMScraper_ConcurrentFasterThanSequential(t *testing.T) { store := &mockStore{vms: vms} sender := &safeSender{} dialer := &delayDialer{delay: dialDelay} - client := &safeProtocolClient{gen: 1} + client := &safeProtocolClient{token: "1"} s, _ := newTestScraper(store, sender, dialer, client) s.concurrency = concurrency @@ -893,7 +843,7 @@ func TestVMScraper_RetryableFailureSchedulesBackoff(t *testing.T) { client.reset() client.errQueue = nil - client.resultQueue = []*vsockclient.GetReportResult{makeReport(1)} + client.resultQueue = []*vsockclient.GetReportResult{makeReport("1")} s.pollOnce(context.Background()) assert.Len(t, client.calls, 0, "should skip while backoff has not elapsed") @@ -928,7 +878,7 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { wantGap time.Duration }{ "send failure should retry using backoff": { - client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport(1)}}, + client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}}, sender: &mockSender{err: errors.New("central unavailable")}, wantBackoff: initialBackoff, wantGap: initialBackoff, @@ -965,7 +915,7 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { }, "invalid report should not retry using backoff": { client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{{ - Meta: &pb.ResponseMeta{ReportGeneration: 1}, + Meta: &pb.ResponseMeta{ReportToken: "1"}, }}}, sender: &mockSender{}, wantBackoff: 0, @@ -994,7 +944,7 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { func TestVMScraper_NonForcedTickSkipsReconcileWhenNotDue(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}} client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1), makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) diff --git a/sensor/common/virtualmachine/vmscraper/spreading_test.go b/sensor/common/virtualmachine/vmscraper/spreading_test.go index 2bc12766bdf70..685cb50816c81 100644 --- a/sensor/common/virtualmachine/vmscraper/spreading_test.go +++ b/sensor/common/virtualmachine/vmscraper/spreading_test.go @@ -47,7 +47,7 @@ func sequencedRand(seq []float64) func() float64 { func TestVMScraper_CadenceRescheduleUsesSteadyBand(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "vm-a", 1)}} s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.spreadFraction = 2.0 / 3 // First draw (reconcile insert) keeps the VM due now; second is the cadence offset. @@ -69,7 +69,7 @@ func TestVMScraper_ManyCadencedSuccessesSpanSteadyBand(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(store, &safeSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s, clock := newTestScraper(store, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.spreadFraction = 2.0 / 3 @@ -116,7 +116,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour units := make([]float64, numVMs) @@ -150,7 +150,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { func TestVMScraper_SingleInsertUsesNewVMIndexReportSpread(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "only", 1)}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = func() float64 { return 0.5 } @@ -166,7 +166,7 @@ func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { makeVM("ns", "a", 1), makeVM("ns", "b", 2), }} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = sequencedRand([]float64{0.25, 0.75}) @@ -195,7 +195,7 @@ func TestVMScraper_NACKPathDoesNotUseSteadyBand(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) store := &mockStore{vms: []*virtualmachine.Info{vm}} s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport(1)}, + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.randFloat64 = sequencedRand([]float64{0, 1}) s.pollOnce(context.Background()) @@ -213,7 +213,7 @@ func TestVMScraper_DuePileIsPacedByStartBudget(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.tickInterval = defaultTickInterval @@ -254,7 +254,7 @@ func TestVMScraper_AllDueClumpDrainsWithinIndexWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -298,7 +298,7 @@ func TestVMScraper_SpreadDuePileStartsAtFleetRate(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{gen: 1}) + s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -336,7 +336,7 @@ func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { for i := range numVMs { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } - s, clock := newTestScraper(&mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{gen: 1}) + s, clock := newTestScraper(&mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.reconcileEvery = reconcilePeriod(s.interval) diff --git a/sensor/common/virtualmachine/vsockclient/client.go b/sensor/common/virtualmachine/vsockclient/client.go index 38ce4c9cf3346..499e7575f6c20 100644 --- a/sensor/common/virtualmachine/vsockclient/client.go +++ b/sensor/common/virtualmachine/vsockclient/client.go @@ -65,17 +65,15 @@ func NewClient(capabilities []string, maxResponseSize int) *Client { return &Client{capabilities: capabilities, maxResponseSize: maxResponseSize} } -// GetReport sends a GetReportRequest and returns the response. knownEpoch is -// the last epoch Sensor observed for this VM (see ResponseMeta.epoch); pass 0 -// when unknown. Sending it lets the agent detect a restart-coincidence false -// match and serve the full report in this same round trip, instead of the -// caller needing a second, forced request. +// GetReport sends a GetReportRequest and returns the response. lastKnownToken +// is the last report_token Sensor observed for this VM; pass "" when unknown +// or when forcing a full report (mandatory refresh). // // The stream must be an io.ReadWriteCloser (from MultiDialer.Dial). If ctx is // cancelled while a write or read is in progress, the stream is closed so the // blocked I/O unblocks promptly — needed on Sensor shutdown, where parent // cancel does not rewrite the dial-time socket deadline. -func (c *Client) GetReport(ctx context.Context, stream io.ReadWriteCloser, ifNewerThan uint32, knownEpoch uint32) (*GetReportResult, error) { +func (c *Client) GetReport(ctx context.Context, stream io.ReadWriteCloser, lastKnownToken string) (*GetReportResult, error) { stop := context.AfterFunc(ctx, func() { _ = stream.Close() }) @@ -88,8 +86,7 @@ func (c *Client) GetReport(ctx context.Context, stream io.ReadWriteCloser, ifNew }, Method: &pb.VMServiceRequest_GetReport{ GetReport: &pb.GetReportRequest{ - LastKnownGeneration: ifNewerThan, - KnownEpoch: knownEpoch, + LastKnownToken: lastKnownToken, }, }, } diff --git a/sensor/common/virtualmachine/vsockclient/client_integration_test.go b/sensor/common/virtualmachine/vsockclient/client_integration_test.go index f7ca53bbdd583..605d99ff1a545 100644 --- a/sensor/common/virtualmachine/vsockclient/client_integration_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_integration_test.go @@ -65,16 +65,16 @@ func newProtocolHarness(t *testing.T, opts protocolHarnessOptions) *protocolHarn } } -func (h *protocolHarness) getReport(t *testing.T, ifNewerThan, knownEpoch uint32) (*GetReportResult, error) { +func (h *protocolHarness) getReport(t *testing.T, lastKnownToken string) (*GetReportResult, error) { t.Helper() - return exchangeOnce(t, h.client, ifNewerThan, knownEpoch, h.handler.HandleConn) + return exchangeOnce(t, h.client, lastKnownToken, h.handler.HandleConn) } // exchangeOnce runs a single GetReport against responder over net.Pipe() // inside a synctest bubble. A stuck peer makes the exchange timeout fire on // the fake clock (via GetReport's AfterFunc stream close) instead of hanging // the package on wall time. -func exchangeOnce(t *testing.T, client *Client, ifNewerThan, knownEpoch uint32, responder func(net.Conn)) (*GetReportResult, error) { +func exchangeOnce(t *testing.T, client *Client, lastKnownToken string, responder func(net.Conn)) (*GetReportResult, error) { t.Helper() var result *GetReportResult @@ -91,7 +91,7 @@ func exchangeOnce(t *testing.T, client *Client, ifNewerThan, knownEpoch uint32, responder(agentConn) }() - result, err = client.GetReport(ctx, clientConn, ifNewerThan, knownEpoch) + result, err = client.GetReport(ctx, clientConn, lastKnownToken) // Close the client end before waiting so a responder blocked in WriteFrame // (or similar) is unblocked even when GetReport already returned. _ = clientConn.Close() @@ -109,11 +109,10 @@ func exchangeOnce(t *testing.T, client *Client, ifNewerThan, knownEpoch uint32, func TestGetReportIntegration(t *testing.T) { cases := map[string]struct { // Arguments - seedReport *v4.IndexReport - seedFacts map[string]string - agentVersion string - ifNewerThan uint32 - knownEpoch uint32 + seedReport *v4.IndexReport + seedFacts map[string]string + agentVersion string + lastKnownToken string // Expectations wantErr error checkResult func(t *testing.T, result *GetReportResult) @@ -132,30 +131,22 @@ func TestGetReportIntegration(t *testing.T) { require.NotNil(t, result.Meta) assert.Equal(t, "roxagent-0.3.1-deadbeef", result.Meta.GetAgentVersion()) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) + assert.NotEmpty(t, result.Meta.GetReportToken()) assert.NotNil(t, result.Meta.GetReportGeneratedAt()) assert.Contains(t, result.Meta.GetSupportedMethods(), "get_report") assert.Equal(t, "rhel", result.Meta.GetFacts()["os"]) assert.Equal(t, "x86_64", result.Meta.GetFacts()["arch"]) }, }, - "should return unchanged when generation matches": { - seedReport: &v4.IndexReport{HashId: "unchanged-hash"}, - ifNewerThan: 1, + "should return full report when last known token does not match": { + seedReport: &v4.IndexReport{HashId: "post-restart-hash"}, + lastKnownToken: "stale-token", checkResult: func(t *testing.T, result *GetReportResult) { - assert.True(t, result.Unchanged) - assert.Nil(t, result.IndexReport) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) - }, - }, - "should return full report when sensor generation exceeds agent after restart": { - seedReport: &v4.IndexReport{HashId: "post-restart-hash"}, - ifNewerThan: 5, - checkResult: func(t *testing.T, result *GetReportResult) { - assert.False(t, result.Unchanged, "agent restarted (gen=1 < requested=5), must serve full report") + assert.False(t, result.Unchanged) require.NotNil(t, result.IndexReport) assert.Equal(t, "post-restart-hash", result.IndexReport.GetHashId()) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) + assert.NotEmpty(t, result.Meta.GetReportToken()) + assert.NotEqual(t, "stale-token", result.Meta.GetReportToken()) assert.NotNil(t, result.Meta.GetReportGeneratedAt()) assert.Contains(t, result.Meta.GetSupportedMethods(), "get_report") }, @@ -170,7 +161,7 @@ func TestGetReportIntegration(t *testing.T) { agentVersion: tc.agentVersion, }) - result, err := h.getReport(t, tc.ifNewerThan, tc.knownEpoch) + result, err := h.getReport(t, tc.lastKnownToken) if tc.wantErr != nil { require.Error(t, err) @@ -185,44 +176,35 @@ func TestGetReportIntegration(t *testing.T) { } } -// TestGetReportIntegration_KnownEpochSingleRoundTrip drives a real Client -// against a real roxagent Handler to verify the single-round-trip contract. -// report_generation resets to 1 on every roxagent restart, so a restarted -// agent can coincidentally match a generation Sensor already has cached. -// known_epoch lets the agent detect that restart itself, in a single round -// trip, by comparing Sensor's last-seen epoch against its current one. -func TestGetReportIntegration_KnownEpochSingleRoundTrip(t *testing.T) { +// TestGetReportIntegration_TokenRoundTrip drives a real Client against a +// real roxagent Handler: matching token is unchanged, a different token +// (restart or new scan) serves the full report in the same round trip. +func TestGetReportIntegration_TokenRoundTrip(t *testing.T) { h := newProtocolHarness(t, protocolHarnessOptions{ - seedReport: &v4.IndexReport{HashId: "epoch-test-hash"}, + seedReport: &v4.IndexReport{HashId: "token-test-hash"}, }) - // First-ever request for this VM: Sensor has no cached epoch yet. - first, err := h.getReport(t, 0, 0) + first, err := h.getReport(t, "") require.NoError(t, err) require.False(t, first.Unchanged) - epoch := first.Meta.GetEpoch() - require.NotZero(t, epoch, "agent should seed a non-zero epoch") + token := first.Meta.GetReportToken() + require.NotEmpty(t, token) - t.Run("unchanged in a single round trip when known epoch matches", func(t *testing.T) { - result, err := h.getReport(t, 1, epoch) + t.Run("unchanged in a single round trip when last known token matches", func(t *testing.T) { + result, err := h.getReport(t, token) require.NoError(t, err) assert.True(t, result.Unchanged) assert.Nil(t, result.IndexReport) + assert.Equal(t, token, result.Meta.GetReportToken()) }) - t.Run("full report in a single round trip when known epoch mismatches despite matching generation", func(t *testing.T) { - result, err := h.getReport(t, 1, epoch+1) + t.Run("full report in a single round trip when last known token mismatches", func(t *testing.T) { + result, err := h.getReport(t, "other-token") require.NoError(t, err) - assert.False(t, result.Unchanged, "epoch mismatch must be resolved in this same response, not a second dial") + assert.False(t, result.Unchanged) require.NotNil(t, result.IndexReport) - assert.Equal(t, "epoch-test-hash", result.IndexReport.GetHashId()) - assert.Equal(t, epoch, result.Meta.GetEpoch(), "response must carry the agent's current epoch") - }) - - t.Run("unchanged when known epoch is zero (backward compat: falls back to generation-only)", func(t *testing.T) { - result, err := h.getReport(t, 1, 0) - require.NoError(t, err) - assert.True(t, result.Unchanged) + assert.Equal(t, "token-test-hash", result.IndexReport.GetHashId()) + assert.Equal(t, token, result.Meta.GetReportToken()) }) } @@ -262,16 +244,15 @@ func respondOnce(t testing.TB, conn net.Conn, build func(*pb.VMServiceRequest) * // oldAgentResponder models a plausible older roxagent that: // - supports only get_report // - does not advertise supported_methods -// - does not include facts, report_generated_at, or epoch -// - always returns the full report (ignores last_known_generation and known_epoch) +// - does not include facts, report_generated_at, or report_token +// - always returns the full report (ignores last_known_token) // - returns UNKNOWN_METHOD for anything else -func oldAgentResponder(t testing.TB, report *v4.IndexReport, generation uint32) func(net.Conn) { +func oldAgentResponder(t testing.TB, report *v4.IndexReport) func(net.Conn) { return func(conn net.Conn) { respondOnce(t, conn, func(req *pb.VMServiceRequest) *pb.VMServiceResponse { resp := &pb.VMServiceResponse{ Meta: &pb.ResponseMeta{ - AgentVersion: "roxagent-0.1.0", - ReportGeneration: generation, + AgentVersion: "roxagent-0.1.0", }, } if req.GetGetReport() != nil { @@ -295,22 +276,22 @@ func oldAgentResponder(t testing.TB, report *v4.IndexReport, generation uint32) // - advertises extra supported_methods beyond get_report // - still handles get_report correctly // - includes richer metadata (facts, supported_methods) -// - supports last_known_generation (unchanged semantics) +// - supports last_known_token (unchanged semantics) // - returns UNKNOWN_METHOD for methods it doesn't recognize -func futureAgentResponder(t testing.TB, report *v4.IndexReport, generation uint32) func(net.Conn) { +func futureAgentResponder(t testing.TB, report *v4.IndexReport, token string) func(net.Conn) { return func(conn net.Conn) { respondOnce(t, conn, func(req *pb.VMServiceRequest) *pb.VMServiceResponse { resp := &pb.VMServiceResponse{ Meta: &pb.ResponseMeta{ AgentVersion: "roxagent-2.0.0-future", - ReportGeneration: generation, + ReportToken: token, SupportedMethods: []string{"get_report", "get_config", "submit_event"}, Facts: map[string]string{"os_id": "rhel", "protocol_version": "2"}, }, } switch { case req.GetGetReport() != nil: - if req.GetGetReport().GetLastKnownGeneration() == generation { + if req.GetGetReport().GetLastKnownToken() == token { resp.Result = &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{Unchanged: true}, } @@ -341,7 +322,7 @@ func assertOldAgentReport(t *testing.T, result *GetReportResult) { require.NotNil(t, result.IndexReport) assert.Equal(t, "compat-hash", result.IndexReport.GetHashId()) assert.Equal(t, "roxagent-0.1.0", result.Meta.GetAgentVersion()) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) + assert.Empty(t, result.Meta.GetReportToken()) assert.Empty(t, result.Meta.GetSupportedMethods()) assert.Empty(t, result.Meta.GetFacts()) } @@ -353,63 +334,52 @@ func TestGetReportCompatibility(t *testing.T) { cases := map[string]struct { // Arguments makeResponderFunc func(t testing.TB) func(net.Conn) - ifNewerThan uint32 - knownEpoch uint32 + lastKnownToken string // Expectations checkResult func(t *testing.T, result *GetReportResult) }{ "old agent should serve get_report to current sensor": { - makeResponderFunc: func(t testing.TB) func(net.Conn) { return oldAgentResponder(t, report, 1) }, + makeResponderFunc: func(t testing.TB) func(net.Conn) { return oldAgentResponder(t, report) }, checkResult: func(t *testing.T, result *GetReportResult) { assert.False(t, result.Unchanged) assertOldAgentReport(t, result) }, }, - "old agent should always return full report ignoring last_known_generation": { - makeResponderFunc: func(t testing.TB) func(net.Conn) { return oldAgentResponder(t, report, 1) }, - ifNewerThan: 1, + "old agent should always return full report ignoring last_known_token": { + makeResponderFunc: func(t testing.TB) func(net.Conn) { return oldAgentResponder(t, report) }, + lastKnownToken: "tok-1", checkResult: func(t *testing.T, result *GetReportResult) { assert.False(t, result.Unchanged, "old agent does not support unchanged optimization") assertOldAgentReport(t, result) }, }, - "old agent should tolerate non-zero known_epoch and omit epoch in response": { - makeResponderFunc: func(t testing.TB) func(net.Conn) { return oldAgentResponder(t, report, 1) }, - ifNewerThan: 1, - knownEpoch: 42, - checkResult: func(t *testing.T, result *GetReportResult) { - assert.False(t, result.Unchanged) - assertOldAgentReport(t, result) - assert.Zero(t, result.Meta.GetEpoch(), "old agent does not populate epoch") - }, - }, "future agent should serve get_report to current sensor": { - makeResponderFunc: func(t testing.TB) func(net.Conn) { return futureAgentResponder(t, report, 1) }, + makeResponderFunc: func(t testing.TB) func(net.Conn) { return futureAgentResponder(t, report, "tok-1") }, checkResult: func(t *testing.T, result *GetReportResult) { assert.False(t, result.Unchanged) require.NotNil(t, result.IndexReport) assert.Equal(t, "compat-hash", result.IndexReport.GetHashId()) assert.Equal(t, "roxagent-2.0.0-future", result.Meta.GetAgentVersion()) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) + assert.Equal(t, "tok-1", result.Meta.GetReportToken()) assert.Equal(t, []string{"get_report", "get_config", "submit_event"}, result.Meta.GetSupportedMethods()) assert.Equal(t, "rhel", result.Meta.GetFacts()["os_id"]) assert.Equal(t, "2", result.Meta.GetFacts()["protocol_version"]) }, }, - "future agent should return unchanged when generation matches": { - makeResponderFunc: func(t testing.TB) func(net.Conn) { return futureAgentResponder(t, report, 3) }, - ifNewerThan: 3, + "future agent should return unchanged when last known token matches": { + makeResponderFunc: func(t testing.TB) func(net.Conn) { return futureAgentResponder(t, report, "tok-3") }, + lastKnownToken: "tok-3", checkResult: func(t *testing.T, result *GetReportResult) { assert.True(t, result.Unchanged) assert.Nil(t, result.IndexReport) - assert.Equal(t, uint32(3), result.Meta.GetReportGeneration()) + assert.Equal(t, "tok-3", result.Meta.GetReportToken()) }, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { - result, err := exchangeOnce(t, client, tc.ifNewerThan, tc.knownEpoch, tc.makeResponderFunc(t)) + result, err := exchangeOnce(t, client, tc.lastKnownToken, tc.makeResponderFunc(t)) require.NoError(t, err) if tc.checkResult != nil { diff --git a/sensor/common/virtualmachine/vsockclient/client_test.go b/sensor/common/virtualmachine/vsockclient/client_test.go index e26e671b3a3fc..18b7d0c252cdf 100644 --- a/sensor/common/virtualmachine/vsockclient/client_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_test.go @@ -40,7 +40,7 @@ func TestSendGetReport_Success(t *testing.T) { defer utils.IgnoreError(clientConn.Close) go serveOnce(t, agentConn, &pb.VMServiceResponse{ - Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportGeneration: 1}, + Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportToken: "tok-1"}, Result: &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{ IndexReport: &v4.IndexReport{HashId: "test-hash"}, @@ -49,14 +49,14 @@ func TestSendGetReport_Success(t *testing.T) { }, func(req *pb.VMServiceRequest) { assert.NotEmpty(t, req.GetMeta().GetRequestId()) assert.Equal(t, []string{CapabilityReportV1}, req.GetMeta().GetCapabilities()) - assert.Equal(t, uint32(0), req.GetGetReport().GetLastKnownGeneration()) + assert.Empty(t, req.GetGetReport().GetLastKnownToken()) }) - result, err := client.GetReport(context.Background(), clientConn, 0, 0) + result, err := client.GetReport(context.Background(), clientConn, "") require.NoError(t, err) assert.Equal(t, "test-hash", result.IndexReport.GetHashId()) assert.False(t, result.Unchanged) - assert.Equal(t, uint32(1), result.Meta.GetReportGeneration()) + assert.Equal(t, "tok-1", result.Meta.GetReportToken()) } func TestSendGetReport_Unchanged(t *testing.T) { @@ -65,20 +65,19 @@ func TestSendGetReport_Unchanged(t *testing.T) { defer utils.IgnoreError(clientConn.Close) go serveOnce(t, agentConn, &pb.VMServiceResponse{ - Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportGeneration: 5}, + Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportToken: "tok-5"}, Result: &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{Unchanged: true}, }, }, func(req *pb.VMServiceRequest) { - assert.Equal(t, uint32(5), req.GetGetReport().GetLastKnownGeneration()) - assert.Equal(t, uint32(42), req.GetGetReport().GetKnownEpoch()) + assert.Equal(t, "tok-5", req.GetGetReport().GetLastKnownToken()) }) - result, err := client.GetReport(context.Background(), clientConn, 5, 42) + result, err := client.GetReport(context.Background(), clientConn, "tok-5") require.NoError(t, err) assert.Nil(t, result.IndexReport) assert.True(t, result.Unchanged) - assert.Equal(t, uint32(5), result.Meta.GetReportGeneration()) + assert.Equal(t, "tok-5", result.Meta.GetReportToken()) } func TestSendGetReport_NilReportRejected(t *testing.T) { @@ -87,13 +86,13 @@ func TestSendGetReport_NilReportRejected(t *testing.T) { defer utils.IgnoreError(clientConn.Close) go serveOnce(t, agentConn, &pb.VMServiceResponse{ - Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportGeneration: 1}, + Meta: &pb.ResponseMeta{AgentVersion: "test-agent", ReportToken: "tok-1"}, Result: &pb.VMServiceResponse_GetReport{ GetReport: &pb.GetReportResponse{}, }, }, nil) - _, err := client.GetReport(context.Background(), clientConn, 0, 0) + _, err := client.GetReport(context.Background(), clientConn, "") require.Error(t, err) assert.Contains(t, err.Error(), "IndexReport is nil") } @@ -140,7 +139,7 @@ func TestSendGetReport_ErrorCodes(t *testing.T) { }, }, nil) - _, err := client.GetReport(context.Background(), clientConn, 0, 0) + _, err := client.GetReport(context.Background(), clientConn, "") require.Error(t, err) assert.ErrorIs(t, err, tc.wantErr) if tc.wantInMsg != "" { @@ -171,7 +170,7 @@ func TestSendGetReport_ContextCancelUnblocks(t *testing.T) { errCh := make(chan error, 1) go func() { - _, err := client.GetReport(ctx, clientConn, 0, 0) + _, err := client.GetReport(ctx, clientConn, "") errCh <- err }() From 6d2ad01ea5b0610753603839ddf47ea6bcebbac0 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:27:22 +0200 Subject: [PATCH 090/638] ROX-36540: Skip VM events and IndexReports Central cannot consume (#22444) --- .../pipeline/virtualmachineindex/pipeline.go | 5 + .../virtualmachineindex/pipeline_test.go | 22 +++- .../pipeline/virtualmachines/pipeline.go | 14 +++ .../pipeline/virtualmachines/pipeline_test.go | 81 ++++++++++++- .../virtualmachine/vmscraper/scraper.go | 42 +++++-- .../virtualmachine/vmscraper/scraper_test.go | 106 ++++++++++++++---- .../vmscraper/spreading_test.go | 28 ++--- 7 files changed, 251 insertions(+), 47 deletions(-) diff --git a/central/sensor/service/pipeline/virtualmachineindex/pipeline.go b/central/sensor/service/pipeline/virtualmachineindex/pipeline.go index 51d4b8524ffb2..1390263358832 100644 --- a/central/sensor/service/pipeline/virtualmachineindex/pipeline.go +++ b/central/sensor/service/pipeline/virtualmachineindex/pipeline.go @@ -64,7 +64,12 @@ type pipelineImpl struct { func (p *pipelineImpl) OnFinish(string) {} +// Capabilities omits VirtualMachinesSupported when ROX_VIRTUAL_MACHINES is +// off so Sensor treats this Central like a pre-VM build. func (p *pipelineImpl) Capabilities() []centralsensor.CentralCapability { + if !features.VirtualMachines.Enabled() { + return nil + } return []centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported} } diff --git a/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go b/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go index b5f79480458d9..2ea1974c21746 100644 --- a/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go +++ b/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go @@ -2,6 +2,7 @@ package virtualmachineindex import ( "context" + "strconv" "testing" "github.com/pkg/errors" @@ -158,8 +159,25 @@ func (suite *PipelineTestSuite) TestRun_UpdateScanError() { } func (suite *PipelineTestSuite) TestCapabilities() { - capabilities := suite.pipeline.Capabilities() - suite.Contains(capabilities, centralsensor.CentralCapability(centralsensor.VirtualMachinesSupported)) + cases := map[string]struct { + enabled bool + want []centralsensor.CentralCapability + }{ + "flag on advertises VirtualMachinesSupported": { + enabled: true, + want: []centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}, + }, + "flag off advertises nothing": { + enabled: false, + want: nil, + }, + } + for name, tc := range cases { + suite.Run(name, func() { + suite.T().Setenv(features.VirtualMachines.EnvVar(), strconv.FormatBool(tc.enabled)) + suite.Equal(tc.want, suite.pipeline.Capabilities()) + }) + } } func (suite *PipelineTestSuite) TestOnFinish() { diff --git a/central/sensor/service/pipeline/virtualmachines/pipeline.go b/central/sensor/service/pipeline/virtualmachines/pipeline.go index 400a1506c1de2..3418898057a11 100644 --- a/central/sensor/service/pipeline/virtualmachines/pipeline.go +++ b/central/sensor/service/pipeline/virtualmachines/pipeline.go @@ -52,7 +52,12 @@ type pipelineImpl struct { func (p *pipelineImpl) OnFinish(_ string) {} +// Capabilities omits VirtualMachinesSupported when ROX_VIRTUAL_MACHINES is +// off so Sensor treats this Central like a pre-VM build. func (p *pipelineImpl) Capabilities() []centralsensor.CentralCapability { + if !features.VirtualMachines.Enabled() { + return nil + } return []centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported} } @@ -61,6 +66,10 @@ func (p *pipelineImpl) Match(msg *central.MsgFromSensor) bool { } func (p *pipelineImpl) Reconcile(ctx context.Context, clusterID string, storeMap *reconciliation.StoreMap) error { + if !features.VirtualMachines.Enabled() { + // An empty Sensor snapshot would otherwise delete every VM row for the cluster. + return nil + } if features.VirtualMachinesEnhancedDataModel.Enabled() { return p.reconcileV2(ctx, clusterID, storeMap) } @@ -102,6 +111,11 @@ func (p *pipelineImpl) reconcileV2(ctx context.Context, clusterID string, storeM func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.MsgFromSensor, _ common.MessageInjector) error { defer countMetrics.IncrementResourceProcessedCounter(pipeline.ActionToOperation(msg.GetEvent().GetAction()), metrics.VirtualMachine) + if !features.VirtualMachines.Enabled() { + // Returning nil avoids Sensor retries; the rows stay untouched. + return nil + } + event := msg.GetEvent() virtualMachine := event.GetVirtualMachine() if virtualMachine == nil { diff --git a/central/sensor/service/pipeline/virtualmachines/pipeline_test.go b/central/sensor/service/pipeline/virtualmachines/pipeline_test.go index f263cc97abd0a..98edf66f1dba2 100644 --- a/central/sensor/service/pipeline/virtualmachines/pipeline_test.go +++ b/central/sensor/service/pipeline/virtualmachines/pipeline_test.go @@ -1,6 +1,7 @@ package virtualmachines import ( + "strconv" "testing" clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" @@ -24,11 +25,25 @@ import ( func TestCapabilities(t *testing.T) { pipeline := &pipelineImpl{} - assert.ElementsMatch( - t, - []centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}, - pipeline.Capabilities(), - ) + cases := map[string]struct { + enabled bool + want []centralsensor.CentralCapability + }{ + "flag on advertises VirtualMachinesSupported": { + enabled: true, + want: []centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}, + }, + "flag off advertises nothing": { + enabled: false, + want: nil, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), strconv.FormatBool(tc.enabled)) + assert.Equal(t, tc.want, pipeline.Capabilities()) + }) + } } func TestMatch(t *testing.T) { @@ -285,6 +300,62 @@ func TestPipelineReconcile(t *testing.T) { } } +func TestPipelineRun_FeatureDisabledDoesNotWrite(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), "false") + testClusterID := fixtureconsts.Cluster1 + upsertTestVM := &virtualMachineV1.VirtualMachine{ + Id: uuid.NewTestUUID(1).String(), + Namespace: "test-namespace", + Name: "test-virtual-machine", + ClusterId: testClusterID, + } + msgs := map[string]*central.MsgFromSensor{ + "create": getVirtualMachineAdditionMessage(upsertTestVM), + "remove": getVirtualMachineRemovalMessage("removed_vm_id"), + } + models := map[string]bool{ + "v1": false, + "v2": true, + } + for model, enhanced := range models { + t.Run(model, func(t *testing.T) { + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), strconv.FormatBool(enhanced)) + for name, msg := range msgs { + t.Run(name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + pipeline := newPipeline( + clusterDSMocks.NewMockDataStore(mockCtrl), + virtualMachineDSMocks.NewMockDataStore(mockCtrl), + virtualMachineV2DSMocks.NewMockDataStore(mockCtrl), + ) + assert.NoError(t, pipeline.Run(t.Context(), testClusterID, msg, nil)) + }) + } + }) + } +} + +func TestPipelineReconcile_FeatureDisabledDoesNotDelete(t *testing.T) { + t.Setenv(features.VirtualMachines.EnvVar(), "false") + testClusterID := fixtureconsts.Cluster1 + cases := map[string]bool{ + "v1": false, + "v2": true, + } + for name, enhanced := range cases { + t.Run(name, func(t *testing.T) { + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), strconv.FormatBool(enhanced)) + mockCtrl := gomock.NewController(t) + pipeline := newPipeline( + nil, + virtualMachineDSMocks.NewMockDataStore(mockCtrl), + virtualMachineV2DSMocks.NewMockDataStore(mockCtrl), + ) + assert.NoError(t, pipeline.Reconcile(t.Context(), testClusterID, reconciliation.NewStoreMap())) + }) + } +} + func TestPipelineRunV2(t *testing.T) { t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), "true") testClusterID := fixtureconsts.Cluster1 diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 70d150f1f9994..49a034fa62ce9 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -14,10 +14,12 @@ import ( "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/sensor/common" + "github.com/stackrox/rox/sensor/common/centralcaps" "github.com/stackrox/rox/sensor/common/message" "github.com/stackrox/rox/sensor/common/virtualmachine" "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" @@ -32,9 +34,13 @@ var ( errStartMoreThanOnce = errors.New("unable to start the VM scraper more than once") ) -// minPollInterval clamps ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL to avoid -// accidental high-churn vsock polling. -const minPollInterval = time.Minute +const ( + // minPollInterval clamps ROX_VIRTUAL_MACHINES_SCRAPER_POLL_INTERVAL to avoid + // accidental high-churn vsock polling. + minPollInterval = time.Minute + + sendNotImplementedLogLimiter = "vm-scraper-send-not-implemented" +) func getVsockPort() uint32 { return uint32(env.VirtualMachinesVsockPort.IntegerSetting()) @@ -87,7 +93,10 @@ type VMScraper struct { warnMaxBytes int stopper concurrency.Stopper started atomic.Bool - now func() time.Time + // loggedSkip is set after the first skip log for a missing-capability stretch + // so a 10s ticker does not repeat it until the capability returns. + loggedSkip atomic.Bool + now func() time.Time // randFloat64 returns a unit sample in [0, 1] for schedule offsets; tests inject a fixed source. randFloat64 func() float64 lastSpreadWarnNumVMs int @@ -254,6 +263,14 @@ func (s *VMScraper) run() { } func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { + if !centralcaps.Has(centralsensor.VirtualMachinesSupported) { + if s.loggedSkip.CompareAndSwap(false, true) { + log.Infof("VMScraper: skipping pulling index reports from VMs; Central does not advertise VirtualMachinesSupported") + } + return + } + s.loggedSkip.Store(false) + tickStart := s.now() reconcile := forceReconcile if !reconcile { @@ -457,12 +474,23 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) if err := s.sender.Send(vmCtx, vm, result.IndexReport); err != nil { - log.Errorf("VMScraper: sending %q report to Central failed: %v", key, err) metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusSendError).Inc() + outcome := scrapeRetryable + if errors.Is(err, errox.NotImplemented) { + logging.GetRateLimitedLogger().WarnL(sendNotImplementedLogLimiter, + "VMScraper: Central cannot consume VM index reports: %v", err) + outcome = scrapeNonRetryable + } else { + log.Errorf("VMScraper: sending %q report to Central failed: %v", key, err) + } // Send failures are typically a transient Central connection issue, so // retry on the short backoff rather than waiting a full poll interval. - next := s.scheduleAfterAttempt(key, scrapeRetryable) - log.Infof("VMScraper: scrape %q failed retryable next=%s", key, next) + next := s.scheduleAfterAttempt(key, outcome) + kind := "retryable" + if outcome == scrapeNonRetryable { + kind = "non-retryable" + } + log.Infof("VMScraper: scrape %q failed %s next=%s", key, kind, next) return false } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index f3374229ccb5f..6df670a5857ac 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -14,14 +14,20 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/errox" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/sensor/common/centralcaps" "github.com/stackrox/rox/sensor/common/virtualmachine" "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" "github.com/stackrox/rox/sensor/common/virtualmachine/vsockclient" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" ) // --- Mocks --- @@ -207,7 +213,7 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { errQueue: []error{nil, nil}, } - s, _ := newTestScraper(store, sender, dialer, client) + s, _ := newTestScraper(t, store, sender, dialer, client) discoveredBefore := testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE")) s.pollOnce(context.Background()) @@ -216,6 +222,58 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { assert.Equal(t, discoveredBefore+2, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) } +// TestVMScraper_SkipsWhenCentralLacksCapability covers a store already +// populated (for example after reconnecting to an older Central) so the +// scraper must not dial or forward. +func TestVMScraper_SkipsWhenCentralLacksCapability(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }} + sender := &mockSender{} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, _ := newTestScraper(t, store, sender, dialer, client) + centralcaps.Set(nil) + + s.pollOnce(context.Background()) + + assert.Equal(t, 0, store.listRunningCalls, "should not reconcile when Central cannot consume reports") + assert.Zero(t, dialer.callIdx.Load()) + assert.Empty(t, sender.sent) + assert.Empty(t, client.calls) +} + +// TestVMScraper_LogsSkipOnceWhileCapabilityMissing covers a missing-capability +// stretch that lasts more than one tick, then a later drop after the +// capability returns, so the skip log fires once per stretch not once per tick. +func TestVMScraper_LogsSkipOnceWhileCapabilityMissing(t *testing.T) { + core, logs := observer.New(zap.InfoLevel) + orig := log + log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} + t.Cleanup(func() { log = orig }) + + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }}, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + }) + centralcaps.Set(nil) + + s.pollOnce(t.Context()) + s.pollOnce(t.Context()) + assert.Equal(t, 1, logs.FilterMessageSnippet("skipping pull").Len()) + + centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) + s.pollOnce(t.Context()) + + centralcaps.Set(nil) + s.pollOnce(t.Context()) + assert.Equal(t, 2, logs.FilterMessageSnippet("skipping pull").Len()) +} + func TestVMScraper_SkipsUnchangedToken(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), @@ -226,7 +284,7 @@ func TestVMScraper_SkipsUnchangedToken(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, sender.sent, 1) @@ -249,7 +307,7 @@ func TestVMScraper_RemainsScheduledAcrossUnchangedPolls(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) @@ -273,7 +331,7 @@ func TestVMScraper_ForwardsAfter4Hours(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, sender.sent, 1) @@ -304,7 +362,7 @@ func TestVMScraper_SendsLastKnownTokenOnRequest(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("tok-100")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, client.calls, 1) @@ -329,7 +387,7 @@ func TestVMScraper_ForwardsOnTokenChange(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, sender.sent, 1) @@ -422,7 +480,7 @@ func TestVMScraper_NACK(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) require.Len(t, sender.sent, 1) @@ -487,7 +545,7 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { vmA.ID = "vm-a-id" store := &mockStore{vms: []*virtualmachine.Info{vmA}} client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) s.pollOnce(t.Context()) require.Equal(t, "1", cachedToken(t, s, "ns1/vm-a")) @@ -574,7 +632,7 @@ func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { resultQueue: tc.resultQueue, errQueue: tc.errQueue, } - s, _ := newTestScraper(&mockStore{vms: vms}, sender, tc.dialer, client) + s, _ := newTestScraper(t, &mockStore{vms: vms}, sender, tc.dialer, client) if tc.perVMTimeout > 0 { s.perVMTimeout = tc.perVMTimeout } @@ -587,7 +645,7 @@ func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { } func TestVMScraper_StartRejectsSecondCall(t *testing.T) { - s, _ := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) require.NoError(t, s.Start()) t.Cleanup(s.Stop) @@ -602,7 +660,7 @@ func TestVMScraper_GetReportTimeoutClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{errors.New("i/o timeout")}, } - s, _ := newTestScraper(&mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) timeoutBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusTimeout)) readErrBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)) @@ -626,7 +684,7 @@ func TestVMScraper_GetReportDeadlineExceededClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{errors.New("i/o timeout")}, } - s, _ := newTestScraper(&mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) defer cancel() @@ -643,7 +701,7 @@ func TestVMScraper_GetReportBusyClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{vsockclient.ErrBusy}, } - s, _ := newTestScraper(&mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) busyBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy)) readErrBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)) @@ -667,7 +725,7 @@ func TestVMScraper_PrunesStaleState(t *testing.T) { resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } - s, clock := newTestScraper(store, sender, dialer, client) + s, clock := newTestScraper(t, store, sender, dialer, client) s.pollOnce(context.Background()) assert.Len(t, s.vmState, 2) assert.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) @@ -704,7 +762,11 @@ func cachedToken(t *testing.T, s *VMScraper, key string) string { }) } -func newTestScraper(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { +func newTestScraper(t *testing.T, store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { + t.Helper() + centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) + t.Cleanup(func() { centralcaps.Set(nil) }) + clock := newTestClock() interval := 5 * time.Minute s := &VMScraper{ @@ -810,7 +872,7 @@ func TestVMScraper_ConcurrentFasterThanSequential(t *testing.T) { dialer := &delayDialer{delay: dialDelay} client := &safeProtocolClient{token: "1"} - s, _ := newTestScraper(store, sender, dialer, client) + s, _ := newTestScraper(t, store, sender, dialer, client) s.concurrency = concurrency // This test measures real dial overlap via delayDialer's timers, so it // needs the wall clock rather than the fake test clock. @@ -835,7 +897,7 @@ func TestVMScraper_RetryableFailureSchedulesBackoff(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{vsockclient.ErrNotReady}, } - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) s.pollOnce(context.Background()) require.Len(t, client.calls, 1) @@ -883,6 +945,12 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { wantBackoff: initialBackoff, wantGap: initialBackoff, }, + "not-implemented send should not retry using backoff": { + client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}}, + sender: &mockSender{err: errox.NotImplemented}, + wantBackoff: 0, + wantGap: 5 * time.Minute, + }, "ErrInternal should retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrInternal}}, sender: &mockSender{}, @@ -924,7 +992,7 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { } for name, tc := range cases { t.Run(name, func(t *testing.T) { - s, clock := newTestScraper( + s, clock := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}}, tc.sender, &mockDialer{}, tc.client, ) @@ -946,7 +1014,7 @@ func TestVMScraper_NonForcedTickSkipsReconcileWhenNotDue(t *testing.T) { client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) s.pollOnce(context.Background()) require.Equal(t, 1, store.listRunningCalls, "pollOnce forces exactly one reconcile") diff --git a/sensor/common/virtualmachine/vmscraper/spreading_test.go b/sensor/common/virtualmachine/vmscraper/spreading_test.go index 685cb50816c81..ab2229ec6ef46 100644 --- a/sensor/common/virtualmachine/vmscraper/spreading_test.go +++ b/sensor/common/virtualmachine/vmscraper/spreading_test.go @@ -46,7 +46,7 @@ func sequencedRand(seq []float64) func() float64 { func TestVMScraper_CadenceRescheduleUsesSteadyBand(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "vm-a", 1)}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.spreadFraction = 2.0 / 3 @@ -69,7 +69,7 @@ func TestVMScraper_ManyCadencedSuccessesSpanSteadyBand(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(store, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.spreadFraction = 2.0 / 3 @@ -116,7 +116,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour units := make([]float64, numVMs) @@ -150,7 +150,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { func TestVMScraper_SingleInsertUsesNewVMIndexReportSpread(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "only", 1)}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = func() float64 { return 0.5 } @@ -166,7 +166,7 @@ func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { makeVM("ns", "a", 1), makeVM("ns", "b", 2), }} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = sequencedRand([]float64{0.25, 0.75}) @@ -180,7 +180,7 @@ func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { func TestVMScraper_RetryPathDoesNotUseSteadyBand(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}} client := &mockProtocolClient{errQueue: []error{vsockclient.ErrNotReady}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) // Insert due now; a later unit of 1 would be full steadyWidth if cadence used RNG. s.randFloat64 = sequencedRand([]float64{0, 1}) @@ -194,7 +194,7 @@ func TestVMScraper_RetryPathDoesNotUseSteadyBand(t *testing.T) { func TestVMScraper_NACKPathDoesNotUseSteadyBand(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) store := &mockStore{vms: []*virtualmachine.Info{vm}} - s, clock := newTestScraper(store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.randFloat64 = sequencedRand([]float64{0, 1}) @@ -213,7 +213,7 @@ func TestVMScraper_DuePileIsPacedByStartBudget(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.tickInterval = defaultTickInterval @@ -254,7 +254,7 @@ func TestVMScraper_AllDueClumpDrainsWithinIndexWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -298,7 +298,7 @@ func TestVMScraper_SpreadDuePileStartsAtFleetRate(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(&mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -336,7 +336,7 @@ func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { for i := range numVMs { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } - s, clock := newTestScraper(&mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, &mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.reconcileEvery = reconcilePeriod(s.interval) @@ -359,7 +359,7 @@ func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { } func TestVMScraper_StartsPerTickSkipsIdleTicks(t *testing.T) { - s, clock := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, clock := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) now := clock.Now() concurrency.WithLock(&s.mu, func() { s.lastReconcile = now @@ -409,7 +409,7 @@ func TestVMScraper_WarnIfSpreadSaturated(t *testing.T) { log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} t.Cleanup(func() { log = orig }) - s, _ := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) s.tickInterval = tc.tickInterval s.spreadFraction = tc.spreadFraction s.warnIfSpreadSaturated(tc.numVMs) @@ -420,7 +420,7 @@ func TestVMScraper_WarnIfSpreadSaturated(t *testing.T) { } func TestVMScraper_ForwardInterarrivalObservesAfterFirst(t *testing.T) { - s, _ := newTestScraper(&mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) before := histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds) s.observeForwardInterarrival() assert.Equal(t, before, histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds), From 7325cd7323161a733c12b01f7f5598e861b137f5 Mon Sep 17 00:00:00 2001 From: David Vail Date: Wed, 26 Aug 2026 14:50:17 -0400 Subject: [PATCH 091/638] fix(ui): Expands sensor compat cell when sensor not initialized (#22478) --- .../src/Containers/Clusters/ClustersTable.tsx | 27 +++++++------------ .../Components/SensorCompatibilityPanel.tsx | 2 +- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx b/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx index 21bfd8eb38888..e3b7079c0ec3f 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClustersTable.tsx @@ -167,23 +167,16 @@ function ClustersTable({ - toggle( - clusterId, - EXPANDABLE_COLUMN.SENSOR - ), - rowIndex, - columnIndex: 4, - } - : undefined - } + compoundExpand={{ + isExpanded: isCellExpanded( + clusterId, + EXPANDABLE_COLUMN.SENSOR + ), + onToggle: () => + toggle(clusterId, EXPANDABLE_COLUMN.SENSOR), + rowIndex, + columnIndex: 4, + }} > Date: Wed, 26 Aug 2026 14:58:52 -0400 Subject: [PATCH 092/638] ROX-36384: Removes auto-margins on sunburst component (#22351) --- ui/apps/platform/src/Components/visuals/Sunburst.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/apps/platform/src/Components/visuals/Sunburst.jsx b/ui/apps/platform/src/Components/visuals/Sunburst.jsx index ee43137269dc6..eb5e3735e0416 100644 --- a/ui/apps/platform/src/Components/visuals/Sunburst.jsx +++ b/ui/apps/platform/src/Components/visuals/Sunburst.jsx @@ -149,7 +149,7 @@ class Sunburst extends Component { // TODO: factor out into dimension mapping width: this.props.small ? 200 : 265, height: this.props.small ? 200 : 271, - className: 'cursor-pointer pointer-events-none my-auto', + className: 'cursor-pointer pointer-events-none', onValueMouseOver: this.onValueMouseOverHandler, onValueMouseOut: this.onValueMouseOutHandler, onValueClick: this.onValueClickHandler, From 9c993b384641be029c2981aef2c69f66c2492b63 Mon Sep 17 00:00:00 2001 From: Stephan Hesselmann Date: Wed, 26 Aug 2026 22:57:19 +0200 Subject: [PATCH 093/638] ROX-34488: add e2e tests for referrer-based signature verification (#21910) Co-authored-by: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 + .../scripts/images-to-prefetch.txt | 4 + .../ImageSignatureVerificationTest.groovy | 575 +++++++++++++----- .../groovy/testdata/referrer-test-images.md | 161 +++++ 4 files changed, 600 insertions(+), 142 deletions(-) create mode 100644 qa-tests-backend/src/test/groovy/testdata/referrer-test-images.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c0f6627e31bd..23a8f64f3d1b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-32461: Red Hat OpenShift Data Foundation is now officially supported as an S3-compatible backup target. - ROX-35962: On OCP, central API is exposed via a new `central-ocp` service, signed and rotated by OCP. - ROX-35508: Scanner V4 now suppresses duplicate OSV.dev vulnerability records when Red Hat VEX data covers the same CVE for a Red Hat product image, showing Red Hat's own severity/CVSS/remediation data instead of a conflicting OSV.dev one. Enabled by default; disable via `ROX_SCANNER_V4_SUPPRESS_OSV_WITH_RED_HAT_VEX=false` if needed. +- ROX-34488: Added support for cosign signature discovery via OCI 1.1 referrers, including + DSSE envelope verification for sigstore bundle-format signatures. ### Removed Features diff --git a/qa-tests-backend/scripts/images-to-prefetch.txt b/qa-tests-backend/scripts/images-to-prefetch.txt index 4d90575c82287..2f20a0616bf1e 100644 --- a/qa-tests-backend/scripts/images-to-prefetch.txt +++ b/qa-tests-backend/scripts/images-to-prefetch.txt @@ -80,4 +80,8 @@ quay.io/rhacs-eng/qa-signatures:distroless-base-multiarch@sha256:bc217643f9c04fc quay.io/rhacs-eng/qa-signatures:keyless-rhtas@sha256:e246aa22ad2cbdfbd19e2a6ca2b275e26245a21920e2b2d0666324cee3f15549 quay.io/rhacs-eng/qa-signatures:keyless-sigstore@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f quay.io/rhacs-eng/qa-signatures:nginx-multiarch@sha256:dd2d0ac3fff2f007d99e033b64854be0941e19a2ad51f174d9240dda20d9f534 +quay.io/rhacs-eng/qa-signatures:referrer-byopki@sha256:05a79c7279f71f86a2a0d05eb72fcb56ea36139150f0a75cd87e80a4272e4e39 +quay.io/rhacs-eng/qa-signatures:referrer-keyless-rhtas@sha256:e8e5cca392e3cf056fcdb3093e7ac2bf83fcf28b3bcf5818fe8ae71cf360c231 +quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-keyless@sha256:98ad9d1a2be345201bb0709b0d38655eb1b370145c7d94ca1fe9c421f76e245a +quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-pubkey@sha256:9e2bbca079387d7965c3a9cee6d0c53f4f4e63ff7637877a83c4c05f2a666112 quay.io/rhacs-eng/qa-signatures:tekton-multiarch@sha256:d12d420438235ccee3f4fcb72cf9e5b2b79f60f713595fe1ada254d10167dfc6 diff --git a/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy b/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy index d7939df593b29..594d17d0aa1a7 100644 --- a/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy +++ b/qa-tests-backend/src/test/groovy/ImageSignatureVerificationTest.groovy @@ -42,6 +42,10 @@ class ImageSignatureVerificationTest extends BaseSpecification { static final private String KEYLESS_SIGSTORE_UNVERIFIABLE = "Keyless-Sigstore-Unverifiable" static final private String KEYLESS_RHTAS_MATCHING = "Keyless-RHTAS-Matching" static final private String KEYLESS_RHTAS_UNVERIFIABLE = "Keyless-RHTAS-Unverifiable" + static final private String REFERRER_BYOPKI_MATCHING = "Referrer-BYOPKI-Matching" + static final private String REFERRER_BYOPKI_UNVERIFIABLE = "Referrer-BYOPKI-Unverifiable" + static final private String REFERRER_PUBKEY_MATCHING = "Referrer-PubKey-Matching" + static final private String REFERRER_PUBKEY_UNVERIFIABLE = "Referrer-PubKey-Unverifiable" // List of integration names used within tests. // NOTE: If you add a new name, make sure to add it here. @@ -59,6 +63,10 @@ class ImageSignatureVerificationTest extends BaseSpecification { KEYLESS_SIGSTORE_UNVERIFIABLE, KEYLESS_RHTAS_MATCHING, KEYLESS_RHTAS_UNVERIFIABLE, + REFERRER_BYOPKI_MATCHING, + REFERRER_BYOPKI_UNVERIFIABLE, + REFERRER_PUBKEY_MATCHING, + REFERRER_PUBKEY_UNVERIFIABLE, ] // Public keys used within signature integrations. @@ -139,6 +147,87 @@ nzTe7BpOmVwmqLkIefEJe5L4PSXtp2KFLZqGO/kY5A== static final private String BYOPKI_UNVERIFIABLE_ISSUER = "invalid" static final private String BYOPKI_UNVERIFIABLE_IDENTITY = "invalid" + // Root CA bundle for referrer BYOPKI test image. + static final private String REFERRER_BYOPKI_CA_BUNDLE = """\ +-----BEGIN CERTIFICATE----- +MIIFqzCCA5OgAwIBAgIUXp5NfhduES4NIr+wVdh6oGiDmMkwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCVVMxEDAOBgNVBAcMB1Rlc3RpbmcxDDAKBgNVBAoMA0RF +VjEQMA4GA1UECwwHVGVzdGluZzETMBEGA1UEAwwKVGVzdGluZyBDQTAgFw0yNjA3 +MjgxMzAwNDZaGA8yMDUzMTIxMjEzMDA0NlowYTELMAkGA1UEBhMCVVMxEDAOBgNV +BAcMB1Rlc3RpbmcxDDAKBgNVBAoMA0RFVjEQMA4GA1UECwwHVGVzdGluZzEgMB4G +A1UEAwwXVGVzdGluZyBJbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCot6wi8hVYOFYioC1hx/kNpXWEO1vvoXLfFFIjgkN/SRz+ +h7JiEYT5h86lgFseNKwyWhXCSRjqU0wgwioZXxk8lWJ4oHUaelSHDoRsC2IuYLuG +zk1yiQiaSMyde3zf7CiQ/zWEY7o5TD4I0jHoWE1igCnP7EKgznNK0OWA0kFmgrb/ +uv6yC/Mi8y0q8uM6SWrQZtnrqVP8ZWwd887l65gXrCzF+sERvG8rV3AEEn4H5x1W +32xBve9H9UT2vqvR0etwEGTdjn5hSXOOp4eL/CmSwhthEJR5UCmtFnix1N2zv0Yx +xRI5HCxcROTdWgqu2pSYr+WmflAMJHwLYnt90hoB5wa094moM+SM3dh3vRaIHw61 +D0VFFgbzePBN/aMIW8F1y/RWwsKsaaA42GVrJ9UrS2LakiYS3h/RAZWAl4JqAEH4 +5Zs0fIZ4oqgJiMWxbeXG3iHTM1NzXluBrxwVpN0bGhQz1q1kqXnlIwbNk6w8ORkq +l8BxFYasXFjiDu3ukrAFeE8qXX1+UdFh9JM08lpfPmjmzeoi95spiN5CzjJQ7Ije +NLs5T1wHloBVSo1DQNAyJ2+wDA2wR7xsji8TlcXayUD3V5427oTl8ReNrDrxyzSB +1V5JTwdnRI7vcFUDK/+Cy/TVAfn2e9BBOPKsnTQVkyX8T8j+FvnMDgCPaZmtPwID +AQABo2YwZDAdBgNVHQ4EFgQUrb+MoT88/AigoiWyKutB71e0Vg8wDgYDVR0PAQH/ +BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQIwHwYDVR0jBBgwFoAUeKB90BMprglp +ttMybtqk3NTWnkcwDQYJKoZIhvcNAQELBQADggIBAK81DMmXVogJ4zASBvjxrIrK +NTAp91bB6ji6SkH2RcBIfyA/rLCrbWhg9euU/tN+9a141WYn/wdZ8IAIcFVNac+m +eCxqC6BWbqesfIKOjuMIlkK1zE8sA36FlAxYZq3JHH31NwHdbN6xPBMP0/qBPWs4 +XuP0yIXOsVcm1elwBDPFztojOcXVZc0hpiwTAuiWSA4p4xbgyKaiubBYyy+KA41C +dpxooMSFwnD82G4yS9UskWOmUq/7yCxIaINZKjFgFFIl3HKrZ2AmoT8fBnIXtfdK +MgrpxpruBPM6pmEFm86tgwlOcZMjfo4/v5KMAUOBKL43+Cd/rAjMChTOdlFrC4RC +Qlpdqv1ylSlCR6y8T+iRT14X4N1SVEIhVq/CqWijUksk+rgXdW8t+Sqf4N5NrecU +AgUbVG8yoDQJx82zirtU1b0TL1dkZuWnJU8PMNm5sVabPmASnrtQmVedlnce2BET +6/Z/RenLDW4FsMitUxAPEjLa8Gw3dCIlrxlRBd//6j30SqcgrXPQ4bAeGHb7xcMR +3by+sIgtttcz10NP8yR4l/zO1FVBlMGB35jI6+e1DrVtRt+Mk9I75WkT0SJ3dC/0 +JWjz6++KJ8sXQqyveGdL/rnxf1d2nylGQj9Lcb41DdY8G8Q9hoN7QNGXvS19AYyk +SX52n8GLrS4ph8b+hDgX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFizCCA3OgAwIBAgIUXbdRB0FhfSY7Qm780j6jn4jGAK4wDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCVVMxEDAOBgNVBAcMB1Rlc3RpbmcxDDAKBgNVBAoMA0RF +VjEQMA4GA1UECwwHVGVzdGluZzETMBEGA1UEAwwKVGVzdGluZyBDQTAgFw0yNjA3 +MjgxMzAwMzVaGA8yMDUzMTIxMjEzMDAzNVowVDELMAkGA1UEBhMCVVMxEDAOBgNV +BAcMB1Rlc3RpbmcxDDAKBgNVBAoMA0RFVjEQMA4GA1UECwwHVGVzdGluZzETMBEG +A1UEAwwKVGVzdGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AMt5lS0A3u7vzNFymcUxz4+M15/z4jfWD3csIgdwKUPXnOshkD5t9jrytlBPO/sA +u9uSz0ZkLPYQivnVfopqobiwrpU0i1hR6WXsEqcAXsPyu5kIP12MMua+HD946RZK +xXE7MP5HtaC634Ki9GsVHHWKkVKn+ChHfP6NVm8tKPipAbMRfxzYKAzV0GAgszKM +MRTViW74NGG3+E6Lh8a88QwkvxC3gSIF7jYdCByCbn88EKpbzPc5asXzEB7XxxI4 +69886omw4G4HFaM4yS6LAc/GFixd4+XUGp6wyckHKt5yvh0pM8f0RkqUndhWREWG +MM0FS0LUnaM/SYUCtp5g4OrYoT59cfqJiXgU7bpR4vjA0NxihoypMmt/8l12Yecb +InGdp88hqPuQl5mUb43/tqWLnU7lqVlz2SifpY/dr8c8lHafY1++erfED19YhJkX +pC+wmInqVxIgoy+O/iu0eZn0Enj1Y4ZkqM1V+R05JQ9LxMjw1RATLy4tDECiJrQI +rr9P0dSdRpb9rWe42OUIrqsNVHtFJJ3iNwGuyC2qn6lFsbbP/smZzaufUUxVQMo3 +HiohNgLnXnLT8H9COmpZ9mPn4TixYUyaHxKhEtp/z3f1CUcZxbXdHiwzG0ua0Mji +TQnaj0MROwyYr46Wx0JSF7G5a/RTexHRd8izq3ZNb9+rAgMBAAGjUzBRMB0GA1Ud +DgQWBBR4oH3QEymuCWm20zJu2qTc1NaeRzAfBgNVHSMEGDAWgBR4oH3QEymuCWm2 +0zJu2qTc1NaeRzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAr +F63FL67wOBUaFsOJS3tjBC6T0LW6jJfdK1J+EPIaeMzfbYEfNrS1WvjHbDbucld7 +OasRv35j+1h3MlbH6H6SjLBqG0cAVoc5HxB9cQ83eWx6AQ3Qz1A20SQIQm+OsRNt +KF44H5mhijtgEbCSXqPBmIMhvuubbZ1cnMzmYX9kS71c0okDEQcamVnNX8J+3T+F +1PwIf94QPfy67jlcnBw3mvh46VmYh8RV52jGZiVBW4TWUDNEGlSfXei5r7zYNEDK +ukaDvKuKdLxPkfqslD3Ewls/kYqA2/82aFDBlWgIQGRH6P1GfWt0OVN1zt2lCR3K +yuO4rpbvz88A1gocDUGGsMF3lX4O429GD8eiJ5Iy3iDekf+fAKsE/C6QvNXZJ3PF +tjGVYw5EoD+lhRIKveuePWwhsOOiEq+0fVDtOl93/QKbT/mODt6eC2v6EBqNKWF6 +MMfAX4tGHsunfJXOu2P2rt+sroTAfzzfhs45mHlR1SRjS0uSH33oMFGPV3VIMNup +cQtyfbxM4JvxKiuJTQSE2KaxpMWz4GI/SIyOkgEQd7o7Gs6aagD1lcg6zcQTF9yI +hN4lyr+IhJcs6FOmY1oCKEm0d+zZyZ0C9UhZT0WtCC21ODIhIY1cIOop+gHrQNI5 +YnCZegw4WELm9/9HL3/UKjODvfJWroVn4k8hPB+wWg== +-----END CERTIFICATE-----""" + static final private String REFERRER_BYOPKI_MATCHING_ISSUER = "https://testing.org" + static final private String REFERRER_BYOPKI_MATCHING_IDENTITY = "team-a@testing.org" + + // Public key used to sign the referrer-based test images. + // Source: https://vault.bitwarden.com/#/vault?itemId=95313e19-de46-4533-b160-af620120452a. + static final private Map REFERRER_COSIGN_PUBLIC_KEY = [ + "Referrer": """\ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu8+OtJR+dVIDHk89zl8WnzcQskow +bfbvZqSDudXtfGwpKu6tZ1CtH9utaLkglXynrLNecl0QvrhuCuKA/htSwA== +-----END PUBLIC KEY----- +""", + ] + static final private String KEYLESS_SIGSTORE_ISSUER = "https://github.com/login/oauth" static final private String KEYLESS_SIGSTORE_IDENTITY = ".*@redhat.com" static final private String KEYLESS_SIGSTORE_REKOR_URL = "https://rekor.sigstore.dev" @@ -194,6 +283,14 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== "sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f" static final private String KEYLESS_RHTAS_IMAGE_DIGEST = "sha256:e246aa22ad2cbdfbd19e2a6ca2b275e26245a21920e2b2d0666324cee3f15549" + static final private String REFERRER_BYOPKI_IMAGE_DIGEST = + "sha256:05a79c7279f71f86a2a0d05eb72fcb56ea36139150f0a75cd87e80a4272e4e39" + static final private String REFERRER_PUBKEY_MATCHING_IMAGE_DIGEST = + "sha256:9e2bbca079387d7965c3a9cee6d0c53f4f4e63ff7637877a83c4c05f2a666112" + static final private String REFERRER_KEYLESS_RHTAS_MATCHING_IMAGE_DIGEST = + "sha256:e8e5cca392e3cf056fcdb3093e7ac2bf83fcf28b3bcf5818fe8ae71cf360c231" + static final private String REFERRER_KEYLESS_SIGSTORE_MATCHING_IMAGE_DIGEST = + "sha256:98ad9d1a2be345201bb0709b0d38655eb1b370145c7d94ca1fe9c421f76e245a" static final private List IMAGE_DIGESTS = [ DISTROLESS_IMAGE_DIGEST, @@ -205,6 +302,10 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== BYOPKI_IMAGE_DIGEST, KEYLESS_SIGSTORE_IMAGE_DIGEST, KEYLESS_RHTAS_IMAGE_DIGEST, + REFERRER_BYOPKI_IMAGE_DIGEST, + REFERRER_PUBKEY_MATCHING_IMAGE_DIGEST, + REFERRER_KEYLESS_RHTAS_MATCHING_IMAGE_DIGEST, + REFERRER_KEYLESS_SIGSTORE_MATCHING_IMAGE_DIGEST, ] // Deployment holding an image which has a cosign signature that is verifiable with the DISTROLESS_PUBLIC_KEY. @@ -294,6 +395,45 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== .setNamespace(SIGNATURE_TESTING_NAMESPACE) .setImagePrefetcherAffinity() + // Deployment holding an image with BYOPKI signature stored as an OCI referrer. + static final private Deployment REFERRER_BYOPKI_DEPLOYMENT = new Deployment() + .setName("referrer-byopki") + .setImage("quay.io/rhacs-eng/qa-signatures:referrer-byopki@$REFERRER_BYOPKI_IMAGE_DIGEST") + .addLabel("app", "image-with-referrer-byopki") + .setCommand(["sleep", "600"]) + .setNamespace(SIGNATURE_TESTING_NAMESPACE) + .setImagePrefetcherAffinity() + + // Deployment holding an image signed with a public key using the sigstore bundle format. + static final private Deployment REFERRER_PUBKEY_MATCHING_DEPLOYMENT = new Deployment() + .setName("referrer-pubkey") + .setImage("quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-pubkey" + + "@$REFERRER_PUBKEY_MATCHING_IMAGE_DIGEST") + .addLabel("app", "image-with-referrer-pubkey") + .setCommand(["sleep", "600"]) + .setNamespace(SIGNATURE_TESTING_NAMESPACE) + .setImagePrefetcherAffinity() + + // Deployment holding an image signed keyless via RHTAS using the sigstore bundle format. + static final private Deployment REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT = new Deployment() + .setName("referrer-rhtas-keyless") + .setImage("quay.io/rhacs-eng/qa-signatures:referrer-keyless-rhtas" + + "@$REFERRER_KEYLESS_RHTAS_MATCHING_IMAGE_DIGEST") + .addLabel("app", "image-with-referrer-keyless-rhtas") + .setCommand(["sleep", "600"]) + .setNamespace(SIGNATURE_TESTING_NAMESPACE) + .setImagePrefetcherAffinity() + + // Deployment holding an image signed keyless via public Sigstore using the sigstore bundle format. + static final private Deployment REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT = new Deployment() + .setName("referrer-sigstore-keyless") + .setImage("quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-keyless" + + "@$REFERRER_KEYLESS_SIGSTORE_MATCHING_IMAGE_DIGEST") + .addLabel("app", "image-with-referrer-sigstore-bundle-keyless") + .setCommand(["sleep", "600"]) + .setNamespace(SIGNATURE_TESTING_NAMESPACE) + .setImagePrefetcherAffinity() + // List of deployments used within the tests. This will be used during setup of the spec / teardown to create / // delete all deployments. // NOTE: If you add another deployment, make sure to add it here as well. @@ -307,6 +447,10 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== BYOPKI_DEPLOYMENT, KEYLESS_SIGSTORE_DEPLOYMENT, KEYLESS_RHTAS_DEPLOYMENT, + REFERRER_BYOPKI_DEPLOYMENT, + REFERRER_PUBKEY_MATCHING_DEPLOYMENT, + REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT, + REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT, ] // Base policy which will be used for creating subsequent policies that have signature integration IDs as values. @@ -490,6 +634,45 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== CREATED_SIGNATURE_INTEGRATIONS.put(KEYLESS_RHTAS_UNVERIFIABLE, keylessRHTASUnverifiableSignatureIntegrationID) + // Signature integration "Referrer-BYOPKI-Matching" which holds the referrer BYOPKI CA bundle + // with matching identity and issuer. + String referrerByopkiMatchingID = createSignatureIntegration( + REFERRER_BYOPKI_MATCHING, NO_PUBLIC_KEYS, + new CertificateVerificationArgs( + chain: REFERRER_BYOPKI_CA_BUNDLE, + identity: REFERRER_BYOPKI_MATCHING_IDENTITY, + issuer: REFERRER_BYOPKI_MATCHING_ISSUER, + ), + ) + assert referrerByopkiMatchingID + CREATED_SIGNATURE_INTEGRATIONS.put(REFERRER_BYOPKI_MATCHING, referrerByopkiMatchingID) + + // Referrer BYOPKI with non-matching identity and issuer. + String referrerByopkiUnverifiableID = createSignatureIntegration( + REFERRER_BYOPKI_UNVERIFIABLE, NO_PUBLIC_KEYS, + new CertificateVerificationArgs( + chain: REFERRER_BYOPKI_CA_BUNDLE, + identity: BYOPKI_UNVERIFIABLE_IDENTITY, + issuer: BYOPKI_UNVERIFIABLE_ISSUER, + ), + ) + assert referrerByopkiUnverifiableID + CREATED_SIGNATURE_INTEGRATIONS.put(REFERRER_BYOPKI_UNVERIFIABLE, referrerByopkiUnverifiableID) + + // Signature integration for referrer signed with a public key. + String referrerPubKeyMatchingID = createSignatureIntegration( + REFERRER_PUBKEY_MATCHING, REFERRER_COSIGN_PUBLIC_KEY, + ) + assert referrerPubKeyMatchingID + CREATED_SIGNATURE_INTEGRATIONS.put(REFERRER_PUBKEY_MATCHING, referrerPubKeyMatchingID) + + // Referrer with wrong public key. + String referrerPubKeyUnverifiableID = createSignatureIntegration( + REFERRER_PUBKEY_UNVERIFIABLE, UNVERIFIABLE_COSIGN_PUBLIC_KEY, + ) + assert referrerPubKeyUnverifiableID + CREATED_SIGNATURE_INTEGRATIONS.put(REFERRER_PUBKEY_UNVERIFIABLE, referrerPubKeyUnverifiableID) + // Create all required deployments. orchestrator.batchCreateDeployments(DEPLOYMENTS) DEPLOYMENTS.each { assert Services.waitForDeployment(it) } @@ -563,151 +746,259 @@ l4RLgOfB7JK26P2QEGpNzOKYHxWIV+oBSrrG1/2q1LS6WJYqhlM2+tLK9g== } where: - policyName | deployment | expectViolations - // Distroless should create alerts for all deployments except those using distroless images. - DISTROLESS | BYOPKI_DEPLOYMENT | true - DISTROLESS | DISTROLESS_DEPLOYMENT | false - DISTROLESS | KEYLESS_RHTAS_DEPLOYMENT | true - DISTROLESS | KEYLESS_SIGSTORE_DEPLOYMENT | true - DISTROLESS | SAME_DIGEST_NO_SIGNATURE | true - DISTROLESS | SAME_DIGEST_WITH_SIGNATURE | true - DISTROLESS | TEKTON_DEPLOYMENT | true - DISTROLESS | UNVERIFIABLE_DEPLOYMENT | true - DISTROLESS | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Tekton should create alerts for all deployments except those using tekton images. - TEKTON | BYOPKI_DEPLOYMENT | true - TEKTON | DISTROLESS_DEPLOYMENT | true - TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true - TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true - TEKTON | SAME_DIGEST_NO_SIGNATURE | true - TEKTON | SAME_DIGEST_WITH_SIGNATURE | true - TEKTON | TEKTON_DEPLOYMENT | false - TEKTON | UNVERIFIABLE_DEPLOYMENT | true - TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Unverifiable should create alerts for all deployments. - UNVERIFIABLE | BYOPKI_DEPLOYMENT | true - UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true - UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true - UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true - UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true - UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true - UNVERIFIABLE | TEKTON_DEPLOYMENT | true - UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true - UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Distroless and tekton should create alerts for all deployments except those using distroless / tekton images. - DISTROLESS_AND_TEKTON | BYOPKI_DEPLOYMENT | true - DISTROLESS_AND_TEKTON | DISTROLESS_DEPLOYMENT | false - DISTROLESS_AND_TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true - DISTROLESS_AND_TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true - DISTROLESS_AND_TEKTON | SAME_DIGEST_NO_SIGNATURE | true - DISTROLESS_AND_TEKTON | SAME_DIGEST_WITH_SIGNATURE | true - DISTROLESS_AND_TEKTON | TEKTON_DEPLOYMENT | false - DISTROLESS_AND_TEKTON | UNVERIFIABLE_DEPLOYMENT | true - DISTROLESS_AND_TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Policy with all three integrations should create alerts for all deployments except those using distroless / - // tekton images. - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | false - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | TEKTON_DEPLOYMENT | false - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true - POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Same digest should create alerts for all deployments except those using alt-nginx image. - SAME_DIGEST | BYOPKI_DEPLOYMENT | true - SAME_DIGEST | DISTROLESS_DEPLOYMENT | true - SAME_DIGEST | KEYLESS_RHTAS_DEPLOYMENT | true - SAME_DIGEST | KEYLESS_SIGSTORE_DEPLOYMENT | true - SAME_DIGEST | SAME_DIGEST_NO_SIGNATURE | true - SAME_DIGEST | SAME_DIGEST_WITH_SIGNATURE | false - SAME_DIGEST | TEKTON_DEPLOYMENT | true - SAME_DIGEST | UNVERIFIABLE_DEPLOYMENT | true - SAME_DIGEST | WITHOUT_SIGNATURE_DEPLOYMENT | true - // BYOPKI wildcard should create alerts for all deployments except the BYOPKI deployment one. - BYOPKI_WILDCARD | BYOPKI_DEPLOYMENT | false - BYOPKI_WILDCARD | DISTROLESS_DEPLOYMENT | true - BYOPKI_WILDCARD | KEYLESS_RHTAS_DEPLOYMENT | true - BYOPKI_WILDCARD | KEYLESS_SIGSTORE_DEPLOYMENT | true - BYOPKI_WILDCARD | SAME_DIGEST_NO_SIGNATURE | true - BYOPKI_WILDCARD | SAME_DIGEST_WITH_SIGNATURE | true - BYOPKI_WILDCARD | TEKTON_DEPLOYMENT | true - BYOPKI_WILDCARD | UNVERIFIABLE_DEPLOYMENT | true - BYOPKI_WILDCARD | WITHOUT_SIGNATURE_DEPLOYMENT | true + policyName | deployment | expectViolations // BYOPKI matching should create alerts for all deployments except the BYOPKI deployment one. - BYOPKI_MATCHING | BYOPKI_DEPLOYMENT | false - BYOPKI_MATCHING | DISTROLESS_DEPLOYMENT | true - BYOPKI_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true - BYOPKI_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true - BYOPKI_MATCHING | SAME_DIGEST_NO_SIGNATURE | true - BYOPKI_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true - BYOPKI_MATCHING | TEKTON_DEPLOYMENT | true - BYOPKI_MATCHING | UNVERIFIABLE_DEPLOYMENT | true - BYOPKI_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true + BYOPKI_MATCHING | BYOPKI_DEPLOYMENT | false + BYOPKI_MATCHING | DISTROLESS_DEPLOYMENT | true + BYOPKI_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true + BYOPKI_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true + BYOPKI_MATCHING | REFERRER_BYOPKI_DEPLOYMENT | true + BYOPKI_MATCHING | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + BYOPKI_MATCHING | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + BYOPKI_MATCHING | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + BYOPKI_MATCHING | SAME_DIGEST_NO_SIGNATURE | true + BYOPKI_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true + BYOPKI_MATCHING | TEKTON_DEPLOYMENT | true + BYOPKI_MATCHING | UNVERIFIABLE_DEPLOYMENT | true + BYOPKI_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true // BYOPKI unverifiable should create alerts for all deployments. - BYOPKI_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true - BYOPKI_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true - BYOPKI_UNVERIFIABLE | TEKTON_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true - BYOPKI_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true - // BYOPKI wildcard + Tekton should create alerts for all deployments except the - // BYOPKI one and the Tekton one. - BYOPKI_WILDCARD_AND_TEKTON | BYOPKI_DEPLOYMENT | false - BYOPKI_WILDCARD_AND_TEKTON | DISTROLESS_DEPLOYMENT | true - BYOPKI_WILDCARD_AND_TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true - BYOPKI_WILDCARD_AND_TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true - BYOPKI_WILDCARD_AND_TEKTON | SAME_DIGEST_NO_SIGNATURE | true - BYOPKI_WILDCARD_AND_TEKTON | SAME_DIGEST_WITH_SIGNATURE | true - BYOPKI_WILDCARD_AND_TEKTON | TEKTON_DEPLOYMENT | false - BYOPKI_WILDCARD_AND_TEKTON | UNVERIFIABLE_DEPLOYMENT | true - BYOPKI_WILDCARD_AND_TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Keyless Sigstore matching should create alerts for all deployments except the - // one running the keyless Sigstore signed image. - KEYLESS_SIGSTORE_MATCHING | BYOPKI_DEPLOYMENT | true - KEYLESS_SIGSTORE_MATCHING | DISTROLESS_DEPLOYMENT | true - KEYLESS_SIGSTORE_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true - KEYLESS_SIGSTORE_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | false - KEYLESS_SIGSTORE_MATCHING | SAME_DIGEST_NO_SIGNATURE | true - KEYLESS_SIGSTORE_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true - KEYLESS_SIGSTORE_MATCHING | TEKTON_DEPLOYMENT | true - KEYLESS_SIGSTORE_MATCHING | UNVERIFIABLE_DEPLOYMENT | true - KEYLESS_SIGSTORE_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Keyless Sigstore unverifiable should create alerts for all deployments. - KEYLESS_SIGSTORE_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true - KEYLESS_SIGSTORE_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true - KEYLESS_SIGSTORE_UNVERIFIABLE | TEKTON_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true - KEYLESS_SIGSTORE_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true - // Keyless RHTAS matching should create alerts for all deployments except the - // one running the keyless RHTAS signed image. - KEYLESS_RHTAS_MATCHING | BYOPKI_DEPLOYMENT | true - KEYLESS_RHTAS_MATCHING | DISTROLESS_DEPLOYMENT | true - KEYLESS_RHTAS_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | false - KEYLESS_RHTAS_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true - KEYLESS_RHTAS_MATCHING | SAME_DIGEST_NO_SIGNATURE | true - KEYLESS_RHTAS_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true - KEYLESS_RHTAS_MATCHING | TEKTON_DEPLOYMENT | true - KEYLESS_RHTAS_MATCHING | UNVERIFIABLE_DEPLOYMENT | true - KEYLESS_RHTAS_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + BYOPKI_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + BYOPKI_UNVERIFIABLE | TEKTON_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + BYOPKI_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // BYOPKI wildcard should create alerts for all deployments except the BYOPKI deployment one. + BYOPKI_WILDCARD | BYOPKI_DEPLOYMENT | false + BYOPKI_WILDCARD | DISTROLESS_DEPLOYMENT | true + BYOPKI_WILDCARD | KEYLESS_RHTAS_DEPLOYMENT | true + BYOPKI_WILDCARD | KEYLESS_SIGSTORE_DEPLOYMENT | true + BYOPKI_WILDCARD | REFERRER_BYOPKI_DEPLOYMENT | true + BYOPKI_WILDCARD | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD | SAME_DIGEST_NO_SIGNATURE | true + BYOPKI_WILDCARD | SAME_DIGEST_WITH_SIGNATURE | true + BYOPKI_WILDCARD | TEKTON_DEPLOYMENT | true + BYOPKI_WILDCARD | UNVERIFIABLE_DEPLOYMENT | true + BYOPKI_WILDCARD | WITHOUT_SIGNATURE_DEPLOYMENT | true + // BYOPKI wildcard + Tekton should create alerts for all deployments except the BYOPKI one and the Tekton one. + BYOPKI_WILDCARD_AND_TEKTON | BYOPKI_DEPLOYMENT | false + BYOPKI_WILDCARD_AND_TEKTON | DISTROLESS_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | REFERRER_BYOPKI_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | SAME_DIGEST_NO_SIGNATURE | true + BYOPKI_WILDCARD_AND_TEKTON | SAME_DIGEST_WITH_SIGNATURE | true + BYOPKI_WILDCARD_AND_TEKTON | TEKTON_DEPLOYMENT | false + BYOPKI_WILDCARD_AND_TEKTON | UNVERIFIABLE_DEPLOYMENT | true + BYOPKI_WILDCARD_AND_TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Distroless should create alerts for all deployments except those using distroless images. + DISTROLESS | BYOPKI_DEPLOYMENT | true + DISTROLESS | DISTROLESS_DEPLOYMENT | false + DISTROLESS | KEYLESS_RHTAS_DEPLOYMENT | true + DISTROLESS | KEYLESS_SIGSTORE_DEPLOYMENT | true + DISTROLESS | REFERRER_BYOPKI_DEPLOYMENT | true + DISTROLESS | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + DISTROLESS | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + DISTROLESS | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + DISTROLESS | SAME_DIGEST_NO_SIGNATURE | true + DISTROLESS | SAME_DIGEST_WITH_SIGNATURE | true + DISTROLESS | TEKTON_DEPLOYMENT | true + DISTROLESS | UNVERIFIABLE_DEPLOYMENT | true + DISTROLESS | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Distroless and tekton should create alerts for all deployments except those using distroless / tekton images. + DISTROLESS_AND_TEKTON | BYOPKI_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | DISTROLESS_DEPLOYMENT | false + DISTROLESS_AND_TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | REFERRER_BYOPKI_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | SAME_DIGEST_NO_SIGNATURE | true + DISTROLESS_AND_TEKTON | SAME_DIGEST_WITH_SIGNATURE | true + DISTROLESS_AND_TEKTON | TEKTON_DEPLOYMENT | false + DISTROLESS_AND_TEKTON | UNVERIFIABLE_DEPLOYMENT | true + DISTROLESS_AND_TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Keyless RHTAS matching should create alerts for all deployments except those running a keyless RHTAS signed image. + KEYLESS_RHTAS_MATCHING | BYOPKI_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | DISTROLESS_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | false + KEYLESS_RHTAS_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | REFERRER_BYOPKI_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | false + KEYLESS_RHTAS_MATCHING | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | SAME_DIGEST_NO_SIGNATURE | true + KEYLESS_RHTAS_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true + KEYLESS_RHTAS_MATCHING | TEKTON_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | UNVERIFIABLE_DEPLOYMENT | true + KEYLESS_RHTAS_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true // Keyless RHTAS unverifiable should create alerts for all deployments. - KEYLESS_RHTAS_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true - KEYLESS_RHTAS_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true - KEYLESS_RHTAS_UNVERIFIABLE | TEKTON_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true - KEYLESS_RHTAS_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + KEYLESS_RHTAS_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + KEYLESS_RHTAS_UNVERIFIABLE | TEKTON_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + KEYLESS_RHTAS_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Keyless Sigstore matching should create alerts for all deployments except those running a keyless Sigstore signed image. + KEYLESS_SIGSTORE_MATCHING | BYOPKI_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | DISTROLESS_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | false + KEYLESS_SIGSTORE_MATCHING | REFERRER_BYOPKI_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | false + KEYLESS_SIGSTORE_MATCHING | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | SAME_DIGEST_NO_SIGNATURE | true + KEYLESS_SIGSTORE_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true + KEYLESS_SIGSTORE_MATCHING | TEKTON_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | UNVERIFIABLE_DEPLOYMENT | true + KEYLESS_SIGSTORE_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Keyless Sigstore unverifiable should create alerts for all deployments. + KEYLESS_SIGSTORE_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + KEYLESS_SIGSTORE_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + KEYLESS_SIGSTORE_UNVERIFIABLE | TEKTON_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + KEYLESS_SIGSTORE_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Policy with all three integrations should create alerts for all deployments except those using distroless / tekton images. + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | false + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | TEKTON_DEPLOYMENT | false + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + POLICY_WITH_DISTROLESS_TEKTON_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Referrer BYOPKI matching: only the referrer BYOPKI deployment passes. + REFERRER_BYOPKI_MATCHING | BYOPKI_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | DISTROLESS_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | REFERRER_BYOPKI_DEPLOYMENT | false + REFERRER_BYOPKI_MATCHING | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | SAME_DIGEST_NO_SIGNATURE | true + REFERRER_BYOPKI_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true + REFERRER_BYOPKI_MATCHING | TEKTON_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | UNVERIFIABLE_DEPLOYMENT | true + REFERRER_BYOPKI_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Referrer BYOPKI unverifiable: wrong identity/issuer, all deployments alert. + REFERRER_BYOPKI_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + REFERRER_BYOPKI_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + REFERRER_BYOPKI_UNVERIFIABLE | TEKTON_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + REFERRER_BYOPKI_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Referrer with public key: only the matching deployment passes. + REFERRER_PUBKEY_MATCHING | BYOPKI_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | DISTROLESS_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | KEYLESS_RHTAS_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | KEYLESS_SIGSTORE_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | REFERRER_BYOPKI_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | false + REFERRER_PUBKEY_MATCHING | SAME_DIGEST_NO_SIGNATURE | true + REFERRER_PUBKEY_MATCHING | SAME_DIGEST_WITH_SIGNATURE | true + REFERRER_PUBKEY_MATCHING | TEKTON_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | UNVERIFIABLE_DEPLOYMENT | true + REFERRER_PUBKEY_MATCHING | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Referrer with wrong public key: all deployments alert. + REFERRER_PUBKEY_UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + REFERRER_PUBKEY_UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + REFERRER_PUBKEY_UNVERIFIABLE | TEKTON_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + REFERRER_PUBKEY_UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Same digest should create alerts for all deployments except those using alt-nginx image. + SAME_DIGEST | BYOPKI_DEPLOYMENT | true + SAME_DIGEST | DISTROLESS_DEPLOYMENT | true + SAME_DIGEST | KEYLESS_RHTAS_DEPLOYMENT | true + SAME_DIGEST | KEYLESS_SIGSTORE_DEPLOYMENT | true + SAME_DIGEST | REFERRER_BYOPKI_DEPLOYMENT | true + SAME_DIGEST | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + SAME_DIGEST | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + SAME_DIGEST | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + SAME_DIGEST | SAME_DIGEST_NO_SIGNATURE | true + SAME_DIGEST | SAME_DIGEST_WITH_SIGNATURE | false + SAME_DIGEST | TEKTON_DEPLOYMENT | true + SAME_DIGEST | UNVERIFIABLE_DEPLOYMENT | true + SAME_DIGEST | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Tekton should create alerts for all deployments except those using tekton images. + TEKTON | BYOPKI_DEPLOYMENT | true + TEKTON | DISTROLESS_DEPLOYMENT | true + TEKTON | KEYLESS_RHTAS_DEPLOYMENT | true + TEKTON | KEYLESS_SIGSTORE_DEPLOYMENT | true + TEKTON | REFERRER_BYOPKI_DEPLOYMENT | true + TEKTON | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + TEKTON | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + TEKTON | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + TEKTON | SAME_DIGEST_NO_SIGNATURE | true + TEKTON | SAME_DIGEST_WITH_SIGNATURE | true + TEKTON | TEKTON_DEPLOYMENT | false + TEKTON | UNVERIFIABLE_DEPLOYMENT | true + TEKTON | WITHOUT_SIGNATURE_DEPLOYMENT | true + // Unverifiable should create alerts for all deployments. + UNVERIFIABLE | BYOPKI_DEPLOYMENT | true + UNVERIFIABLE | DISTROLESS_DEPLOYMENT | true + UNVERIFIABLE | KEYLESS_RHTAS_DEPLOYMENT | true + UNVERIFIABLE | KEYLESS_SIGSTORE_DEPLOYMENT | true + UNVERIFIABLE | REFERRER_BYOPKI_DEPLOYMENT | true + UNVERIFIABLE | REFERRER_KEYLESS_RHTAS_MATCHING_DEPLOYMENT | true + UNVERIFIABLE | REFERRER_KEYLESS_SIGSTORE_MATCHING_DEPLOYMENT | true + UNVERIFIABLE | REFERRER_PUBKEY_MATCHING_DEPLOYMENT | true + UNVERIFIABLE | SAME_DIGEST_NO_SIGNATURE | true + UNVERIFIABLE | SAME_DIGEST_WITH_SIGNATURE | true + UNVERIFIABLE | TEKTON_DEPLOYMENT | true + UNVERIFIABLE | UNVERIFIABLE_DEPLOYMENT | true + UNVERIFIABLE | WITHOUT_SIGNATURE_DEPLOYMENT | true } // Helper which creates a policy builder for a policy which uses the image signature policy criteria. diff --git a/qa-tests-backend/src/test/groovy/testdata/referrer-test-images.md b/qa-tests-backend/src/test/groovy/testdata/referrer-test-images.md new file mode 100644 index 0000000000000..e10cc3de6b2ca --- /dev/null +++ b/qa-tests-backend/src/test/groovy/testdata/referrer-test-images.md @@ -0,0 +1,161 @@ +# Referrer-based Signature Test Images + +Instructions for creating test images with OCI 1.1 referrer-based sigstore +bundle signatures for `ImageSignatureVerificationTest`. + +## Prerequisites + +```bash +cosign version # requires v3+ for --new-bundle-format +crane version # for copying base images +``` + +## Generate a key pair + +```bash +cosign generate-key-pair +# Produces cosign.key (private) and cosign.pub (public). +# Store both in Bitwarden alongside existing signature test credentials. +``` + +## 1. referrer-sigstore-bundle-pubkey + +Sigstore bundle format, public key, attached as OCI referrer. + +```bash +crane cp docker.io/library/alpine:latest \ + quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-pubkey + +DIGEST=$(crane digest quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-pubkey) + +cosign sign \ + --key cosign.key \ + --new-bundle-format \ + "quay.io/rhacs-eng/qa-signatures@$DIGEST" +``` + +## 2. referrer-sigstore-bundle-keyless + +Sigstore bundle format, keyless (public Sigstore), attached as OCI referrer. +Sign with an `@redhat.com` GitHub identity to match the test's OIDC config. + +```bash +crane cp docker.io/library/alpine:latest \ + quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-keyless + +DIGEST=$(crane digest quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-keyless) + +cosign sign \ + --new-bundle-format \ + "quay.io/rhacs-eng/qa-signatures@$DIGEST" +``` + +## 3. referrer-keyless-rhtas + +Sigstore bundle format, keyless via the private RHTAS instance on the testing +services cluster, attached as OCI referrer. Sign with an `@redhat.com` Google +identity to match the test's OIDC config. + +Cosign's default OIDC flow uses a Sigstore-hosted OAuth client that only +supports GitHub and Microsoft identities. To sign with a Google (`@redhat.com`) +identity, create a GCP OAuth 2.0 client (type "Desktop app") and pass its +credentials to cosign. The RHTAS TUF URL is derived from the cluster's route +(see `KEYLESS_RHTAS_REKOR_URL` in the test file for the cluster base URL). + +```bash +# Point cosign at the private RHTAS TUF root to use its Fulcio and Rekor. +TUF_URL= +cosign initialize --mirror=$TUF_URL --root=$TUF_URL/root.json + +crane cp docker.io/library/alpine:latest \ + quay.io/rhacs-eng/qa-signatures:referrer-keyless-rhtas + +DIGEST=$(crane digest quay.io/rhacs-eng/qa-signatures:referrer-keyless-rhtas) + +cosign sign --yes \ + --oidc-client-id="" \ + --oidc-client-secret-file=$HOME/oidc-client-secret \ + "quay.io/rhacs-eng/qa-signatures@$DIGEST" +``` + +## 4. referrer-byopki + +Sigstore bundle format, BYOPKI (bring-your-own-PKI) certificate chain, attached +as OCI referrer. The signing certificate must carry OIDC extensions for issuer +(`https://testing.org`) and identity (`team-a@testing.org`). + +### Generate the CA hierarchy + +```bash +# Root CA. +openssl req -x509 -newkey rsa:4096 -keyout root-ca.key -out root-ca.pem \ + -days 10000 -nodes -subj "/C=US/L=Testing/O=DEV/OU=Testing/CN=Testing CA" + +# Intermediate CA. +openssl req -newkey rsa:4096 -keyout intermediate-ca.key -out intermediate-ca.csr \ + -nodes -subj "/C=US/L=Testing/O=DEV/OU=Testing/CN=Testing Intermediate CA" +openssl x509 -req -in intermediate-ca.csr -CA root-ca.pem -CAkey root-ca.key \ + -CAcreateserial -out intermediate-ca.pem -days 10000 \ + -extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:2\nkeyUsage=critical,keyCertSign") + +# Leaf signing key and certificate with OIDC extensions. +openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -keyout leaf.key -out leaf.csr -nodes \ + -subj "/C=US/L=Testing/O=DEV/OU=Testing/CN=team-a@testing.org" +openssl x509 -req -in leaf.csr -CA intermediate-ca.pem -CAkey intermediate-ca.key \ + -CAcreateserial -out leaf.pem -days 10000 \ + -extfile <(printf "1.3.6.1.4.1.57264.1.1=ASN1:UTF8String:https://testing.org\n1.3.6.1.4.1.57264.1.7=ASN1:UTF8String:team-a@testing.org") + +# CA bundle (intermediate + root, order matters). +cat intermediate-ca.pem root-ca.pem > ca-bundle.pem +``` + +### Sign the image + +```bash +crane cp docker.io/library/alpine:latest \ + quay.io/rhacs-eng/qa-signatures:referrer-byopki + +DIGEST=$(crane digest quay.io/rhacs-eng/qa-signatures:referrer-byopki) + +cosign sign \ + --key leaf.key \ + --certificate leaf.pem \ + --certificate-chain ca-bundle.pem \ + --new-bundle-format \ + "quay.io/rhacs-eng/qa-signatures@$DIGEST" +``` + +## After signing + +Update `ImageSignatureVerificationTest.groovy` with: +- `REFERRER_COSIGN_PUBLIC_KEY`: contents of `cosign.pub` +- `REFERRER_PUBKEY_MATCHING_IMAGE_DIGEST`: digest from step 1 +- `REFERRER_KEYLESS_SIGSTORE_MATCHING_IMAGE_DIGEST`: digest from step 2 +- `REFERRER_KEYLESS_RHTAS_MATCHING_IMAGE_DIGEST`: digest from step 3 +- `REFERRER_BYOPKI_IMAGE_DIGEST`: digest from step 4 +- `REFERRER_BYOPKI_CA_BUNDLE`: contents of `ca-bundle.pem` + +## Verification + +```bash +cosign verify --key cosign.pub --new-bundle-format \ + quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-pubkey + +cosign verify --new-bundle-format \ + --certificate-identity-regexp=".*@redhat.com" \ + --certificate-oidc-issuer="https://github.com/login/oauth" \ + quay.io/rhacs-eng/qa-signatures:referrer-sigstore-bundle-keyless + +# Requires prior: cosign initialize --mirror=$TUF_URL --root=$TUF_URL/root.json +cosign verify --new-bundle-format \ + --certificate-identity-regexp=".*@redhat.com" \ + --certificate-oidc-issuer="https://accounts.google.com" \ + quay.io/rhacs-eng/qa-signatures:referrer-keyless-rhtas + +cosign verify --new-bundle-format \ + --certificate-identity="team-a@testing.org" \ + --certificate-oidc-issuer="https://testing.org" \ + --cert-chain=ca-bundle.pem \ + quay.io/rhacs-eng/qa-signatures:referrer-byopki +``` From eb6549093da9b8448846c5e4599948427389f854 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:07:50 -0500 Subject: [PATCH 094/638] ROX-35122: Add filters to Virtual Machines overview page (#21714) --- .../attributes/virtualMachine.ts | 40 +++++++ ...ble.tsx => VirtualMachineCvesCveTable.tsx} | 55 +++++---- .../VirtualMachineCvesFilterToolbar.tsx | 43 +++++++ .../VirtualMachineCvesOverviewPage.tsx | 68 ++++++------ .../VirtualMachineCvesToggleGroup.tsx | 42 +++++++ ...able.tsx => VirtualMachineCvesVmTable.tsx} | 105 ++++++++---------- .../Vulnerabilities/searchFilterConfig.ts | 28 ++++- .../src/services/VirtualMachineService.ts | 19 +++- ui/apps/platform/src/types/searchOptions.ts | 2 + ui/apps/platform/src/utils/searchUtils.ts | 2 + 10 files changed, 285 insertions(+), 119 deletions(-) rename ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/{VirtualMachineCVEsTable.tsx => VirtualMachineCvesCveTable.tsx} (79%) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesFilterToolbar.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx rename ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/{VirtualMachinesCvesTable.tsx => VirtualMachineCvesVmTable.tsx} (78%) diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/virtualMachine.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/virtualMachine.ts index fb9fb5d41c24b..92d5f5023642c 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/virtualMachine.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/virtualMachine.ts @@ -42,9 +42,49 @@ export const VirtualMachineID: CompoundSearchFilterAttribute = { inputType: 'text', }; +export const VirtualMachineGuestOs: CompoundSearchFilterAttribute = { + displayName: 'Guest OS', + filterChipLabel: 'Virtual machine guest OS', + searchTerm: 'Guest OS', + inputType: 'text', +}; + export const VirtualMachineName: CompoundSearchFilterAttribute = { displayName: 'Name', filterChipLabel: 'Virtual machine name', searchTerm: 'Virtual Machine Name', inputType: 'text', }; + +export const VirtualMachineScanTime: CompoundSearchFilterAttribute = { + displayName: 'Scan time', + filterChipLabel: 'Virtual machine scan time', + searchTerm: 'Virtual Machine Scan Time', + inputType: 'date-picker', +}; + +export const VirtualMachineCVECvss: CompoundSearchFilterAttribute = { + displayName: 'CVSS', + filterChipLabel: 'CVE CVSS', + searchTerm: 'CVSS', + inputType: 'condition-number', +}; + +export const VirtualMachineCVEDiscoveredTime: CompoundSearchFilterAttribute = { + displayName: 'Discovered time', + filterChipLabel: 'CVE discovered time', + searchTerm: 'CVE Created Time', + inputType: 'date-picker', +}; + +export const virtualMachineAttributes = [ + VirtualMachineCVECvss, + VirtualMachineCVEDiscoveredTime, + VirtualMachineCVEName, + VirtualMachineComponentName, + VirtualMachineComponentVersion, + VirtualMachineGuestOs, + VirtualMachineID, + VirtualMachineName, + VirtualMachineScanTime, +]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCVEsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx similarity index 79% rename from ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCVEsTable.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx index a14668f04b6c6..3b094e82c1605 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCVEsTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx @@ -1,55 +1,67 @@ import { useCallback } from 'react'; import { Link } from 'react-router-dom-v5-compat'; -import { Flex, Pagination } from '@patternfly/react-core'; import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import CvssFormatted from 'Components/CvssFormatted'; import DateDistance from 'Components/DateDistance'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import type { UseURLPaginationResult } from 'hooks/useURLPagination'; import useRestQuery from 'hooks/useRestQuery'; -import useURLPagination from 'hooks/useURLPagination'; import { listVMCVEs } from 'services/VirtualMachineService'; +import type { SearchFilter } from 'types/search'; import { getTableUIState } from 'utils/getTableUIState'; import SeverityCountLabels from '../../components/SeverityCountLabels'; +import TableEntityToolbar from '../../components/TableEntityToolbar'; import { getVirtualMachineEntityPagePath } from '../../utils/searchUtils'; -import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; import { formatEpssProbabilityAsPercent } from '../../WorkloadCves/Tables/table.utils'; +import VirtualMachineCvesFilterToolbar from './VirtualMachineCvesFilterToolbar'; +import VirtualMachineCvesToggleGroup from './VirtualMachineCvesToggleGroup'; -function VirtualMachineCVEsTable() { - const { page, perPage, setPage, setPerPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); +type VirtualMachineCvesCveTableProps = { + searchFilter: SearchFilter; + pagination: UseURLPaginationResult; + isFiltered: boolean; + onClearFilters: () => void; +}; + +function VirtualMachineCvesCveTable({ + searchFilter, + pagination, + isFiltered, + onClearFilters, +}: VirtualMachineCvesCveTableProps) { + const { page, perPage } = pagination; const fetchVirtualMachineCVEs = useCallback( - () => listVMCVEs({ page, perPage }), - [page, perPage] + () => listVMCVEs({ searchFilter, page, perPage }), + [searchFilter, page, perPage] ); const { data, isLoading, error } = useRestQuery(fetchVirtualMachineCVEs); + const totalCount = data?.totalCount ?? 0; + const tableState = getTableUIState({ isLoading, data: data?.cves ?? [], error, - searchFilter: {}, + searchFilter, }); return ( <> - - setPage(newPage)} - onPerPageSelect={(_, newPerPage) => { - setPerPage(newPerPage); - }} - /> - + } + entityToggleGroup={} + pagination={pagination} + tableRowCount={totalCount} + isFiltered={isFiltered} + /> @@ -68,6 +80,7 @@ function VirtualMachineCVEsTable() { message: 'No CVEs have been detected for virtual machines across your secured clusters', }} + filteredEmptyProps={{ onClearFilters }} renderer={({ data }) => ( {data.map((virtualMachineCve) => { @@ -124,4 +137,4 @@ function VirtualMachineCVEsTable() { ); } -export default VirtualMachineCVEsTable; +export default VirtualMachineCvesCveTable; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesFilterToolbar.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesFilterToolbar.tsx new file mode 100644 index 0000000000000..5d861a6256bda --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesFilterToolbar.tsx @@ -0,0 +1,43 @@ +import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; + +import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; +import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; +import { + virtualMachineCVESearchFilterConfig, + virtualMachineComponentSearchFilterConfig, + virtualMachinesClusterSearchFilterConfig, + virtualMachinesNamespaceSearchFilterConfig, + virtualMachinesSearchFilterConfig, +} from '../../searchFilterConfig'; + +const searchFilterConfig = [ + virtualMachinesClusterSearchFilterConfig, + virtualMachineCVESearchFilterConfig, + virtualMachinesNamespaceSearchFilterConfig, + virtualMachinesSearchFilterConfig, + virtualMachineComponentSearchFilterConfig, +]; + +// Shared filter toolbar for both overview tables, which each render their own toolbar. +// Factored out to make sure the two tables stay in sync. State is read from the URL, not props. +function VirtualMachineCvesFilterToolbar() { + const { searchFilter, setSearchFilter } = useURLSearch(); + const { setPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); + + return ( + { + setSearchFilter(newFilter); + setPage(1, 'replace'); + }} + /> + ); +} + +export default VirtualMachineCvesFilterToolbar; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx index 46ba07fc89d27..1007dbbe943f9 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx @@ -1,31 +1,36 @@ -import { - Content, - Flex, - PageSection, - Title, - ToggleGroup, - ToggleGroupItem, -} from '@patternfly/react-core'; +import { Content, Flex, PageSection, Title } from '@patternfly/react-core'; import PageTitle from 'Components/PageTitle'; import TechnologyPreviewLabel from 'Components/PatternFly/PreviewLabel/TechnologyPreviewLabel'; +import { useManagedColumns } from 'hooks/useManagedColumns'; import useFeatureFlags from 'hooks/useFeatureFlags'; +import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; import useURLStringUnion from 'hooks/useURLStringUnion'; +import { getHasSearchApplied } from 'utils/searchUtils'; +import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; import { virtualMachineEntityTabValues } from '../../types'; import VirtualMachineScanScopeAlert from '../components/VirtualMachineScanScopeAlert'; -import VirtualMachineCVEsTable from './VirtualMachineCVEsTable'; -import VirtualMachinesCvesTable from './VirtualMachinesCvesTable'; +import VirtualMachineCvesCveTable from './VirtualMachineCvesCveTable'; +import VirtualMachineCvesVmTable, { defaultColumns } from './VirtualMachineCvesVmTable'; function VirtualMachineCvesOverviewPage() { const { isFeatureFlagEnabled } = useFeatureFlags(); const isEnhancedDataModelEnabled = isFeatureFlagEnabled( 'ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL' ); - const [activeEntityTabKey, setActiveEntityTabKey] = useURLStringUnion( - 'entityTab', - virtualMachineEntityTabValues - ); + const [activeEntityTabKey] = useURLStringUnion('entityTab', virtualMachineEntityTabValues); + const { searchFilter, setSearchFilter } = useURLSearch(); + const pagination = useURLPagination(DEFAULT_VM_PAGE_SIZE); + const managedColumnState = useManagedColumns('VirtualMachinesCvesTable', defaultColumns); + + const isFiltered = getHasSearchApplied(searchFilter); + + function onClearFilters() { + setSearchFilter({}); + pagination.setPage(1); + } return ( <> @@ -41,29 +46,26 @@ function VirtualMachineCvesOverviewPage() { - + - {isEnhancedDataModelEnabled && ( - - setActiveEntityTabKey('CVE')} - /> - setActiveEntityTabKey('VirtualMachine')} - /> - - )} + + {isEnhancedDataModelEnabled && activeEntityTabKey === 'CVE' && ( - + )} {(!isEnhancedDataModelEnabled || activeEntityTabKey === 'VirtualMachine') && ( - + )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx new file mode 100644 index 0000000000000..994a908879d0d --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx @@ -0,0 +1,42 @@ +import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core'; + +import useURLPagination from 'hooks/useURLPagination'; +import useURLStringUnion from 'hooks/useURLStringUnion'; + +import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; +import { virtualMachineEntityTabValues } from '../../types'; + +// Shared entity toggle for both overview tables, which each render their own toolbar. +// Factored out to make sure the two tables stay in sync. State is read from the URL, not props. +function VirtualMachineCvesToggleGroup() { + const [activeEntityTabKey, setActiveEntityTabKey] = useURLStringUnion( + 'entityTab', + virtualMachineEntityTabValues + ); + const { setPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); + + return ( + + { + setActiveEntityTabKey('CVE'); + setPage(1); + }} + /> + { + setActiveEntityTabKey('VirtualMachine'); + setPage(1); + }} + /> + + ); +} + +export default VirtualMachineCvesToggleGroup; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachinesCvesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx similarity index 78% rename from ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachinesCvesTable.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx index 6b663a7647d9b..2ce7d69cde079 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachinesCvesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx @@ -1,42 +1,29 @@ import { useCallback } from 'react'; import { Link } from 'react-router-dom-v5-compat'; -import { Flex, FlexItem, Pagination } from '@patternfly/react-core'; +import { ToolbarItem } from '@patternfly/react-core'; import { InnerScrollContainer, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import ColumnManagementButton from 'Components/ColumnManagementButton'; import DateDistance from 'Components/DateDistance'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; -import { - generateVisibilityForColumns, - getHiddenColumnCount, - useManagedColumns, -} from 'hooks/useManagedColumns'; +import { generateVisibilityForColumns, getHiddenColumnCount } from 'hooks/useManagedColumns'; +import type { ManagedColumns } from 'hooks/useManagedColumns'; import useFeatureFlags from 'hooks/useFeatureFlags'; import useRestQuery from 'hooks/useRestQuery'; -import useURLPagination from 'hooks/useURLPagination'; -import useURLSearch from 'hooks/useURLSearch'; +import type { UseURLPaginationResult } from 'hooks/useURLPagination'; import useURLSort from 'hooks/useURLSort'; import { listVMs } from 'services/VirtualMachineService'; +import type { SearchFilter } from 'types/search'; import { getTableUIState } from 'utils/getTableUIState'; -import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; import SeverityCountLabels from '../../components/SeverityCountLabels'; -import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; -import { - virtualMachinesClusterSearchFilterConfig, - virtualMachinesNamespaceSearchFilterConfig, - virtualMachinesSearchFilterConfig, -} from '../../searchFilterConfig'; +import TableEntityToolbar from '../../components/TableEntityToolbar'; import { getVirtualMachineEntityPagePath } from '../../utils/searchUtils'; import { VIRTUAL_MACHINE_SORT_FIELD } from '../../utils/sortFields'; +import VirtualMachineCvesFilterToolbar from './VirtualMachineCvesFilterToolbar'; +import VirtualMachineCvesToggleGroup from './VirtualMachineCvesToggleGroup'; import VirtualMachinesCvesTableLegacy from './VirtualMachinesCvesTableLegacy'; -const searchFilterConfig = [ - virtualMachinesClusterSearchFilterConfig, - virtualMachinesNamespaceSearchFilterConfig, - virtualMachinesSearchFilterConfig, -]; - const sortFields = [VIRTUAL_MACHINE_SORT_FIELD]; const defaultSortOption = { field: VIRTUAL_MACHINE_SORT_FIELD, direction: 'asc' } as const; @@ -69,14 +56,28 @@ export const defaultColumns = { }, } as const; -function VirtualMachinesCvesTableEnhanced() { - const managedColumnState = useManagedColumns('VirtualMachinesCvesTable', defaultColumns); - const { page, perPage, setPage, setPerPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); - const { searchFilter, setSearchFilter } = useURLSearch(); +type VirtualMachineCvesVmTableProps = { + searchFilter: SearchFilter; + pagination: UseURLPaginationResult; + managedColumnState: ManagedColumns; + isFiltered: boolean; + onClearFilters: () => void; +}; + +type VirtualMachineCvesVmTableEnhancedProps = VirtualMachineCvesVmTableProps; + +function VirtualMachineCvesVmTableEnhanced({ + searchFilter, + pagination, + managedColumnState, + isFiltered, + onClearFilters, +}: VirtualMachineCvesVmTableEnhancedProps) { + const { page, perPage } = pagination; const { sortOption, getSortParams } = useURLSort({ sortFields, defaultSortOption, - onSort: () => setPage(1), + onSort: () => pagination.setPage(1, 'replace'), }); const getVisibilityClass = generateVisibilityForColumns(managedColumnState.columns); @@ -88,6 +89,9 @@ function VirtualMachinesCvesTableEnhanced() { [searchFilter, page, perPage, sortOption] ); const { data, isLoading, error } = useRestQuery(fetchVirtualMachines); + + const totalCount = data?.totalCount ?? 0; + const tableState = getTableUIState({ isLoading, data: data?.vms ?? [], @@ -97,40 +101,26 @@ function VirtualMachinesCvesTableEnhanced() { return ( <> - - - { - setSearchFilter(newFilter); - setPage(1, 'replace'); - }} + } + entityToggleGroup={} + pagination={pagination} + tableRowCount={totalCount} + isFiltered={isFiltered} + > + + - - - setPage(newPage)} - onPerPageSelect={(_, newPerPage) => { - setPerPage(newPerPage); - }} - /> - + +
    @@ -161,6 +151,7 @@ function VirtualMachinesCvesTableEnhanced() { emptyProps={{ message: 'No CVEs have been detected', }} + filteredEmptyProps={{ onClearFilters }} renderer={({ data }) => ( {data.map((virtualMachine) => { @@ -236,17 +227,17 @@ function VirtualMachinesCvesTableEnhanced() { ); } -function VirtualMachinesCvesTable() { +function VirtualMachineCvesVmTable(props: VirtualMachineCvesVmTableProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); const isEnhancedDataModelEnabled = isFeatureFlagEnabled( 'ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL' ); if (isEnhancedDataModelEnabled) { - return ; + return ; } return ; } -export default VirtualMachinesCvesTable; +export default VirtualMachineCvesVmTable; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts index 56dd4f52f9f4f..9abd4f0fe1f7a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts @@ -32,6 +32,9 @@ import { imageAttributes } from 'Components/CompoundSearchFilter/attributes/imag import { imageCVEAttributes } from 'Components/CompoundSearchFilter/attributes/imageCVE'; import { imageComponentAttributes } from 'Components/CompoundSearchFilter/attributes/imageComponent'; import { + Annotation as namespaceAnnotation, + ID as namespaceId, + Label as namespaceLabel, Name as namespaceName, namespaceAttributes, } from 'Components/CompoundSearchFilter/attributes/namespace'; @@ -40,12 +43,16 @@ import { nodeCVEAttributes } from 'Components/CompoundSearchFilter/attributes/no import { nodeComponentAttributes } from 'Components/CompoundSearchFilter/attributes/nodeComponent'; import { platformCVEAttributes } from 'Components/CompoundSearchFilter/attributes/platformCVE'; import { + VirtualMachineCVECvss, + VirtualMachineCVEDiscoveredTime, VirtualMachineCVEName, VirtualMachineComponentName, VirtualMachineComponentSource, VirtualMachineComponentVersion, + VirtualMachineGuestOs, VirtualMachineID, VirtualMachineName, + VirtualMachineScanTime, } from 'Components/CompoundSearchFilter/attributes/virtualMachine'; import { vulnerabilitySeverityLabels } from 'messages/common'; @@ -120,14 +127,19 @@ export const platformCVESearchFilterConfig: CompoundSearchFilterEntity = { export const virtualMachinesSearchFilterConfig: CompoundSearchFilterEntity = { displayName: 'Virtual machine', - searchCategory: 'VIRTUAL_MACHINES', - attributes: [VirtualMachineID, VirtualMachineName], + searchCategory: 'SEARCH_UNSET', + attributes: [ + VirtualMachineGuestOs, + VirtualMachineID, + VirtualMachineName, + VirtualMachineScanTime, + ], }; export const virtualMachineCVESearchFilterConfig: CompoundSearchFilterEntity = { displayName: 'CVE', searchCategory: 'SEARCH_UNSET', // we don't have autocomplete for virtual machines - attributes: [VirtualMachineCVEName], + attributes: [VirtualMachineCVECvss, VirtualMachineCVEDiscoveredTime, VirtualMachineCVEName], }; export const virtualMachineComponentLegacySearchFilterConfig: CompoundSearchFilterEntity = { @@ -149,13 +161,19 @@ export const virtualMachineComponentSearchFilterConfig: CompoundSearchFilterEnti export const virtualMachinesClusterSearchFilterConfig: CompoundSearchFilterEntity = { displayName: 'Cluster', searchCategory: 'CLUSTERS', - attributes: [clusterIdAttribute, clusterNameAttribute], + attributes: [ + clusterIdAttribute, + clusterLabelAttribute, + clusterNameAttribute, + clusterPlatformTypeAttribute, + clusterTypeAttribute, + ], }; export const virtualMachinesNamespaceSearchFilterConfig: CompoundSearchFilterEntity = { displayName: 'Namespace', searchCategory: 'SEARCH_UNSET', // virtual machine pipeline doesn't add namespace - attributes: [namespaceName], + attributes: [namespaceAnnotation, namespaceId, namespaceLabel, namespaceName], }; // attributes for separate search filter elements in AdvancedFiltersToolbar.tsx file diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index 17b6f5b5c8563..44ec663773958 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -226,14 +226,27 @@ export function listVMs({ perPage, searchFilter, }: SearchQueryOptions): Promise { - const params = buildNestedRawQueryParams({ page, perPage, sortOption, searchFilter }); + const params = buildNestedRawQueryParams({ + page, + perPage, + sortOption, + searchFilter: applyRegexSearchModifiers(searchFilter ?? {}), + }); return axios .get(`/v2/virtualmachines/vms?${params}`) .then((response) => response.data); } -export function listVMCVEs({ page, perPage }: SearchQueryOptions): Promise { - const params = buildNestedRawQueryParams({ page, perPage }); +export function listVMCVEs({ + searchFilter, + page, + perPage, +}: SearchQueryOptions): Promise { + const params = buildNestedRawQueryParams({ + page, + perPage, + searchFilter: applyRegexSearchModifiers(searchFilter ?? {}), + }); return axios .get(`/v2/virtualmachines/cves?${params}`) .then((response) => response.data); diff --git a/ui/apps/platform/src/types/searchOptions.ts b/ui/apps/platform/src/types/searchOptions.ts index 5cc6854495401..83665db4f75e4 100644 --- a/ui/apps/platform/src/types/searchOptions.ts +++ b/ui/apps/platform/src/types/searchOptions.ts @@ -422,8 +422,10 @@ export const searchFieldLabels = [ 'AuthProvider Name', // // Virtual Machine fields. + 'Guest OS', 'Virtual Machine ID', 'Virtual Machine Name', + 'Virtual Machine Scan Time', 'Scan Status', // frontend only pending backend ] as const; diff --git a/ui/apps/platform/src/utils/searchUtils.ts b/ui/apps/platform/src/utils/searchUtils.ts index 6fa8ec55c075d..9be99fdd3d567 100644 --- a/ui/apps/platform/src/utils/searchUtils.ts +++ b/ui/apps/platform/src/utils/searchUtils.ts @@ -26,6 +26,7 @@ import { } from 'Components/CompoundSearchFilter/attributes/cluster'; import { policyAttributes } from 'Components/CompoundSearchFilter/attributes/policy'; import { profileName } from 'Components/CompoundSearchFilter/attributes/profile'; +import { virtualMachineAttributes } from 'Components/CompoundSearchFilter/attributes/virtualMachine'; import type { NonEmptyArray, ValueOf } from './type.utils'; import { safeGeneratePath } from './urlUtils'; @@ -449,6 +450,7 @@ const regexSearchOptions = [ clusterTypeAttribute, policyAttributes, profileName, + virtualMachineAttributes, ] .flat() .filter(({ inputType }) => inputType === 'text' || inputType === 'autocomplete') From 2cd3e888ec7117910158089f27db25ea3d1e6165 Mon Sep 17 00:00:00 2001 From: David Vail Date: Wed, 26 Aug 2026 17:24:32 -0400 Subject: [PATCH 095/638] ROX-35953: Direct nagivate to dynamic plugin status page (#22460) --- ui/apps/platform/cypress/helpers/main.js | 11 +++++++++++ .../platform/cypress/integration-ocp/smoke.test.ts | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ui/apps/platform/cypress/helpers/main.js b/ui/apps/platform/cypress/helpers/main.js index 0b0cbab3fdd42..d5cc4298bd905 100644 --- a/ui/apps/platform/cypress/helpers/main.js +++ b/ui/apps/platform/cypress/helpers/main.js @@ -106,6 +106,17 @@ export function visitConsoleMainDashboard(staticResponseMap) { cy.get(`h1:contains("Overview")`); } +/** + * @param {Record} [staticResponseMap] + */ +export function visitConsoleDynamicPluginsStatusPage(staticResponseMap) { + visitConsole( + '/k8s/cluster/operator.openshift.io~v1~Console/cluster/console-plugins', + routeMatcherMapForConsole, + staticResponseMap + ); +} + /** * Visit main dashboard to test conditional rendering for user role permissions specified as response or fixture. * Conditional rendering for permissions might make a subset of requests. diff --git a/ui/apps/platform/cypress/integration-ocp/smoke.test.ts b/ui/apps/platform/cypress/integration-ocp/smoke.test.ts index 8b2fb4f8e2647..13cfd9b1a9cbb 100644 --- a/ui/apps/platform/cypress/integration-ocp/smoke.test.ts +++ b/ui/apps/platform/cypress/integration-ocp/smoke.test.ts @@ -1,4 +1,4 @@ -import { visitFromConsoleLeftNavExpandable } from '../helpers/nav'; +import { visitConsoleDynamicPluginsStatusPage } from '../helpers/main'; import { withOcpAuth } from '../helpers/ocpAuth'; describe('Basic tests of the OCP plugin', () => { @@ -30,7 +30,7 @@ describe('Basic tests of the OCP plugin', () => { }); it('should display plugin information in cluster settings', () => { - visitFromConsoleLeftNavExpandable('Administration', 'Dynamic Plugins'); + visitConsoleDynamicPluginsStatusPage(); cy.get('td[data-label="name"]:contains("advanced-cluster-security")') .parent() From 71c7cf45e1c678807567cad4f5a158f98b00e98a Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:25:20 -0500 Subject: [PATCH 096/638] ROX-36420: Node report configuration deletion (#22425) --- .../Configurations/NodeConfigurationsList.tsx | 101 ++++++++++++++-- .../Reports/components/DeleteReportsModal.tsx | 114 ++++++++++++++++++ .../ViewVulnReport/ViewVulnReportPage.tsx | 2 + .../VulnReports/ConfigReportsTab.tsx | 62 ++-------- .../hooks/useDeleteModal.ts | 11 +- .../src/services/NodeReportsService.ts | 10 +- 6 files changed, 229 insertions(+), 71 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/components/DeleteReportsModal.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx index ccb6b8578eec5..d844f7bec8705 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx @@ -3,6 +3,7 @@ import { Link, generatePath } from 'react-router-dom-v5-compat'; import { Button, Content, + DropdownItem, Flex, FlexItem, PageSection, @@ -15,10 +16,12 @@ import { import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import HelpIconTh from 'Components/HelpIconTh'; +import MenuDropdown from 'Components/PatternFly/MenuDropdown'; import PageTitle from 'Components/PageTitle'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; import usePermissions from 'hooks/usePermissions'; import useRestQuery from 'hooks/useRestQuery'; +import useTableSelection from 'hooks/useTableSelection'; import useURLPagination from 'hooks/useURLPagination'; import useURLSearch from 'hooks/useURLSearch'; import useURLSort from 'hooks/useURLSort'; @@ -27,11 +30,15 @@ import { vulnerabilityNodeConfigurationsReportsPath, } from 'routePaths'; import { + deleteNodeReportConfiguration, fetchNodeReportConfigurations, fetchNodeReportConfigurationsCount, } from 'services/NodeReportsService'; import { getTableUIState } from 'utils/getTableUIState'; +import DeleteReportsModal from '../../components/DeleteReportsModal'; +import useDeleteModal from '../../../VulnerablityReporting/hooks/useDeleteModal'; + const CreateReportsButton = () => { return ( @@ -49,9 +56,15 @@ const sortOptions = { function NodeConfigurationsList() { const { hasReadWriteAccess, hasReadAccess } = usePermissions(); + const hasDeleteAccessForReport = hasReadWriteAccess('WorkflowAdministration'); const hasWriteAccessForReport = hasReadWriteAccess('WorkflowAdministration') && hasReadAccess('Integration'); + // single source of truth for which optional columns actually render, should prevent drift if + // any permissions change above + const hasSelectColumn = hasDeleteAccessForReport; + const hasActionsColumn = hasDeleteAccessForReport || hasWriteAccessForReport; + const { page, perPage, setPage, setPerPage } = useURLPagination(10); const { sortOption, getSortParams } = useURLSort(sortOptions); const { searchFilter, setSearchFilter } = useURLSearch(); @@ -74,13 +87,16 @@ function NodeConfigurationsList() { data: reportConfigurations, isLoading: isListLoading, error: listError, + refetch: refetchList, } = useRestQuery(fetchReportConfigurationsCallback); const fetchReportConfigurationsCountCallback = useCallback( () => fetchNodeReportConfigurationsCount(searchFilter), [searchFilter] ); - const { data: countData } = useRestQuery(fetchReportConfigurationsCountCallback); + const { data: countData, refetch: refetchCount } = useRestQuery( + fetchReportConfigurationsCountCallback + ); // table should still load even if countData is not available const totalReports = countData?.count ?? 0; @@ -92,7 +108,39 @@ function NodeConfigurationsList() { searchFilter, }); - const colSpan = hasWriteAccessForReport ? 5 : 4; + const { + selected, + numSelected, + allRowsSelected, + hasSelections, + onSelect, + onSelectAll, + onClearAll: onClearAllSelected, + getSelectedIds, + } = useTableSelection(reportConfigurations ?? []); + + const { + openDeleteModal, + isDeleteModalOpen, + closeDeleteModal, + isDeleting, + onDelete, + deleteResults, + reportIdsToDelete, + } = useDeleteModal({ + deleteFunction: deleteNodeReportConfiguration, + onCompleted: () => { + onClearAllSelected(); + refetchList(); + refetchCount(); + }, + }); + + function onConfirmDeleteSelection() { + openDeleteModal(getSelectedIds()); + } + + const colSpan = 4 + (hasSelectColumn ? 1 : 0) + (hasActionsColumn ? 1 : 0); function onClearFilters() { setSearchValue(''); @@ -135,6 +183,15 @@ function NodeConfigurationsList() { onClear={onClearFilters} /> + {hasDeleteAccessForReport && ( + + + + Delete ({numSelected}) + + + + )} + {hasSelectColumn && ( + - {hasWriteAccessForReport && - data.map((report) => { + data.map((report, rowIndex) => { const reportURL = generatePath( vulnerabilityNodeConfigurationsReportsDetailsPath, { @@ -190,6 +255,15 @@ function NodeConfigurationsList() { return ( + {hasSelectColumn && ( + @@ -198,16 +272,18 @@ function NodeConfigurationsList() { {report.description || '-'} - {hasWriteAccessForReport && ( + {hasActionsColumn && ( )} @@ -219,6 +295,15 @@ function NodeConfigurationsList() { />
    + )} Report Description My last job status} + {hasActionsColumn && }
    + )} {report.name} ROX-36102 { + event.preventDefault(); + openDeleteModal([report.id]); + }, }, ]} - isDisabled />
    + ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/components/DeleteReportsModal.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/components/DeleteReportsModal.tsx new file mode 100644 index 0000000000000..f9a74a416e7c4 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/components/DeleteReportsModal.tsx @@ -0,0 +1,114 @@ +import { + Alert, + AlertGroup, + Button, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + Title, +} from '@patternfly/react-core'; +import pluralize from 'pluralize'; + +import type { ReportConfigurationBase } from 'services/ReportsService.types'; + +import { + isErrorDeleteResult, + isSuccessDeleteResult, +} from '../../VulnerablityReporting/hooks/useDeleteModal'; +import type { DeleteResult } from '../../VulnerablityReporting/hooks/useDeleteModal'; + +export type DeleteReportsModalProps = { + isOpen: boolean; + onClose: () => void; + isDeleting: boolean; + onDelete: () => void; + reportIdsToDelete: string[]; + deleteResults: DeleteResult[] | null; + reportConfigurations: ReportConfigurationBase[] | null; +}; + +function DeleteReportsModal({ + isOpen, + onClose, + isDeleting, + onDelete, + reportIdsToDelete, + deleteResults, + reportConfigurations, +}: DeleteReportsModalProps) { + const numSuccessfulDeletions = deleteResults?.filter(isSuccessDeleteResult).length ?? 0; + const title = `Permanently delete ${reportIdsToDelete.length} report ${pluralize( + 'configuration', + reportIdsToDelete.length + )}?`; + + return ( + + + + {title} + + + + + {numSuccessfulDeletions > 0 && ( + + )} + {deleteResults?.filter(isErrorDeleteResult).map((deleteResult) => { + const reportConfiguration = reportConfigurations?.find( + (configuration) => configuration.id === deleteResult.id + ); + if (!reportConfiguration) { + return null; + } + return ( + + {deleteResult.error} + + ); + })} + +

    + The selected report {pluralize('configuration', reportIdsToDelete.length)} and + any attached downloadable reports will be permanently deleted. The action cannot + be undone. +

    +
    + + + + +
    + ); +} + +export default DeleteReportsModal; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx index d7dfc98dfb506..fa31bd15e73fb 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx @@ -28,6 +28,7 @@ import NotFoundMessage from 'Components/NotFoundMessage/NotFoundMessage'; import usePermissions from 'hooks/usePermissions'; import useToasts from 'hooks/patternfly/useToasts'; import type { Toast } from 'hooks/patternfly/useToasts'; +import { deleteReportConfiguration } from 'services/ReportsService'; import type { ReportConfiguration } from 'services/ReportsService.types'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; @@ -79,6 +80,7 @@ function ViewVulnReportPage() { onDelete, deleteResults, } = useDeleteModal({ + deleteFunction: deleteReportConfiguration, onCompleted: () => { navigate(vulnerabilityConfigurationsReportsPath); }, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx index 2fdfa788bd82b..8e7a9c0e2897c 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx @@ -31,7 +31,6 @@ import useURLPagination from 'hooks/useURLPagination'; import useURLSearch from 'hooks/useURLSearch'; import useURLSort from 'hooks/useURLSort'; -import DeleteModal from 'Components/PatternFly/DeleteModal'; import PageTitle from 'Components/PageTitle'; import EmptyStateTemplate from 'Components/EmptyStateTemplate/EmptyStateTemplate'; import CollectionsFormModal from 'Containers/Collections/CollectionsFormModal'; @@ -39,22 +38,19 @@ import useToasts from 'hooks/patternfly/useToasts'; import type { Toast } from 'hooks/patternfly/useToasts'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; import useTableSelection from 'hooks/useTableSelection'; -import pluralize from 'pluralize'; import HelpIconTh from 'Components/HelpIconTh'; import JobStatusPopoverContent from 'Components/ReportJob/JobStatusPopoverContent'; import MyLastJobStatus from 'Components/ReportJob/MyLastJobStatus'; import useAuthStatus from 'hooks/useAuthStatus'; -import { reportDownloadURL } from 'services/ReportsService'; +import { deleteReportConfiguration, reportDownloadURL } from 'services/ReportsService'; import type { ImageVulnerabilityReportConfiguration } from 'services/ReportsService.types'; +import useDeleteModal from '../hooks/useDeleteModal'; +import DeleteReportsModal from '../../Reports/components/DeleteReportsModal'; import type { ImageVulnerabilityResourceScope } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types'; import useFetchReports from '../api/useFetchReports'; import useRunReport from '../api/useRunReport'; import { useWatchLastSnapshotForReports } from '../api/useWatchLastSnapshotForReports'; -import useDeleteModal, { - isErrorDeleteResult, - isSuccessDeleteResult, -} from '../hooks/useDeleteModal'; import { vulnerabilityConfigurationReportDetailsPath } from '../pathsForVulnerabilityReporting'; // resourceScope: {} after roll back to previous version that does not support a newer resource scope. @@ -151,6 +147,7 @@ function ConfigReportsTab() { deleteResults, reportIdsToDelete, } = useDeleteModal({ + deleteFunction: deleteReportConfiguration, onCompleted: () => { onClearAllSelected(); fetchReports(); @@ -170,8 +167,6 @@ function ConfigReportsTab() { openDeleteModal(selectedIds); } - const numSuccessfulDeletions = deleteResults?.filter(isSuccessDeleteResult).length || 0; - return ( <> @@ -525,54 +520,15 @@ function ConfigReportsTab() { )} - - - {numSuccessfulDeletions > 0 && ( - - )} - {deleteResults?.filter(isErrorDeleteResult).map((deleteResult) => { - const report = reportConfigurations?.find( - (reportConfig) => reportConfig.id === deleteResult.id - ); - if (!report) { - return null; - } - return ( - - {deleteResult.error} - - ); - })} - -

    - The selected report(s) and any attached downloadable reports will be permanently - deleted. The action cannot be undone. -

    -
    + reportIdsToDelete={reportIdsToDelete} + deleteResults={deleteResults} + reportConfigurations={reportConfigurations} + /> {collectionModalId && ( Promise; onCompleted: () => void; }; @@ -43,7 +43,10 @@ export function isErrorDeleteResult(deleteResult: DeleteResult): deleteResult is return deleteResult.success === false; } -function useDeleteModal({ onCompleted }: UseDeleteModalProps): UseDeleteModalResult { +function useDeleteModal({ + deleteFunction, + onCompleted, +}: UseDeleteModalProps): UseDeleteModalResult { const { isModalOpen: isDeleteModalOpen, openModal, closeModal } = useModal(); const [reportIdsToDelete, setReportIdsToDelete] = useState([]); const [isDeleting, setIsDeleting] = useState(false); @@ -66,7 +69,7 @@ function useDeleteModal({ onCompleted }: UseDeleteModalProps): UseDeleteModalRes // potential errors. async function onSafeDelete(id: string): Promise { try { - const result = await deleteReportConfiguration(id); + const result = await deleteFunction(id); return { success: true, id, result }; } catch (error) { return { diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts index 6371fed7e6992..7bf802a67c000 100644 --- a/ui/apps/platform/src/services/NodeReportsService.ts +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -89,12 +89,10 @@ export function fetchNodeReportConfiguration( } // DeleteNodeReportConfiguration -export function deleteNodeReportConfiguration(reportId: string): CancellableRequest { - return makeCancellableAxiosRequest((signal) => - axios - .delete(`/v2/reports/node/configurations/${reportId}`, { signal }) - .then((response) => response.data) - ); +export function deleteNodeReportConfiguration(reportId: string): Promise { + return axios + .delete(`/v2/reports/node/configurations/${reportId}`) + .then((response) => response.data); } // Configuration-based jobs From 70aed7a10c02d47fc4f132225cafcf51962c7f80 Mon Sep 17 00:00:00 2001 From: Cong Du Date: Wed, 26 Aug 2026 15:00:15 -0700 Subject: [PATCH 097/638] ROX-36240: fix flaky Severity sort test in violations E2E (#22436) Co-authored-by: Claude Sonnet 4.6 (1M context) Co-authored-by: Kirk Bater --- .../cypress/integration/violations/violations.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/apps/platform/cypress/integration/violations/violations.test.js b/ui/apps/platform/cypress/integration/violations/violations.test.js index 45ca139da3491..7b0e0457ba0fa 100644 --- a/ui/apps/platform/cypress/integration/violations/violations.test.js +++ b/ui/apps/platform/cypress/integration/violations/violations.test.js @@ -212,6 +212,10 @@ describe('Violations', () => { }); it('should sort the Severity column', () => { + // Freeze clock so the 5s poll in ViolationsTablePage doesn't race + // with waiting for violations responses. + cy.clock(Date.now(), ['setInterval']); + interactAndWaitForViolationsResponses(() => { visitViolations(); }); From f3f3b37bba4ee607e7ab2c7f3b4d7dc87442ddd3 Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:20:16 -0500 Subject: [PATCH 098/638] ROX-35970: Add CVE Origin UI (#22464) --- .../DeploymentPageVulnerabilities.tsx | 2 + .../Image/ImagePageVulnerabilities.tsx | 2 + ...eploymentComponentVulnerabilitiesTable.tsx | 11 +++- .../Tables/DeploymentVulnerabilitiesTable.tsx | 21 ++++++- .../ImageComponentVulnerabilitiesTable.tsx | 28 ++++++++- .../Tables/ImageVulnerabilitiesTable.tsx | 22 ++++++- .../WorkloadCves/Tables/infoForTh.tsx | 41 +++++++++++++ .../WorkloadCves/Tables/table.utils.ts | 31 ++++++---- .../utils/vulnerabilityUtils.test.ts | 60 +++++++++++++++++++ .../utils/vulnerabilityUtils.ts | 38 ++++++++++++ 10 files changed, 240 insertions(+), 16 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx index d614fc6259805..2fbb9fb8a4e42 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx @@ -182,9 +182,11 @@ function DeploymentPageVulnerabilities({ const managedColumnState = useManagedColumns(tableId, defaultColumns); const isEpssProbabilityColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); + const isOriginColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); const columnConfig = overrideManagedColumns(managedColumnState.columns, { epssProbability: hideColumnIf(!isEpssProbabilityColumnEnabled), + origin: hideColumnIf(!isOriginColumnEnabled), }); // Keep searchFilterConfigWithFeatureFlagDependency for ROX_SCANNER_V4 also Advisory. diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx index 97a8c6ca68244..2b771dfcd6b0e 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx @@ -237,6 +237,7 @@ function ImagePageVulnerabilities({ const isNvdCvssColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); const isEpssProbabilityColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); + const isOriginColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); const managedColumnState = useManagedColumns(tableId, defaultColumns); @@ -244,6 +245,7 @@ function ImagePageVulnerabilities({ cveSelection: hideColumnIf(!canSelectRows), nvdCvss: hideColumnIf(!isNvdCvssColumnEnabled), epssProbability: hideColumnIf(!isEpssProbabilityColumnEnabled), + origin: hideColumnIf(!isOriginColumnEnabled), requestDetails: hideColumnIf(vulnerabilityState === 'OBSERVED'), rowActions: hideColumnIf(createTableActions === undefined), }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentComponentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentComponentVulnerabilitiesTable.tsx index 9453b2bd55852..4cd5efd00a24e 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentComponentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentComponentVulnerabilitiesTable.tsx @@ -10,6 +10,7 @@ import type { VulnerabilityState } from 'types/cve.proto'; import CvssFormatted from 'Components/CvssFormatted'; import AdvisoryLinkOrText from '../../components/AdvisoryLinkOrText'; +import { getOriginDisplayName } from '../../utils/vulnerabilityUtils'; import PendingExceptionLabel from '../../components/PendingExceptionLabel'; import ImageNameLink from '../components/ImageNameLink'; import { @@ -37,6 +38,7 @@ export const deploymentComponentVulnerabilitiesFragment = gql` cvss scoreVersion fixedByVersion + origin advisory { name link @@ -68,8 +70,10 @@ function DeploymentComponentVulnerabilitiesTable({ }: DeploymentComponentVulnerabilitiesTableProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); const isAdvisoryColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); + const isOriginColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); - const colSpanForDockerfileLayer = 8 + (isAdvisoryColumnEnabled ? 1 : 0); + const colSpanForDockerfileLayer = + 8 + (isAdvisoryColumnEnabled ? 1 : 0) + (isOriginColumnEnabled ? 1 : 0); const { sortOption, getSortParams } = useTableSort({ sortFields, defaultSortOption }); const componentVulns = images.flatMap(({ imageMetadataContext, componentVulnerabilities }) => @@ -89,6 +93,7 @@ function DeploymentComponentVulnerabilitiesTable({ CVE fixed in {isAdvisoryColumnEnabled && Advisory} Source + {isOriginColumnEnabled && CVE origin} Location @@ -101,6 +106,7 @@ function DeploymentComponentVulnerabilitiesTable({ cvss, scoreVersion, fixedByVersion, + origin, advisory, location, source, @@ -161,6 +167,9 @@ function DeploymentComponentVulnerabilitiesTable({ )} {source} + {isOriginColumnEnabled && ( + {getOriginDisplayName(origin)} + )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx index dd3910522cb85..25a806bff9be5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx @@ -5,6 +5,7 @@ import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternf import { gql } from '@apollo/client'; import useFeatureFlags from 'hooks/useFeatureFlags'; +import useMetadata from 'hooks/useMetadata'; import useSet from 'hooks/useSet'; import type { UseURLSortResult } from 'hooks/useURLSort'; import VulnerabilitySeverityIconText from 'Components/PatternFly/IconText/VulnerabilitySeverityIconText'; @@ -27,7 +28,7 @@ import DeploymentComponentVulnerabilitiesTable, { } from './DeploymentComponentVulnerabilitiesTable'; import PartialCVEDataAlert from '../../components/PartialCVEDataAlert'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; -import { infoForEpssProbability } from './infoForTh'; +import { getInfoForCveOrigin, infoForEpssProbability } from './infoForTh'; import { formatEpssProbabilityAsPercent } from './table.utils'; import type { FormattedDeploymentVulnerability } from './table.utils'; @@ -63,6 +64,10 @@ export const defaultColumns = { title: 'Affected components', isShownByDefault: true, }, + origin: { + title: 'CVE origin', + isShownByDefault: true, + }, firstDiscovered: { title: 'First discovered', isShownByDefault: true, @@ -120,6 +125,7 @@ function DeploymentVulnerabilitiesTable({ tableConfig, }: DeploymentVulnerabilitiesTableProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); + const { version } = useMetadata(); const { urlBuilder } = useWorkloadCveViewContext(); const getVisibilityClass = generateVisibilityForColumns(tableConfig); const hiddenColumnCount = getHiddenColumnCount(tableConfig); @@ -156,6 +162,12 @@ function DeploymentVulnerabilitiesTable({ Affected components {isFiltered && } + + CVE origin + First discovered Published @@ -175,6 +187,7 @@ function DeploymentVulnerabilitiesTable({ severity, summary, isFixable, + origin, images, affectedComponentsText, discoveredAtImage, @@ -274,6 +287,12 @@ function DeploymentVulnerabilitiesTable({ > {affectedComponentsText} + + {origin} + Component Version + CVE severity + CVSS CVE fixed in {isAdvisoryColumnEnabled && Advisory} Source + {isOriginColumnEnabled && CVE origin} {isLayerTypeColumnEnabled && Layer type} Location @@ -84,7 +97,11 @@ function ImageComponentVulnerabilitiesTable({ image, name, version, + severity, + cvss, + scoreVersion, fixedByVersion, + origin, advisory, location, source, @@ -105,6 +122,12 @@ function ImageComponentVulnerabilitiesTable({ {name} {version} + + + + + + @@ -114,6 +137,9 @@ function ImageComponentVulnerabilitiesTable({ )} {source} + {isOriginColumnEnabled && ( + {getOriginDisplayName(origin)} + )} {isLayerTypeColumnEnabled && (
    + Supported operating systems (RHACS) + + + + + } + /> + ), + }; +} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/table.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/table.utils.ts index 70077e05412bc..a29eefaa326ba 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/table.utils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/table.utils.ts @@ -9,6 +9,7 @@ import type { SourceType } from 'types/image.proto'; import type { ApiSortOptionSingle } from 'types/search'; import { + getAggregateOrigin, getHighestVulnerabilitySeverity, getIsSomeVulnerabilityFixable, } from '../../utils/vulnerabilityUtils'; @@ -82,7 +83,10 @@ export type ComponentVulnerabilityBase = { inBaseImageLayer?: boolean; imageVulnerabilities: { severity: string; + cvss: number; + scoreVersion: string; fixedByVersion: string; + origin?: string; advisory?: Advisory | null; pendingExceptionCount: number; }[]; @@ -99,6 +103,7 @@ export type DeploymentComponentVulnerability = Omit< cvss: number; scoreVersion: string; fixedByVersion: string; + origin?: string; advisory?: Advisory | null; discoveredAtImage: string | null; publishedOn: string | null; @@ -120,6 +125,8 @@ export type TableDataRow = { fixedByVersion: string; advisory?: Advisory | null; severity: VulnerabilitySeverity; + cvss: number; + scoreVersion: string; version: string; location: string; source: SourceType; @@ -128,6 +135,7 @@ export type TableDataRow = { instruction: string; value: string; } | null; + origin?: string; pendingExceptionCount: number; inBaseImageLayer?: boolean; }; @@ -158,23 +166,13 @@ export function flattenImageComponentVulns( export function flattenDeploymentComponentVulns( imageMetadataContext: ImageMetadataContext, componentVulnerabilities: DeploymentComponentVulnerability[] -): (TableDataRow & { - cvss: number; - scoreVersion: string; -})[] { +): TableDataRow[] { const image = imageMetadataContext; const layers = imageMetadataContext.metadata?.v1?.layers ?? []; return componentVulnerabilities.map((component) => { const vulnerability = component.imageVulnerabilities[0]; - const cvss = vulnerability?.cvss ?? 0; - const scoreVersion = vulnerability?.scoreVersion ?? 'N/A'; - - return { - ...extractCommonComponentFields(image, layers, component, vulnerability), - scoreVersion, - cvss, - }; + return extractCommonComponentFields(image, layers, component, vulnerability); }); } @@ -203,7 +201,10 @@ function extractCommonComponentFields( vulnerability?.severity && isVulnerabilitySeverity(vulnerability.severity) ? vulnerability.severity : 'UNKNOWN_VULNERABILITY_SEVERITY'; + const cvss = vulnerability?.cvss ?? 0; + const scoreVersion = vulnerability?.scoreVersion ?? 'N/A'; const fixedByVersion = vulnerability?.fixedByVersion ?? 'N/A'; + const origin = vulnerability?.origin; const advisory = vulnerability?.advisory; const pendingExceptionCount = vulnerability?.pendingExceptionCount ?? 0; @@ -219,7 +220,10 @@ function extractCommonComponentFields( }, layer, severity, + cvss, + scoreVersion, fixedByVersion, + origin, advisory, pendingExceptionCount, inBaseImageLayer, @@ -277,6 +281,7 @@ export type FormattedDeploymentVulnerability = { operatingSystem: string; severity: VulnerabilitySeverity; isFixable: boolean; + origin: string; discoveredAtImage: Date | null; publishedOn: Date | null; summary: string; @@ -310,6 +315,7 @@ export function formatVulnerabilityData( const allVulnerabilities = allVulnerableComponents.flatMap((c) => c.imageVulnerabilities); const highestVulnSeverity = getHighestVulnerabilitySeverity(allVulnerabilities); const isFixableInDeployment = getIsSomeVulnerabilityFixable(allVulnerabilities); + const origin = getAggregateOrigin(allVulnerabilities); const allDiscoveredDates = allVulnerableComponents .flatMap((c) => c.imageVulnerabilities.map((v) => v.discoveredAtImage)) .filter((d): d is string => d !== null); @@ -343,6 +349,7 @@ export function formatVulnerabilityData( operatingSystem, severity: highestVulnSeverity, isFixable: isFixableInDeployment, + origin, discoveredAtImage: oldestDiscoveredVulnDate, publishedOn: publishedOnDate, summary, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.test.ts b/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.test.ts index d95e38d4d5f2e..1fc281ae818a7 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.test.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.test.ts @@ -1,9 +1,11 @@ import type { NonEmptyArray } from 'utils/type.utils'; import { + getAggregateOrigin, getEarliestDiscoveredAtTime, getHighestCvssScore, getHighestVulnerabilitySeverity, getIsSomeVulnerabilityFixable, + getOriginDisplayName, } from './vulnerabilityUtils'; const CRITICAL = 'CRITICAL_VULNERABILITY_SEVERITY'; @@ -100,4 +102,62 @@ describe('vulnerability utils', () => { expect(getEarliestDiscoveredAtTime(vulnerabilities)).toEqual('2021-01-01T00:00:00Z'); }); }); + + describe('getOriginDisplayName', () => { + const testCases = [ + { origin: 'VULN_ORIGIN_ALPINE', expected: 'Alpine Linux' }, + { origin: 'VULN_ORIGIN_RED_HAT', expected: 'Red Hat' }, + { origin: 'VULN_ORIGIN_UBUNTU', expected: 'Ubuntu' }, + { origin: 'VULN_ORIGIN_OTHER', expected: 'Other' }, + ]; + + testCases.forEach(({ origin, expected }) => { + it(`returns '${expected}' for origin: ${origin}`, () => { + expect(getOriginDisplayName(origin)).toEqual(expected); + }); + }); + + it('returns the raw value for an unrecognized origin', () => { + expect(getOriginDisplayName('VULN_ORIGIN_FUTURE')).toEqual('VULN_ORIGIN_FUTURE'); + }); + + it("returns 'Other' when the origin is null or undefined", () => { + expect(getOriginDisplayName(null)).toEqual('Other'); + expect(getOriginDisplayName(undefined)).toEqual('Other'); + }); + }); + + describe('getAggregateOrigin', () => { + it('returns the display name when all vulnerabilities share a single origin', () => { + const vulnerabilities = [ + { origin: 'VULN_ORIGIN_UBUNTU' }, + { origin: 'VULN_ORIGIN_UBUNTU' }, + ]; + expect(getAggregateOrigin(vulnerabilities)).toEqual('Ubuntu'); + }); + + it("returns 'Multiple' when vulnerabilities have more than one distinct origin", () => { + const vulnerabilities = [ + { origin: 'VULN_ORIGIN_UBUNTU' }, + { origin: 'VULN_ORIGIN_RED_HAT' }, + ]; + expect(getAggregateOrigin(vulnerabilities)).toEqual('Multiple'); + }); + + it("returns 'Other' when there are no vulnerabilities", () => { + expect(getAggregateOrigin([])).toEqual('Other'); + }); + + it("returns 'Other' when no vulnerability has an origin", () => { + const vulnerabilities = [{ origin: undefined }, {}]; + expect(getAggregateOrigin(vulnerabilities)).toEqual('Other'); + }); + + it("returns 'Multiple' when a known origin is mixed with a missing origin", () => { + // A missing origin counts as the distinct 'Other' origin, so it aggregates to + // 'Multiple' rather than collapsing to the single known origin. + const vulnerabilities = [{ origin: 'VULN_ORIGIN_RED_HAT' }, { origin: undefined }]; + expect(getAggregateOrigin(vulnerabilities)).toEqual('Multiple'); + }); + }); }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.ts index 8e4c4774b8ab1..91d770dfb743d 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/utils/vulnerabilityUtils.ts @@ -77,6 +77,44 @@ export function getEarliestDiscoveredAtTime( return earliestDiscoveredAt; } +const OTHER_ORIGIN_DISPLAY_NAME = 'Other'; + +export const originDisplayNames: Partial> = { + VULN_ORIGIN_ALPINE: 'Alpine Linux', + VULN_ORIGIN_AMAZON: 'Amazon Linux', + VULN_ORIGIN_DEBIAN: 'Debian', + VULN_ORIGIN_ORACLE: 'Oracle Linux', + VULN_ORIGIN_OSV: 'OSV.dev', + VULN_ORIGIN_PHOTON: 'Photon OS', + VULN_ORIGIN_RED_HAT: 'Red Hat', + VULN_ORIGIN_SUSE: 'SUSE', + VULN_ORIGIN_UBUNTU: 'Ubuntu', + VULN_ORIGIN_OTHER: OTHER_ORIGIN_DISPLAY_NAME, +}; + +export function getOriginDisplayName(origin: string | null | undefined): string { + if (!origin) { + return OTHER_ORIGIN_DISPLAY_NAME; + } + return originDisplayNames[origin] ?? origin; +} + +export function getAggregateOrigin(vulnerabilities: { origin?: string }[]): string { + // A missing origin is treated as 'Other', which is a distinct origin, so a CVE that + // originates from Red Hat data in one component and has no origin in another aggregates + // to 'Multiple' rather than 'Red Hat'. + const distinctOrigins = [ + ...new Set(vulnerabilities.map(({ origin }) => origin || 'VULN_ORIGIN_OTHER')), + ]; + if (distinctOrigins.length === 0) { + return OTHER_ORIGIN_DISPLAY_NAME; + } + if (distinctOrigins.length === 1) { + return getOriginDisplayName(distinctOrigins[0]); + } + return 'Multiple'; +} + export function hasKnownExploit(exploit: Exploit | null | undefined): exploit is Exploit { return Boolean(exploit); } From 546a256ff429a185ae2e879c22601f143107f6eb Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:58:05 -0400 Subject: [PATCH 099/638] ROX-36262: Add pg_notify for report config changes (#21572) Co-authored-by: Claude Opus 4.6 (1M context) --- .../reports/scheduler/v2/scheduler_impl.go | 6 +- .../scheduler/v2/scheduler_impl_test.go | 1 + central/reports/service/v2/service.go | 5 +- central/reports/service/v2/service_impl.go | 70 ++++++++++++++-- .../reports/service/v2/service_impl_test.go | 79 ++++++++++++++++++- central/reports/service/v2/singleton.go | 2 +- central/reports/validation/validate.go | 69 ++++++++++++++++ 7 files changed, 221 insertions(+), 11 deletions(-) diff --git a/central/reports/scheduler/v2/scheduler_impl.go b/central/reports/scheduler/v2/scheduler_impl.go index 76b6dc706d4fa..d6166f13a9097 100644 --- a/central/reports/scheduler/v2/scheduler_impl.go +++ b/central/reports/scheduler/v2/scheduler_impl.go @@ -336,8 +336,10 @@ func (s *scheduler) SubmitReportRequest(ctx context.Context, request *reportGen. return "", err } - q.Enqueue(request) - s.readyForReports.Signal() + if s.isStarted.Load() { + q.Enqueue(request) + s.readyForReports.Signal() + } return request.ReportSnapshot.GetReportId(), nil } diff --git a/central/reports/scheduler/v2/scheduler_impl_test.go b/central/reports/scheduler/v2/scheduler_impl_test.go index 85b68d2c08347..ea1df02cc3646 100644 --- a/central/reports/scheduler/v2/scheduler_impl_test.go +++ b/central/reports/scheduler/v2/scheduler_impl_test.go @@ -291,6 +291,7 @@ func TestQueuePendingReports(t *testing.T) { defer cronScheduler.Stop() s := newSchedulerImpl(mockReportConfigDS, mockSnapshotStore, mockCollectionDS, nil, nil, nil, nil, cronScheduler, nil) + s.isStarted.Store(true) s.queuePendingReports() assert.Equal(t, 2, s.queues[0].queue.Len()) diff --git a/central/reports/service/v2/service.go b/central/reports/service/v2/service.go index 6ef015e4d6adf..2e9625112eb65 100644 --- a/central/reports/service/v2/service.go +++ b/central/reports/service/v2/service.go @@ -12,6 +12,7 @@ import ( collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" apiV2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/pkg/grpc" + "github.com/stackrox/rox/pkg/postgres" ) // Service provides the interface to the gRPC service for reports. @@ -25,7 +26,8 @@ type Service interface { // New returns a new instance of the service. func New(reportConfigStore reportConfigDS.DataStore, snapshotDatastore snapshotDS.DataStore, collectionDatastore collectionDS.DataStore, notifierDatastore notifierDS.DataStore, - scheduler schedulerV2.Scheduler, blobStore blobDS.Datastore, validator *validation.Validator) Service { + scheduler schedulerV2.Scheduler, blobStore blobDS.Datastore, validator *validation.Validator, + db postgres.DB) Service { return &serviceImpl{ reportConfigStore: reportConfigStore, snapshotDatastore: snapshotDatastore, @@ -34,5 +36,6 @@ func New(reportConfigStore reportConfigDS.DataStore, snapshotDatastore snapshotD scheduler: scheduler, blobStore: blobStore, validator: validator, + db: db, } } diff --git a/central/reports/service/v2/service_impl.go b/central/reports/service/v2/service_impl.go index 2996bd27844d9..be2bd0f43bb7d 100644 --- a/central/reports/service/v2/service_impl.go +++ b/central/reports/service/v2/service_impl.go @@ -10,6 +10,7 @@ import ( "github.com/stackrox/rox/central/reports/common" reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" snapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" "github.com/stackrox/rox/central/reports/validation" collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" @@ -17,12 +18,16 @@ import ( apiV2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/grpc/authn" "github.com/stackrox/rox/pkg/grpc/authz" "github.com/stackrox/rox/pkg/grpc/authz/perrpc" "github.com/stackrox/rox/pkg/grpc/authz/user" + "github.com/stackrox/rox/pkg/postgres" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" + "github.com/stackrox/rox/pkg/retry" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/search" @@ -83,6 +88,7 @@ type serviceImpl struct { scheduler schedulerV2.Scheduler blobStore blobDS.Datastore validator *validation.Validator + db postgres.DB } func (s *serviceImpl) RegisterServiceServer(grpcServer *grpc.Server) { @@ -124,8 +130,9 @@ func (s *serviceImpl) PostReportConfiguration(ctx context.Context, request *apiV return nil, err } - err = s.scheduler.UpsertReportSchedule(createdReportConfig) - if err != nil { + if env.CentralWorkerEnabled.BooleanSetting() { + notifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, id) + } else if err := s.scheduler.UpsertReportSchedule(createdReportConfig); err != nil { return nil, err } @@ -172,8 +179,9 @@ func (s *serviceImpl) UpdateReportConfiguration(ctx context.Context, request *ap return nil, err } - err = s.scheduler.UpsertReportSchedule(updatedConfig) - if err != nil { + if env.CentralWorkerEnabled.BooleanSetting() { + notifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, updatedConfig.GetId()) + } else if err := s.scheduler.UpsertReportSchedule(updatedConfig); err != nil { return nil, err } return &apiV2.Empty{}, nil @@ -261,7 +269,11 @@ func (s *serviceImpl) DeleteReportConfiguration(ctx context.Context, id *apiV2.R return nil, err } - s.scheduler.RemoveReportSchedule(id.GetId()) + if env.CentralWorkerEnabled.BooleanSetting() { + notifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, id.GetId()) + } else { + s.scheduler.RemoveReportSchedule(id.GetId()) + } return &apiV2.Empty{}, nil } @@ -378,6 +390,17 @@ func (s *serviceImpl) RunReport(ctx context.Context, req *apiV2.RunReportRequest return nil, err } + if env.CentralWorkerEnabled.BooleanSetting() { + reportID, err := s.persistSnapshotAndNotify(ctx, reportReq, pgNotify.ReportRequestSubmitted) + if err != nil { + return nil, err + } + return &apiV2.RunReportResponse{ + ReportConfigId: req.GetReportConfigId(), + ReportId: reportID, + }, nil + } + reportID, err := s.scheduler.SubmitReportRequest(ctx, reportReq, false) if err != nil { return nil, err @@ -403,6 +426,11 @@ func (s *serviceImpl) CancelReport(ctx context.Context, req *apiV2.ResourceByID) return nil, err } + if env.CentralWorkerEnabled.BooleanSetting() { + notifyWithRetry(ctx, s.db, pgNotify.ReportRequestCancelled, req.GetId()) + return &apiV2.Empty{}, nil + } + cancelled, err := s.scheduler.CancelReportRequest(ctx, req.GetId()) if err != nil { return nil, err @@ -484,7 +512,14 @@ func (s *serviceImpl) PostViewBasedReport(ctx context.Context, req *apiV2.Report return nil, err } - // Submit to scheduler. view-based reports are always on-demand, not re-submissions. + if env.CentralWorkerEnabled.BooleanSetting() { + reportID, err := s.persistSnapshotAndNotify(ctx, reportReq, pgNotify.ReportRequestSubmitted) + if err != nil { + return nil, err + } + return &apiV2.RunReportResponseViewBased{ReportID: reportID, RequestName: reportReq.ReportSnapshot.GetName()}, nil + } + reportID, err := s.scheduler.SubmitReportRequest(ctx, reportReq, false) if err != nil { return nil, errors.Wrapf(errox.ServerError, "Scheduler error:%s", err) @@ -604,3 +639,26 @@ func verifyNoUserSearchLabels(q *v1.Query) error { }) return err } + +func notifyWithRetry(ctx context.Context, db postgres.DB, channel, payload string) { + if db == nil { + return + } + err := retry.WithRetry(func() error { + return pgNotify.Notify(ctx, db, channel, payload) + }, retry.Tries(3), retry.BetweenAttempts(func(previousAttempt int) { + log.Errorf("pg_notify %s failed (attempt %d), retrying", channel, previousAttempt+1) + })) + if err != nil { + log.Errorf("pg_notify %s failed after retries: %v", channel, err) + } +} + +func (s *serviceImpl) persistSnapshotAndNotify(ctx context.Context, reportReq *reportGen.ReportRequest, channel string) (string, error) { + reportID, err := s.validator.PersistReportSnapshot(ctx, reportReq.ReportSnapshot) + if err != nil { + return "", err + } + notifyWithRetry(ctx, s.db, channel, reportID) + return reportID, nil +} diff --git a/central/reports/service/v2/service_impl_test.go b/central/reports/service/v2/service_impl_test.go index 55bfdb6861d10..8b7de86da3b21 100644 --- a/central/reports/service/v2/service_impl_test.go +++ b/central/reports/service/v2/service_impl_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/jackc/pgx/v5/pgconn" "github.com/pkg/errors" blobDSMocks "github.com/stackrox/rox/central/blob/datastore/mocks" notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" @@ -25,6 +26,8 @@ import ( "github.com/stackrox/rox/pkg/grpc/authn" mockIdentity "github.com/stackrox/rox/pkg/grpc/authn/mocks" "github.com/stackrox/rox/pkg/grpc/testutils" + postgresMocks "github.com/stackrox/rox/pkg/postgres/mocks" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" "github.com/stackrox/rox/pkg/protoassert" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/search" @@ -56,6 +59,7 @@ type ReportServiceTestSuite struct { notifierDataStore *notifierDSMocks.MockDataStore blobStore *blobDSMocks.MockDatastore scheduler *schedulerMocks.MockScheduler + db *postgresMocks.MockDB service Service } @@ -68,8 +72,9 @@ func (s *ReportServiceTestSuite) SetupTest() { s.notifierDataStore = notifierDSMocks.NewMockDataStore(s.mockCtrl) s.blobStore = blobDSMocks.NewMockDatastore(s.mockCtrl) s.scheduler = schedulerMocks.NewMockScheduler(s.mockCtrl) + s.db = postgresMocks.NewMockDB(s.mockCtrl) validator := validation.New(s.reportConfigDataStore, s.reportSnapshotDataStore, s.collectionDataStore, s.notifierDataStore) - s.service = New(s.reportConfigDataStore, s.reportSnapshotDataStore, s.collectionDataStore, s.notifierDataStore, s.scheduler, s.blobStore, validator) + s.service = New(s.reportConfigDataStore, s.reportSnapshotDataStore, s.collectionDataStore, s.notifierDataStore, s.scheduler, s.blobStore, validator, s.db) } func (s *ReportServiceTestSuite) TearDownSuite() { @@ -134,6 +139,38 @@ func (s *ReportServiceTestSuite) TestCreateReportConfiguration() { s.Error(err) } +func (s *ReportServiceTestSuite) TestCreateReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + requestConfig := fixtures.GetValidV2ReportConfigWithMultipleNotifiers() + requestConfig.Notifiers = nil + requestConfig.Schedule = nil + s.mockCollectionStoreCalls(requestConfig, true, false, false) + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + identity := mockIdentity.NewMockIdentity(s.mockCtrl) + identity.EXPECT().UID().Return(creator.GetId()).AnyTimes() + identity.EXPECT().FullName().Return(creator.GetName()).AnyTimes() + identity.EXPECT().FriendlyName().Return(creator.GetName()).AnyTimes() + role := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + role.EXPECT().GetAccessScope().Return(&storage.SimpleAccessScope{}).Times(1) + identity.EXPECT().Roles().Return([]permissions.ResolvedRole{role}).Times(1) + ctx := authn.ContextWithIdentity(s.ctx, identity, s.T()) + + storedConfig := fixtures.GetValidReportConfigWithMultipleNotifiersV2() + storedConfig.Notifiers = nil + storedConfig.Schedule = nil + storedConfig.Creator = creator + storedConfig.GetVulnReportFilters().AccessScopeRules = []*storage.SimpleAccessScope_Rules{{}} + s.reportConfigDataStore.EXPECT().AddReportConfiguration(ctx, storedConfig).Return(storedConfig.GetId(), nil).Times(1) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(ctx, storedConfig.GetId()).Return(storedConfig, true, nil).Times(1) + s.db.EXPECT().Exec(ctx, "SELECT pg_notify($1, $2)", pgNotify.ReportConfigChanged, storedConfig.GetId()). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) + + _, err := s.service.PostReportConfiguration(ctx, requestConfig) + s.NoError(err) +} + func (s *ReportServiceTestSuite) TestUpdateReportConfigurationError() { requester := &storage.SlimUser{ @@ -228,6 +265,30 @@ func (s *ReportServiceTestSuite) TestUpdateReportConfiguration() { } } +func (s *ReportServiceTestSuite) TestUpdateReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + requestConfig := fixtures.GetValidV2ReportConfigWithMultipleNotifiers() + requestConfig.Notifiers = nil + requestConfig.Schedule = nil + s.mockCollectionStoreCalls(requestConfig, true, false, true) + + storedConfig := fixtures.GetValidReportConfigWithMultipleNotifiersV2() + storedConfig.Notifiers = nil + storedConfig.Schedule = nil + storedConfig.Creator = creator + s.reportConfigDataStore.EXPECT().GetReportConfiguration(ctx, storedConfig.GetId()).Return(storedConfig, true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(ctx, gomock.Any()).Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportConfigDataStore.EXPECT().UpdateReportConfiguration(ctx, storedConfig).Return(nil).Times(1) + s.db.EXPECT().Exec(ctx, "SELECT pg_notify($1, $2)", pgNotify.ReportConfigChanged, storedConfig.GetId()). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) + + _, err := s.service.UpdateReportConfiguration(ctx, requestConfig) + s.NoError(err) +} + func (s *ReportServiceTestSuite) TestListReportConfigurations() { allAccessContext := sac.WithAllAccess(context.Background()) testCases := []struct { @@ -408,6 +469,22 @@ func (s *ReportServiceTestSuite) TestDeleteReportConfiguration() { } } +func (s *ReportServiceTestSuite) TestDeleteReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + const configID = "config-id" + ctx := sac.WithAllAccess(context.Background()) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), configID). + Return(fixtures.GetValidReportConfigWithMultipleNotifiersV2(), true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()).Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportConfigDataStore.EXPECT().RemoveReportConfiguration(ctx, configID).Return(nil).Times(1) + s.db.EXPECT().Exec(ctx, "SELECT pg_notify($1, $2)", pgNotify.ReportConfigChanged, configID). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) + + _, err := s.service.DeleteReportConfiguration(ctx, &apiV2.ResourceByID{Id: configID}) + s.NoError(err) +} + func (s *ReportServiceTestSuite) upsertReportConfigTestCases(isUpdate bool) []upsertTestCase { cases := []upsertTestCase{ { diff --git a/central/reports/service/v2/singleton.go b/central/reports/service/v2/singleton.go index d9ab9b95d3f1b..45e900480243b 100644 --- a/central/reports/service/v2/singleton.go +++ b/central/reports/service/v2/singleton.go @@ -27,7 +27,7 @@ func initialize() { } collectionDatastore, _ := collectionDS.Singleton() svc = New(reportConfigDS.Singleton(), snapshotDS.Singleton(), collectionDatastore, notifierDS.Singleton(), scheduler, - blobDS.Singleton(), validation.Singleton()) + blobDS.Singleton(), validation.Singleton(), globaldb.GetPostgres()) } // Singleton provides the instance of the service to register. diff --git a/central/reports/validation/validate.go b/central/reports/validation/validate.go index ef1d5e89f1987..dd9ebfc085b5c 100644 --- a/central/reports/validation/validate.go +++ b/central/reports/validation/validate.go @@ -23,6 +23,7 @@ import ( "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/grpc/authn" + "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/set" @@ -367,6 +368,74 @@ func (v *Validator) ValidateCancelReportRequest(reportID string, requester *stor return nil } +// PersistReportSnapshot validates that the user does not already have a pending +// report for the same config, sets the snapshot to WAITING, and persists it to +// the database. Returns the report ID. +func (v *Validator) PersistReportSnapshot(ctx context.Context, snapshot *storage.ReportSnapshot) (string, error) { + reportType := snapshot.GetType() + if snapshot.GetVulnReportFilters() != nil && snapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_ON_DEMAND { + hasPending, err := v.doesUserHavePendingReport(snapshot.GetReportConfigurationId(), snapshot.GetRequester().GetId(), reportType) + if err != nil { + return "", err + } + if hasPending { + return "", errors.Wrapf(errox.AlreadyExists, "User already has a report running for config ID '%s'", + snapshot.GetReportConfigurationId()) + } + } + + if snapshot.GetViewBasedVulnReportFilters() != nil { + hasPending, err := v.doesUserHaveViewBasedPendingReport(snapshot.GetRequester().GetId(), reportType) + if err != nil { + return "", err + } + if hasPending { + return "", errors.New("User already has a view based report queued") + } + } + + snapshot.ReportStatus.RunState = storage.ReportStatus_WAITING + snapshot.ReportStatus.QueuedAt = protocompat.TimestampNow() + reportID, err := v.snapshotDatastore.AddReportSnapshot(ctx, snapshot) + if err != nil { + return "", err + } + return reportID, nil +} + +func (v *Validator) doesUserHavePendingReport(configID string, userID string, reportType storage.ReportSnapshot_ReportType) (bool, error) { + query := search.NewQueryBuilder(). + AddExactMatches(search.ReportConfigID, configID). + AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()). + AddExactMatches(search.ReportRequestType, storage.ReportStatus_ON_DEMAND.String()). + AddExactMatches(search.ReportType, reportType.String()). + ProtoQuery() + snapshots, err := v.snapshotDatastore.SearchReportSnapshots(allAccessCtx, query) + if err != nil { + return false, err + } + for _, snap := range snapshots { + if snap.GetRequester().GetId() == userID { + return true, nil + } + } + return false, nil +} + +func (v *Validator) doesUserHaveViewBasedPendingReport(userID string, reportType storage.ReportSnapshot_ReportType) (bool, error) { + query := search.NewQueryBuilder(). + AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()). + AddExactMatches(search.ReportRequestType, storage.ReportStatus_VIEW_BASED.String()). + AddExactMatches(search.UserID, userID). + AddExactMatches(search.ReportType, reportType.String()). + ProtoQuery() + count, err := v.snapshotDatastore.Count(allAccessCtx, query) + if err != nil { + return false, err + } + return count > 0, nil +} + func collectionSnapshot(collection *storage.ResourceCollection) *storage.CollectionSnapshot { if collection == nil { return nil From f7040d25713db3d753c5b82d8d897ee5e3bc8b99 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 03:58:31 +0000 Subject: [PATCH 100/638] Update all dependencies (#22490) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/operator-index-build.yaml | 2 +- image/postgres/konflux.Dockerfile | 2 +- image/rhel/konflux.Dockerfile | 2 +- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/db/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 506b5e162521c..66345daafd02e 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787668499@sha256:f221165cd493d89c13ce384e8e58914571ea353f47a1443698d981494842ff83 + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787774815@sha256:1a755651ffe1a438f137418d183f18ebad527ec929206c17804f93490a97869e imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index ac4ccc99f4142..a31569928fdc1 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:034554e4e112a6754c1b9be3e0661d42231751da43fb8173a82e953de3d01057 AS final +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:175e5b0bffe9b9dabfb3e7fe2d707c64e6301d1e2dfe97acdac2e085561026de AS final USER root diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 016ac63b6b9ef..bad5909d7b109 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -59,7 +59,7 @@ ENV UI_PKG_INSTALL_EXTRA_ARGS="--ignore-scripts" RUN make -C ui build -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 25bbc5cdb464d..c2cc8245055c9 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -25,7 +25,7 @@ ENV GOEXPERIMENT=strictfipsruntime RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh ./roxctl && \ cp bin/linux_$(go env GOARCH)/roxctl image/bin/roxctl -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index aa94ddd450aa6..d54bfa3772553 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -17,7 +17,7 @@ ENV CI=1 GOFLAGS="" CGO_ENABLED=1 RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/main.go image/bin/operator -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index 1abe997f15f1b..a999f3a755be8 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:034554e4e112a6754c1b9be3e0661d42231751da43fb8173a82e953de3d01057 +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:175e5b0bffe9b9dabfb3e7fe2d707c64e6301d1e2dfe97acdac2e085561026de ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index df250db70de0f..c57f45f10b9ea 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -16,7 +16,7 @@ WORKDIR /src RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-scripts -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7e7f79ab747bf2b452e3043dd89f388e92be4c7fdcc8b815b58adf6c99c39c95 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer From 8b57f33708dd588f21f1005070830c3947f22753 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 04:08:55 +0000 Subject: [PATCH 101/638] Refresh RPM lockfiles (#22432) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 400 ++++++++++++++++++++++++------------------------- 1 file changed, 200 insertions(+), 200 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index ca6b21c8a87cf..76ad27aa7bf30 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -39,48 +39,48 @@ arches: name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/go-toolset-1.26.5-1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 12473 - checksum: sha256:620f3204ee99c57a87354dfb2ba566b7c1d099915a67d80550fccdada823c2f6 + size: 12361 + checksum: sha256:7c4e8b7e0647a459b3b04e9deb17b6d5d02a43fa1bff0e8dc782f48e08fccd30 name: go-toolset - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-1.26.5-1.el9_8.aarch64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-1.26.7-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1519189 - checksum: sha256:82f201dbf4dc69fb60a6c3d2a0a19823f64c6194a1d4cd1ed035dc2b90fb7558 + size: 1520214 + checksum: sha256:2f1b635e07c0f74ac53e9a79e28e287ae47742599deec1138aa7a27f94b4d5c9 name: golang - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-bin-1.26.5-1.el9_8.aarch64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 41545990 - checksum: sha256:79a9efc6c72b374e0355ae27ca6c15cff222a430e25399eb48a2d5b62043c3eb + size: 42741902 + checksum: sha256:aff04f0bea0253c15c37d1f008d2826884cfdc401714c0d884c3172f2affce01 name: golang-bin - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-race-1.26.5-1.el9_8.aarch64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1743341 - checksum: sha256:0e74ba670a978e63f3af9511641730f65913d4a58bf1c2d62c85fcb651912662 + size: 1743223 + checksum: sha256:cb3fed044deab06fcf00a3426b635f4562cdfd6f5937912d9e2a854b88498c72 name: golang-race - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-src-1.26.5-1.el9_8.noarch.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 12881624 - checksum: sha256:4fc1f95f29566904290cad635094a1d07c5dc3421c4d0f3fb8efebe18bde2bc0 + size: 12881267 + checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 name: golang-src - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.aarch64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2872021 - checksum: sha256:badc73a635a0552aa4d86eeaba804ccbbe811065e4fbbf3531fe1f9cf646fcd9 + size: 2874189 + checksum: sha256:4eaed7563988ff556b2c52e8e90195f42dadeaae5c5c7e849af2d3f5a87afdd0 name: kernel-headers - evr: 5.14.0-687.41.1.el9_8 - sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm + evr: 5.14.0-687.42.1.el9_8 + sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -480,13 +480,13 @@ arches: name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libattr-2.5.1-3.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 20696 - checksum: sha256:b6a73c9da522df55690b886c0a9af39338571d34ee6f0d4d9b994c8289df3412 + size: 23276 + checksum: sha256:ec08036348dbe2ee41645bdb96eb485b9db5121ec0524258b0639426a22a49bc name: libattr - evr: 2.5.1-3.el9 - sourcerpm: attr-2.5.1-3.el9.src.rpm + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 113931 @@ -1033,12 +1033,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/g/golang-1.26.5-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 36513301 - checksum: sha256:45d5f6cd4321709dd8c2f2c1deccd4727573af5da20d750439966f5c627b8acf + size: 36535123 + checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 name: golang - evr: 1.26.5-1.el9_8 + evr: 1.26.7-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 846236 @@ -1093,12 +1093,12 @@ arches: checksum: sha256:b140deb68f46517b0093e18969d8fb42a17216064fb2ee5783e9316d786430db name: acl evr: 2.4.0-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/a/attr-2.5.1-3.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/a/attr-2.6.0-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 482234 - checksum: sha256:5171534e7de11df197f3c5e08658544983198288e04624c739b5c3d9db07b59c + size: 389805 + checksum: sha256:9bdd2e605e0ddc9818382bd95644594fa8b8eedfe08048303093db13cde9963c name: attr - evr: 2.5.1-3.el9 + evr: 2.6.0-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/a/audit-3.1.5-8.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 1275064 @@ -1279,12 +1279,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152395202 - checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec + size: 152411531 + checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 name: kernel - evr: 5.14.0-687.41.1.el9_8 + evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1622,10 +1622,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/7cf8b3393b778ce2e2b45da66e8112c75fca9fc036d9890c28f42445d7f63d5f-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0b335b05c4b4910b48f590d68defa760399c9416dab4f2d1dda50fa8499dc40c-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 64613 - checksum: sha256:7cf8b3393b778ce2e2b45da66e8112c75fca9fc036d9890c28f42445d7f63d5f + size: 65051 + checksum: sha256:0b335b05c4b4910b48f590d68defa760399c9416dab4f2d1dda50fa8499dc40c - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1663,48 +1663,48 @@ arches: name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/go-toolset-1.26.5-1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 12493 - checksum: sha256:9064749e7f12c74ec1cd5603ea42227f7ab850b30ab977f18a91f1b0bf917974 + size: 12381 + checksum: sha256:ff9550d877b563eb05b3a49b4f352a1b578a42cd234704cbc43db08825b9d632 name: go-toolset - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-1.26.5-1.el9_8.ppc64le.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-1.26.7-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1520603 - checksum: sha256:5b1cacea443746d5c5cdfa02d867ec58d5a1f6a559fdf5b9d4afdcf085f9548b + size: 1520537 + checksum: sha256:d09b9280e3d43c13454aadceb3c00e0997063fc7d621b7b8a2359b4c737f619f name: golang - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-bin-1.26.5-1.el9_8.ppc64le.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 42717141 - checksum: sha256:84d77e1d2e804e06840ef8039ab49f22f7f8cc688f2301d1090d5d705afab096 + size: 43924293 + checksum: sha256:db278a5a095d81a8100e884f5ffebcff5cca10b7c4f3fd3f9230b596c667026e name: golang-bin - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-race-1.26.5-1.el9_8.ppc64le.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1663710 - checksum: sha256:e2c691d291865cf6f46e91b170771493519efa6e250f0eaf51a6342f487a86b1 + size: 1663611 + checksum: sha256:ed9e4c84393cf7875bf23a34489fa235610df93ebb97dc9d2c52bb9d3d0733f4 name: golang-race - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-src-1.26.5-1.el9_8.noarch.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 12881624 - checksum: sha256:4fc1f95f29566904290cad635094a1d07c5dc3421c4d0f3fb8efebe18bde2bc0 + size: 12881267 + checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 name: golang-src - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.ppc64le.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2895853 - checksum: sha256:77cd0483c762c111190ae1b006ec02cb176aba064c1846f733c7edf859eed410 + size: 2898017 + checksum: sha256:57297ffdb162abd9cd49914972a2d43bda711f9d4ead2a90cfc9331f93daf0cf name: kernel-headers - evr: 5.14.0-687.41.1.el9_8 - sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm + evr: 5.14.0-687.42.1.el9_8 + sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -2104,13 +2104,13 @@ arches: name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libattr-2.5.1-3.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 21501 - checksum: sha256:a2398158adad2016fca3d0d2c272e027b8279020992a349664caf6a2299ec50b + size: 24709 + checksum: sha256:9931133a1f833d158bc8098815924c1b15cf6c024bc89e0f55571ef906ae8f43 name: libattr - evr: 2.5.1-3.el9 - sourcerpm: attr-2.5.1-3.el9.src.rpm + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libblkid-2.37.4-25.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 130845 @@ -2664,12 +2664,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/g/golang-1.26.5-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 36513301 - checksum: sha256:45d5f6cd4321709dd8c2f2c1deccd4727573af5da20d750439966f5c627b8acf + size: 36535123 + checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 name: golang - evr: 1.26.5-1.el9_8 + evr: 1.26.7-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 846236 @@ -2724,12 +2724,12 @@ arches: checksum: sha256:b140deb68f46517b0093e18969d8fb42a17216064fb2ee5783e9316d786430db name: acl evr: 2.4.0-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/a/attr-2.5.1-3.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/a/attr-2.6.0-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 482234 - checksum: sha256:5171534e7de11df197f3c5e08658544983198288e04624c739b5c3d9db07b59c + size: 389805 + checksum: sha256:9bdd2e605e0ddc9818382bd95644594fa8b8eedfe08048303093db13cde9963c name: attr - evr: 2.5.1-3.el9 + evr: 2.6.0-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/a/audit-3.1.5-8.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 1275064 @@ -2910,12 +2910,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152395202 - checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec + size: 152411531 + checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 name: kernel - evr: 5.14.0-687.41.1.el9_8 + evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3259,10 +3259,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/0621e21f421625934a7a35529a4daa5df035b14e50ebcc270955cee939299f52-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/45cfc2702162c5dcf3a4586d596601c70a645d75236fbbfb4e6307e251d5dfa1-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 64720 - checksum: sha256:0621e21f421625934a7a35529a4daa5df035b14e50ebcc270955cee939299f52 + size: 64975 + checksum: sha256:45cfc2702162c5dcf3a4586d596601c70a645d75236fbbfb4e6307e251d5dfa1 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3300,48 +3300,48 @@ arches: name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/go-toolset-1.26.5-1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 12465 - checksum: sha256:157c5faa4d40c5890a19e27d6c3064f063eba47ca8b1f6a426627b6425d01321 + size: 12353 + checksum: sha256:aba50425a0797a21ef3c7a5bc054ed4afb3c96a8802ea0758db89a4bf974a6f5 name: go-toolset - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-1.26.5-1.el9_8.s390x.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-1.26.7-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1519629 - checksum: sha256:5b8e471be65daee3807c8b4d0ecf2d74c31810fe77bd743a44604544efebf075 + size: 1520130 + checksum: sha256:e7bee02758864c0cb61bdd3e8a1318b5c32195df2ec07bb8e914639b381acbcb name: golang - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-bin-1.26.5-1.el9_8.s390x.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 45137069 - checksum: sha256:50e9ffabb62ef294a14a96fa58cbc3dff1c78ed612d22fc9f1f2c3ffb94908ca + size: 46549574 + checksum: sha256:e03b18d614adf56123dc37c84535013b366e645e270ec105c2489e6a569703bd name: golang-bin - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-race-1.26.5-1.el9_8.s390x.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1711739 - checksum: sha256:e94275b37bb5e73b2fba01d15272ab89a1edbaccbe7853598afcaedfe10be7e5 + size: 1711630 + checksum: sha256:7e441ca5a2954e97ffb978489bec3b13378708fde3fe471f7aca78f3b6089094 name: golang-race - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-src-1.26.5-1.el9_8.noarch.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 12881624 - checksum: sha256:4fc1f95f29566904290cad635094a1d07c5dc3421c4d0f3fb8efebe18bde2bc0 + size: 12881267 + checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 name: golang-src - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.s390x.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2902017 - checksum: sha256:5078b1c5b552c0f2f738e8aae65d87bac7d3060f4eaa028d3d6c31b69603b292 + size: 2904169 + checksum: sha256:1ea7397bda094b9a2848af9b91e46731863b4e7e820c9c1fffa9e7a4fa07672d name: kernel-headers - evr: 5.14.0-687.41.1.el9_8 - sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm + evr: 5.14.0-687.42.1.el9_8 + sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -3741,13 +3741,13 @@ arches: name: libatomic evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libattr-2.5.1-3.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 20575 - checksum: sha256:4013df08b49661a8592c2c57428f8040f8e2397379dfc02fa9a125cbf8de44c6 + size: 23584 + checksum: sha256:a0bb5b85dfc28983f2522c860fcde0792ec03398f1d39ef019d134b840f88165 name: libattr - evr: 2.5.1-3.el9 - sourcerpm: attr-2.5.1-3.el9.src.rpm + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libblkid-2.37.4-25.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 111331 @@ -4288,12 +4288,12 @@ arches: evr: 1.2.11-40.el9 sourcerpm: zlib-1.2.11-40.el9.src.rpm source: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/g/golang-1.26.5-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 36513301 - checksum: sha256:45d5f6cd4321709dd8c2f2c1deccd4727573af5da20d750439966f5c627b8acf + size: 36535123 + checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 name: golang - evr: 1.26.5-1.el9_8 + evr: 1.26.7-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 846236 @@ -4348,12 +4348,12 @@ arches: checksum: sha256:b140deb68f46517b0093e18969d8fb42a17216064fb2ee5783e9316d786430db name: acl evr: 2.4.0-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/a/attr-2.5.1-3.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/a/attr-2.6.0-1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 482234 - checksum: sha256:5171534e7de11df197f3c5e08658544983198288e04624c739b5c3d9db07b59c + size: 389805 + checksum: sha256:9bdd2e605e0ddc9818382bd95644594fa8b8eedfe08048303093db13cde9963c name: attr - evr: 2.5.1-3.el9 + evr: 2.6.0-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/a/audit-3.1.5-8.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 1275064 @@ -4534,12 +4534,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152395202 - checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec + size: 152411531 + checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 name: kernel - evr: 5.14.0-687.41.1.el9_8 + evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4877,10 +4877,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/28d710080e2d47dce0fb30a77c16850d99fbe79f86acc634be3809452de38b05-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/f186465d263407d3495046b9360b8cebfc9452a198d5442945b9e3c80bcf6c26-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 62927 - checksum: sha256:28d710080e2d47dce0fb30a77c16850d99fbe79f86acc634be3809452de38b05 + size: 63167 + checksum: sha256:f186465d263407d3495046b9360b8cebfc9452a198d5442945b9e3c80bcf6c26 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4925,48 +4925,48 @@ arches: name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/go-toolset-1.26.5-1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 12505 - checksum: sha256:6fe04126a34d4802ec9271403682b126f8861d88aefec0ddbaca7da483bf39e8 + size: 12393 + checksum: sha256:e8d30ba284a372bbdbf1f899231ad3c59ddac6a783415f0784d194a1f4d314f1 name: go-toolset - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-1.26.5-1.el9_8.x86_64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-1.26.7-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1520284 - checksum: sha256:ffb6333998d5c61e80202fe8fa5b427a8dbf126b2b5cebf4e687e269669c1dbe + size: 1520026 + checksum: sha256:ab786d635b438822c9fa3ddc2a0f056e1cf9ab83bcfcca285c2ac9714a9b903c name: golang - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-bin-1.26.5-1.el9_8.x86_64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 46263591 - checksum: sha256:f177bf6c26824c1a20693eb8e2d2a16b42977a7d9f3bd288b508afad65812564 + size: 47194775 + checksum: sha256:d68cc646382e339b5860d44cdf3c23c135523dc52a5cf52c4919f25130ad7c07 name: golang-bin - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-race-1.26.5-1.el9_8.x86_64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1743103 - checksum: sha256:00c68c81e26c9acc38d80b838f0e782bb587a28757e7f11ecd725c4499ea0777 + size: 1742998 + checksum: sha256:c4f7bcb59a53fb8881935b88869ccb6cac3facf6444b344e2f5adb76e16cf932 name: golang-race - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-src-1.26.5-1.el9_8.noarch.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 12881624 - checksum: sha256:4fc1f95f29566904290cad635094a1d07c5dc3421c4d0f3fb8efebe18bde2bc0 + size: 12881267 + checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 name: golang-src - evr: 1.26.5-1.el9_8 - sourcerpm: golang-1.26.5-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.41.1.el9_8.x86_64.rpm + evr: 1.26.7-1.el9_8 + sourcerpm: golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2911313 - checksum: sha256:7fab4963bb2f80e238051586a6df2b0ffea787daf7ef59659ba8d0fa3648f46e + size: 2913473 + checksum: sha256:6a2a1bdb7d755358f35a283de880abb48b359aedd5fdfcd2da80bbe03541688c name: kernel-headers - evr: 5.14.0-687.41.1.el9_8 - sourcerpm: kernel-5.14.0-687.41.1.el9_8.src.rpm + evr: 5.14.0-687.42.1.el9_8 + sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5345,13 +5345,13 @@ arches: name: libacl evr: 2.4.0-1.el9_8 sourcerpm: acl-2.4.0-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libattr-2.5.1-3.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libattr-2.6.0-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 20786 - checksum: sha256:6519f028915fbd7ee0474f0bf4e98e35f04b5d0cf7be9f66c0cb9eafac0b8c5a + size: 23752 + checksum: sha256:9e37537f690c748f7f05faa80966072f118ec722e38ef332fe19cf22b087349f name: libattr - evr: 2.5.1-3.el9 - sourcerpm: attr-2.5.1-3.el9.src.rpm + evr: 2.6.0-1.el9_8 + sourcerpm: attr-2.6.0-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libblkid-2.37.4-25.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 114192 @@ -5898,12 +5898,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/g/golang-1.26.5-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 36513301 - checksum: sha256:45d5f6cd4321709dd8c2f2c1deccd4727573af5da20d750439966f5c627b8acf + size: 36535123 + checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 name: golang - evr: 1.26.5-1.el9_8 + evr: 1.26.7-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 846236 @@ -5958,12 +5958,12 @@ arches: checksum: sha256:b140deb68f46517b0093e18969d8fb42a17216064fb2ee5783e9316d786430db name: acl evr: 2.4.0-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/a/attr-2.5.1-3.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/a/attr-2.6.0-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 482234 - checksum: sha256:5171534e7de11df197f3c5e08658544983198288e04624c739b5c3d9db07b59c + size: 389805 + checksum: sha256:9bdd2e605e0ddc9818382bd95644594fa8b8eedfe08048303093db13cde9963c name: attr - evr: 2.5.1-3.el9 + evr: 2.6.0-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/a/audit-3.1.5-8.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 1275064 @@ -6144,12 +6144,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.41.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152395202 - checksum: sha256:59ce85c21f55f88b54cf4cb1c43d7d9a9551c96b568b998b833df3f1072be2ec + size: 152411531 + checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 name: kernel - evr: 5.14.0-687.41.1.el9_8 + evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6487,7 +6487,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/95263aa61868e76c0831959590c4ef16bf051151b74e33442a6bef37d14c0366-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/5318852ad7d83cc0af24d7be94083c7668329ad1405e50de53c854035760a048-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 67289 - checksum: sha256:95263aa61868e76c0831959590c4ef16bf051151b74e33442a6bef37d14c0366 + size: 67412 + checksum: sha256:5318852ad7d83cc0af24d7be94083c7668329ad1405e50de53c854035760a048 From 7654309581ef274b116268ac4f52bf8f43de96be Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 27 Aug 2026 06:51:48 +0200 Subject: [PATCH 102/638] tests(perf-scale): update stale step in comments (#22476) --- scripts/ci/jobs/ocp-perf-scale-tests-post.sh | 2 +- scripts/ci/jobs/ocp-perf-scale-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh index f387169edc848..ca83582ff7713 100755 --- a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh +++ b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh @@ -53,7 +53,7 @@ fi # Set API_ENDPOINT since we know it from the port-forward export API_ENDPOINT="localhost:8000" -# Get admin password from SHARED_DIR (saved by stackrox-install-helm step) +# Get admin password from SHARED_DIR (saved by stackrox-install-roxie step) if [[ -n "${SHARED_DIR:-}" && -f "${SHARED_DIR}/rox_admin_password" ]]; then export ROX_ADMIN_PASSWORD="$(cat "${SHARED_DIR}/rox_admin_password")" info "Retrieved admin password from SHARED_DIR" diff --git a/scripts/ci/jobs/ocp-perf-scale-tests.sh b/scripts/ci/jobs/ocp-perf-scale-tests.sh index 08f143494c2f6..1c0194ef6887a 100755 --- a/scripts/ci/jobs/ocp-perf-scale-tests.sh +++ b/scripts/ci/jobs/ocp-perf-scale-tests.sh @@ -19,7 +19,7 @@ info "Will poll for: $(awk '{print $1}' "${image_list}")" poll_for_system_test_images 3600 -# Export the tag for stackrox-install-helm to use +# Export the tag for stackrox-install-roxie to use if [[ -n "${SHARED_DIR:-}" ]]; then echo "${MAIN_IMAGE_TAG}" > "${SHARED_DIR}/acs_image_tag" info "Exported ACS_IMAGE_TAG=${MAIN_IMAGE_TAG} to ${SHARED_DIR}/acs_image_tag" From 507dcef5de2210aded2cd587e567b6da04107f9b Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 27 Aug 2026 06:55:14 +0200 Subject: [PATCH 103/638] ROX-36541: use roxie in scale tests (#22443) --- tests/e2e/run-scale.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/e2e/run-scale.sh b/tests/e2e/run-scale.sh index 82d06b803668c..0fb12cb9c5cb6 100755 --- a/tests/e2e/run-scale.sh +++ b/tests/e2e/run-scale.sh @@ -38,8 +38,19 @@ scale_test() { } deploy_stackrox_in_scale_mode() { - "$ROOT/deploy/k8s/deploy.sh" - + local roxie_envrc; roxie_envrc="$(mktemp)" + + "${ROOT}/scripts/roxie.sh" --verbose deploy \ + --single-namespace \ + --resources ci \ + --pause-reconciliation \ + --envrc "$roxie_envrc" + + extend_roxie_envrc "$roxie_envrc" + # shellcheck source=/dev/null + source "$roxie_envrc" + rm -f "$roxie_envrc" + DEPLOY_DIR="deploy/${ORCHESTRATOR_FLAVOR}" \ export_central_basic_auth_creds From d3844ccc58a8b845236563b289b586cdd99a4a1f Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 27 Aug 2026 06:57:35 +0200 Subject: [PATCH 104/638] ROX-33128: operator-e2e-tests use helm exclusively (#22442) --- operator/Makefile | 27 ++++++--------------- operator/hack/generate-and-install-chart.sh | 22 +++++++++++++++++ operator/hack/install-4.10.sh | 18 -------------- operator/hack/upgrade-autoselect.sh | 2 +- operator/tests/run.sh | 8 +++--- 5 files changed, 34 insertions(+), 43 deletions(-) create mode 100755 operator/hack/generate-and-install-chart.sh delete mode 100755 operator/hack/install-4.10.sh diff --git a/operator/Makefile b/operator/Makefile index a611b46489bb1..8cfb88a07f6a3 100755 --- a/operator/Makefile +++ b/operator/Makefile @@ -443,24 +443,18 @@ deploy-previous-via-olm: kuttl bundle-post-process ## Deploy replaced version of set -x ;\ KUTTL=$(KUTTL) PATH="$(PROJECT_DIR)/hack:$${PATH}" ./hack/olm-operator-install.sh $(TEST_NAMESPACE) $(INDEX_IMG_BASE) $(INDEX_IMG_TAG) $${replaced_version} $(INSTALL_CHANNEL) -.PHONY: deploy-via-installer -deploy-via-installer: ## Deploy current version of operator via distribution manifest file. - ./hack/retry-kubectl.sh &2 "Usage: $0 " + echo >&2 "Example: $0 4.11.2" + echo >&2 "Note: may optionally include the leading 'v'" + exit 1 +fi + +# Strip optional leading 'v'. +version="${1#v}" + +# The rhacs-eng chart is not preserved anywhere on releases, so we produce it on the fly. +dir="$(mktemp -d)" +echo >&2 "Deploying operator version ${version} from a temporary checkout at ${dir}" + +git worktree add "${dir}" "${version}" +trap 'git worktree remove --force "${dir}"' EXIT + +make -C "${dir}/operator" chart deploy-via-chart VERSION="${version}" diff --git a/operator/hack/install-4.10.sh b/operator/hack/install-4.10.sh deleted file mode 100755 index 3c1ec9cc64728..0000000000000 --- a/operator/hack/install-4.10.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Temporary script to install 4.10 operator to test upgrades to latest version using helm chart. -# Operator is not supported downstream on non-OpenShift platforms before 4.11, but this lets us roughly test -# the upgrade path from the upstream tech preview in 4.10 to the helm chart. -# TODO(ROX-33128): delete and just use helm after release 4.11 ships -set -euo pipefail - -version="4.10.0" - -dir="$(mktemp -d)" -echo >&2 "Deploying operator version ${version} from a temporary checkout at ${dir}" - -git worktree add "${dir}" "${version}" -trap 'git worktree remove --force "${dir}"' EXIT - -cd "${dir}" -export VERSION="${version}" ROX_PRODUCT_BRANDING=RHACS_BRANDING -make -C operator/ build-installer deploy-via-installer TEST_NAMESPACE="rhacs-operator-system" diff --git a/operator/hack/upgrade-autoselect.sh b/operator/hack/upgrade-autoselect.sh index 1dea066a2a1f5..ff7228e5daec4 100755 --- a/operator/hack/upgrade-autoselect.sh +++ b/operator/hack/upgrade-autoselect.sh @@ -9,7 +9,7 @@ openshift*) target="upgrade-via-olm" ;; *) - target="upgrade-via-chart" + target="deploy-via-chart" ;; esac make -C "${ROOT_DIR}/operator" "${target}" "$@" diff --git a/operator/tests/run.sh b/operator/tests/run.sh index 7aa223b5d6ce6..2f87498cc12a9 100755 --- a/operator/tests/run.sh +++ b/operator/tests/run.sh @@ -39,12 +39,12 @@ _EO_KUTTL_HELP_ "${kuttl_help}" \ "make" "-C" "operator" "deploy-previous-via-olm" TEST_NAMESPACE="rhacs-operator-system" else - # TODO(ROX-33128): change to use helm after release 4.11 junit_wrap deploy-previous-operator \ - "Deploy version 4.10 of the operator using install manifest." \ + "Deploy previous version of the operator using helm chart." \ "${kuttl_help}" \ - ./operator/hack/install-4.10.sh - info "Preparing operator helm chart" + "env" "ROX_PRODUCT_BRANDING=RHACS_BRANDING" \ + "make" "-C" "operator" "deploy-previous-via-chart" TEST_NAMESPACE="rhacs-operator-system" + info "Preparing current operator helm chart" junit_wrap prepare-operator-helm-chart \ "Prepare operator helm chart." \ "${kuttl_help}" \ From b99829a0e4628af23d36ceecba00985f041231f8 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 27 Aug 2026 08:18:57 +0200 Subject: [PATCH 105/638] ROX-36490: Enable VM enhanced data model by default (#22405) --- CHANGELOG.md | 1 + .../datastore/telemetry_test.go | 3 +- pkg/features/list.go | 2 +- tests/e2e/lib-compat.sh | 1 + tests/e2e/lib.sh | 3 + .../virtualMachineCves/getVM.json | 30 +++ .../virtualMachineCves/getVirtualMachine.json | 116 ----------- .../virtualMachineCves/listVMs.json | 84 ++++++++ .../listVirtualMachineComponents-vm-002.json | 18 ++ .../listVirtualMachineComponents.json | 44 ++++ .../listVirtualMachineCves-vm-002.json | 29 +++ .../listVirtualMachineCves.json | 80 ++++++++ .../listVirtualMachines.json | 188 ------------------ .../VirtualMachineCve.helpers.ts | 36 +++- .../virtualMachineCvesOverviewPage.test.ts | 72 +++++-- .../virtualMachinePage.test.ts | 130 +++++++----- 16 files changed, 452 insertions(+), 385 deletions(-) create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVM.json delete mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVirtualMachine.json create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVMs.json create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents-vm-002.json create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents.json create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves-vm-002.json create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves.json delete mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachines.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a8f64f3d1b7..7fa0353dfec23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35079: installation of the `app.k8s.io/v1beta1/Application` resource when central is installed is deprecated. It will be removed in a future release. ### Technical Changes +- ROX-36490: The virtual machine enhanced data model (`ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL`) is now enabled by default. - ROX-32969: The `roxctl-linux` symlink has been removed from the `/assets/downloads/cli/` directory inside the main container image. Only the architecture-specific binaries (`roxctl-linux-amd64`, `roxctl-linux-arm64`, etc.) remain. This change does not affect CLI downloads from the Central UI or any other supported download path. - ROX-33078: Fixed telemetry gatherer failing to report database size metrics when using an external database. The database name is now read from the connection config instead of using the hardcoded default. - ROX-35006: Go runtime upgraded to 1.26. Unbracketed IPv6 addresses (e.g. `2001:db8::1`) are no longer accepted; use bracketed format instead (e.g. `[2001:db8::1]:443`). diff --git a/central/virtualmachine/datastore/telemetry_test.go b/central/virtualmachine/datastore/telemetry_test.go index 5c9fa9ce10b27..0d8f01b6861a2 100644 --- a/central/virtualmachine/datastore/telemetry_test.go +++ b/central/virtualmachine/datastore/telemetry_test.go @@ -25,9 +25,10 @@ var ( arbitraryNowFunc = func() time.Time { return arbitraryNow } ) +// TestVirtualMachineTelemetry covers the v1 gatherer, which only runs when the enhanced data model is off. func TestVirtualMachineTelemetry(t *testing.T) { - // Ensure feature flag is enabled for these tests t.Setenv(features.VirtualMachines.EnvVar(), "true") + t.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), "false") tests := map[string]struct { vms []*storage.VirtualMachine diff --git a/pkg/features/list.go b/pkg/features/list.go index 30473d2703d7d..2fa41b0d09857 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -132,7 +132,7 @@ var ( NodeVulnerabilityReports = registerFeature("Enables interface for scheduled node vulnerability reports", "ROX_NODE_VULNERABILITY_REPORTS") // VirtualMachinesEnhancedDataModel enables usage of the enhanced and more flexible data model - VirtualMachinesEnhancedDataModel = registerFeature("Enables virtual machine enhanced data model", "ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL") + VirtualMachinesEnhancedDataModel = registerFeature("Enables virtual machine enhanced data model", "ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL", enabled) // InitContainerSupport enables extraction, scanning, and evaluation of init containers in deployments. InitContainerSupport = registerFeature("Enable init container support", "ROX_INIT_CONTAINER_SUPPORT", enabled) diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index 2bb556537bcec..df72fd80814f4 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -199,6 +199,7 @@ collect_feature_flags() { env_with_default ROX_NODE_VULNERABILITY_REPORTS "true" env_with_default ROX_TAILORED_PROFILES "true" env_with_default ROX_INIT_CONTAINER_SUPPORT "true" + env_with_default ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL "true" env_with_default ROX_LABEL_BASED_POLICY_SCOPING "true" env_with_default ROX_POLICY_CRITERIA_MODAL "true" env_with_default ROX_VULN_MGMT_LEGACY_SNOOZE "true" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index fd8a36d57a27e..466bbeeffd197 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -443,6 +443,7 @@ export_test_environment() { ci_export ROX_NETFLOW_BATCHING "${ROX_NETFLOW_BATCHING:-true}" ci_export ROX_NETFLOW_CACHE_LIMITING "${ROX_NETFLOW_CACHE_LIMITING:-true}" ci_export ROX_INIT_CONTAINER_SUPPORT "${ROX_INIT_CONTAINER_SUPPORT:-true}" + ci_export ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL "${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}" ci_export ROX_UI_SECRETS_PAGE_MIGRATION "${ROX_UI_SECRETS_PAGE_MIGRATION:-true}" ci_export SCANNER_V4_VULN_READINESS "${SCANNER_V4_VULN_READINESS:-true}" @@ -620,6 +621,8 @@ deploy_central_via_operator() { customize_envVars+=$'\n value: "true"' customize_envVars+=$'\n - name: ROX_INIT_CONTAINER_SUPPORT' customize_envVars+=$'\n value: "true"' + customize_envVars+=$'\n - name: ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL' + customize_envVars+=$'\n value: "'"${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}"'"' customize_envVars+=$'\n - name: ROX_UI_SECRETS_PAGE_MIGRATION' customize_envVars+=$'\n value: "'"${ROX_UI_SECRETS_PAGE_MIGRATION}"'"' if [[ "${ROX_VIRTUAL_MACHINES:-}" == "true" ]]; then diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVM.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVM.json new file mode 100644 index 0000000000000..cdfe8aeab670f --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVM.json @@ -0,0 +1,30 @@ +{ + "id": "vm-001", + "name": "cypress-vm-1", + "namespace": "default", + "clusterId": "cluster-001", + "clusterName": "production-cluster", + "guestOs": "Red Hat Enterprise Linux 9.2", + "state": "VM_STATE_RUNNING", + "lastUpdated": "2025-04-15T10:30:00.000Z", + "facts": { + "guestOS": "Red Hat Enterprise Linux 9.2", + "ipAddresses": "10.0.1.50", + "nodeName": "worker-node-1", + "activePodCount": "3", + "activePods": "3", + "bootOrder": "disk,network" + }, + "annotations": {}, + "labels": {}, + "vsockCid": 3, + "notes": [], + "latestScan": { + "scanId": "scan-001", + "scanOs": "rhel:9", + "scanTime": "2025-04-15T10:30:00.000Z", + "topCvss": 9.8, + "scanNotes": [] + }, + "agentStatus": "AGENT_STATUS_ACTIVE" +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVirtualMachine.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVirtualMachine.json deleted file mode 100644 index d70646ab6c984..0000000000000 --- a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVirtualMachine.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "id": "vm-001", - "namespace": "default", - "name": "cypress-vm-1", - "clusterId": "cluster-001", - "clusterName": "production-cluster", - "facts": { - "guestOS": "Red Hat Enterprise Linux 9.2", - "ipAddresses": "10.0.1.50", - "nodeName": "worker-node-1", - "activePodCount": "3", - "bootOrder": "disk,network" - }, - "scan": { - "scanTime": "2025-04-15T10:30:00.000Z", - "operatingSystem": "rhel:9", - "components": [ - { - "name": "openssl", - "version": "3.0.7-20.el9", - "vulns": [ - { - "cve": "CVE-2024-0001", - "advisory": { - "name": "RHSA-2024:0001", - "link": "https://access.redhat.com/errata/RHSA-2024:0001" - }, - "cvss": 9.8, - "summary": "Critical vulnerability in openssl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0001", - "fixedBy": "3.0.7-25.el9", - "publishedOn": "2024-01-15T00:00:00.000Z", - "lastModified": "2024-02-01T00:00:00.000Z", - "firstSystemOccurrence": "2024-01-20T00:00:00.000Z", - "severity": "CRITICAL_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.85, - "epssPercentile": 0.97 - } - }, - { - "cve": "CVE-2024-0002", - "advisory": { - "name": "RHSA-2024:0002", - "link": "https://access.redhat.com/errata/RHSA-2024:0002" - }, - "cvss": 5.3, - "summary": "Moderate vulnerability in openssl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0002", - "fixedBy": "3.0.7-25.el9", - "publishedOn": "2024-02-10T00:00:00.000Z", - "lastModified": "2024-03-01T00:00:00.000Z", - "firstSystemOccurrence": "2024-02-15T00:00:00.000Z", - "severity": "MODERATE_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.12, - "epssPercentile": 0.45 - } - } - ], - "source": "OS", - "topCvss": 9.8, - "riskScore": 9.8, - "architecture": "x86_64", - "notes": [] - }, - { - "name": "curl", - "version": "7.76.1-26.el9", - "vulns": [ - { - "cve": "CVE-2024-0003", - "advisory": { - "name": "RHSA-2024:0003", - "link": "https://access.redhat.com/errata/RHSA-2024:0003" - }, - "cvss": 7.5, - "summary": "Important vulnerability in curl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0003", - "fixedBy": "7.76.1-29.el9", - "publishedOn": "2024-03-01T00:00:00.000Z", - "lastModified": "2024-03-15T00:00:00.000Z", - "firstSystemOccurrence": "2024-03-05T00:00:00.000Z", - "severity": "IMPORTANT_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.45, - "epssPercentile": 0.82 - } - } - ], - "source": "OS", - "topCvss": 7.5, - "riskScore": 7.5, - "architecture": "x86_64", - "notes": [] - }, - { - "name": "systemd", - "version": "252-18.el9", - "vulns": [], - "source": "OS", - "topCvss": 0, - "riskScore": 0, - "architecture": "x86_64", - "notes": ["UNSCANNED"] - } - ], - "notes": [] - }, - "lastUpdated": "2025-04-15T10:30:00.000Z", - "vsockCid": 3, - "state": "RUNNING" -} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVMs.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVMs.json new file mode 100644 index 0000000000000..7eb933653a824 --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVMs.json @@ -0,0 +1,84 @@ +{ + "vms": [ + { + "id": "vm-001", + "name": "cypress-vm-1", + "namespace": "default", + "clusterId": "cluster-001", + "clusterName": "production-cluster", + "guestOs": "Red Hat Enterprise Linux 9.2", + "state": "VM_STATE_RUNNING", + "scanTime": "2025-04-15T10:30:00.000Z", + "scanOs": "rhel:9", + "lastUpdated": "2025-04-15T10:30:00.000Z", + "vsockCid": 3, + "facts": { + "guestOS": "Red Hat Enterprise Linux 9.2", + "ipAddresses": "10.0.1.50", + "nodeName": "worker-node-1", + "activePodCount": "3", + "activePods": "3", + "bootOrder": "disk,network" + }, + "cveSeverityCounts": { + "critical": { "total": 1, "fixable": 1 }, + "important": { "total": 1, "fixable": 1 }, + "moderate": { "total": 1, "fixable": 1 }, + "low": { "total": 0, "fixable": 0 }, + "unknown": { "total": 0, "fixable": 0 } + }, + "componentScanCount": { "scanned": 2, "total": 3 } + }, + { + "id": "vm-002", + "name": "cypress-vm-2", + "namespace": "monitoring", + "clusterId": "cluster-001", + "clusterName": "production-cluster", + "guestOs": "Red Hat Enterprise Linux 8.8", + "state": "VM_STATE_RUNNING", + "scanTime": "2025-04-14T08:15:00.000Z", + "scanOs": "rhel:8", + "lastUpdated": "2025-04-14T08:15:00.000Z", + "vsockCid": 4, + "facts": { + "guestOS": "Red Hat Enterprise Linux 8.8", + "ipAddresses": "10.0.1.51", + "nodeName": "worker-node-2", + "activePodCount": "1", + "bootOrder": "disk" + }, + "cveSeverityCounts": { + "critical": { "total": 0, "fixable": 0 }, + "important": { "total": 0, "fixable": 0 }, + "moderate": { "total": 0, "fixable": 0 }, + "low": { "total": 1, "fixable": 0 }, + "unknown": { "total": 0, "fixable": 0 } + }, + "componentScanCount": { "scanned": 1, "total": 1 } + }, + { + "id": "vm-003", + "name": "cypress-vm-3", + "namespace": "default", + "clusterId": "cluster-002", + "clusterName": "staging-cluster", + "guestOs": "Red Hat Enterprise Linux 9.3", + "state": "VM_STATE_STOPPED", + "lastUpdated": "2025-04-13T12:00:00.000Z", + "vsockCid": 5, + "facts": { + "guestOS": "Red Hat Enterprise Linux 9.3" + }, + "cveSeverityCounts": { + "critical": { "total": 0, "fixable": 0 }, + "important": { "total": 0, "fixable": 0 }, + "moderate": { "total": 0, "fixable": 0 }, + "low": { "total": 0, "fixable": 0 }, + "unknown": { "total": 0, "fixable": 0 } + }, + "componentScanCount": { "scanned": 0, "total": 0 } + } + ], + "totalCount": 3 +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents-vm-002.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents-vm-002.json new file mode 100644 index 0000000000000..a4307f6c80230 --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents-vm-002.json @@ -0,0 +1,18 @@ +{ + "components": [ + { + "id": "kernel-4.18.0-477.el8", + "name": "kernel", + "version": "4.18.0-477.el8", + "source": "OS", + "scanStatus": "SCANNED", + "lastScanned": "2025-04-14T08:15:00.000Z", + "cveCount": 1, + "architecture": "x86_64", + "topCvss": 4.0, + "riskScore": 4.0, + "notes": [] + } + ], + "totalCount": 1 +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents.json new file mode 100644 index 0000000000000..d9f23a0ea51da --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineComponents.json @@ -0,0 +1,44 @@ +{ + "components": [ + { + "id": "openssl-3.0.7-20.el9", + "name": "openssl", + "version": "3.0.7-20.el9", + "source": "OS", + "scanStatus": "SCANNED", + "lastScanned": "2025-04-15T10:30:00.000Z", + "cveCount": 2, + "architecture": "x86_64", + "topCvss": 9.8, + "riskScore": 9.8, + "notes": [] + }, + { + "id": "curl-7.76.1-26.el9", + "name": "curl", + "version": "7.76.1-26.el9", + "source": "OS", + "scanStatus": "SCANNED", + "lastScanned": "2025-04-15T10:30:00.000Z", + "cveCount": 1, + "architecture": "x86_64", + "topCvss": 7.5, + "riskScore": 7.5, + "notes": [] + }, + { + "id": "systemd-252-18.el9", + "name": "systemd", + "version": "252-18.el9", + "source": "OS", + "scanStatus": "NOT_SCANNED", + "lastScanned": "2025-04-15T10:30:00.000Z", + "cveCount": 0, + "architecture": "x86_64", + "topCvss": 0, + "riskScore": 0, + "notes": ["UNSCANNED"] + } + ], + "totalCount": 3 +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves-vm-002.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves-vm-002.json new file mode 100644 index 0000000000000..400f23f6ee45a --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves-vm-002.json @@ -0,0 +1,29 @@ +{ + "cves": [ + { + "cve": "CVE-2024-0004", + "severity": "LOW_VULNERABILITY_SEVERITY", + "isFixable": false, + "cvss": 4.0, + "nvdCvss": 4.0, + "epssProbability": 0.02, + "epssPercentile": 0.15, + "epss": { + "epssProbability": 0.02, + "epssPercentile": 0.15 + }, + "affectedComponentCount": 1, + "publishedOn": "2024-04-01T00:00:00.000Z", + "lastModified": "2024-04-15T00:00:00.000Z", + "firstSystemOccurrence": "2024-04-05T00:00:00.000Z", + "summary": "Low severity kernel vulnerability", + "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0004", + "cvssMetrics": [], + "advisory": { + "name": "", + "link": "" + } + } + ], + "totalCount": 1 +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves.json new file mode 100644 index 0000000000000..8e818e2e54e9b --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachineCves.json @@ -0,0 +1,80 @@ +{ + "cves": [ + { + "cve": "CVE-2024-0001", + "severity": "CRITICAL_VULNERABILITY_SEVERITY", + "isFixable": true, + "cvss": 9.8, + "nvdCvss": 9.8, + "epssProbability": 0.85, + "epssPercentile": 0.97, + "epss": { + "epssProbability": 0.85, + "epssPercentile": 0.97 + }, + "affectedComponentCount": 1, + "publishedOn": "2024-01-15T00:00:00.000Z", + "lastModified": "2024-02-01T00:00:00.000Z", + "firstSystemOccurrence": "2024-01-20T00:00:00.000Z", + "summary": "Critical vulnerability in openssl", + "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0001", + "fixedBy": "3.0.7-25.el9", + "cvssMetrics": [], + "advisory": { + "name": "RHSA-2024:0001", + "link": "https://access.redhat.com/errata/RHSA-2024:0001" + } + }, + { + "cve": "CVE-2024-0002", + "severity": "MODERATE_VULNERABILITY_SEVERITY", + "isFixable": true, + "cvss": 5.3, + "nvdCvss": 5.3, + "epssProbability": 0.12, + "epssPercentile": 0.45, + "epss": { + "epssProbability": 0.12, + "epssPercentile": 0.45 + }, + "affectedComponentCount": 1, + "publishedOn": "2024-02-10T00:00:00.000Z", + "lastModified": "2024-03-01T00:00:00.000Z", + "firstSystemOccurrence": "2024-02-15T00:00:00.000Z", + "summary": "Moderate vulnerability in openssl", + "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0002", + "fixedBy": "3.0.7-25.el9", + "cvssMetrics": [], + "advisory": { + "name": "RHSA-2024:0002", + "link": "https://access.redhat.com/errata/RHSA-2024:0002" + } + }, + { + "cve": "CVE-2024-0003", + "severity": "IMPORTANT_VULNERABILITY_SEVERITY", + "isFixable": true, + "cvss": 7.5, + "nvdCvss": 7.5, + "epssProbability": 0.45, + "epssPercentile": 0.82, + "epss": { + "epssProbability": 0.45, + "epssPercentile": 0.82 + }, + "affectedComponentCount": 1, + "publishedOn": "2024-03-01T00:00:00.000Z", + "lastModified": "2024-03-15T00:00:00.000Z", + "firstSystemOccurrence": "2024-03-05T00:00:00.000Z", + "summary": "Important vulnerability in curl", + "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0003", + "fixedBy": "7.76.1-29.el9", + "cvssMetrics": [], + "advisory": { + "name": "RHSA-2024:0003", + "link": "https://access.redhat.com/errata/RHSA-2024:0003" + } + } + ], + "totalCount": 3 +} diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachines.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachines.json deleted file mode 100644 index 2acd246a61dd8..0000000000000 --- a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/listVirtualMachines.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "virtualMachines": [ - { - "id": "vm-001", - "namespace": "default", - "name": "cypress-vm-1", - "clusterId": "cluster-001", - "clusterName": "production-cluster", - "facts": { - "guestOS": "Red Hat Enterprise Linux 9.2", - "ipAddresses": "10.0.1.50", - "nodeName": "worker-node-1", - "activePodCount": "3", - "bootOrder": "disk,network" - }, - "scan": { - "scanTime": "2025-04-15T10:30:00.000Z", - "operatingSystem": "rhel:9", - "components": [ - { - "name": "openssl", - "version": "3.0.7-20.el9", - "vulns": [ - { - "cve": "CVE-2024-0001", - "advisory": { - "name": "RHSA-2024:0001", - "link": "https://access.redhat.com/errata/RHSA-2024:0001" - }, - "cvss": 9.8, - "summary": "Critical vulnerability in openssl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0001", - "fixedBy": "3.0.7-25.el9", - "publishedOn": "2024-01-15T00:00:00.000Z", - "lastModified": "2024-02-01T00:00:00.000Z", - "firstSystemOccurrence": "2024-01-20T00:00:00.000Z", - "severity": "CRITICAL_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.85, - "epssPercentile": 0.97 - } - }, - { - "cve": "CVE-2024-0002", - "advisory": { - "name": "RHSA-2024:0002", - "link": "https://access.redhat.com/errata/RHSA-2024:0002" - }, - "cvss": 5.3, - "summary": "Moderate vulnerability in openssl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0002", - "fixedBy": "3.0.7-25.el9", - "publishedOn": "2024-02-10T00:00:00.000Z", - "lastModified": "2024-03-01T00:00:00.000Z", - "firstSystemOccurrence": "2024-02-15T00:00:00.000Z", - "severity": "MODERATE_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.12, - "epssPercentile": 0.45 - } - } - ], - "source": "OS", - "topCvss": 9.8, - "riskScore": 9.8, - "architecture": "x86_64", - "notes": [] - }, - { - "name": "curl", - "version": "7.76.1-26.el9", - "vulns": [ - { - "cve": "CVE-2024-0003", - "advisory": { - "name": "RHSA-2024:0003", - "link": "https://access.redhat.com/errata/RHSA-2024:0003" - }, - "cvss": 7.5, - "summary": "Important vulnerability in curl", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0003", - "fixedBy": "7.76.1-29.el9", - "publishedOn": "2024-03-01T00:00:00.000Z", - "lastModified": "2024-03-15T00:00:00.000Z", - "firstSystemOccurrence": "2024-03-05T00:00:00.000Z", - "severity": "IMPORTANT_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.45, - "epssPercentile": 0.82 - } - } - ], - "source": "OS", - "topCvss": 7.5, - "riskScore": 7.5, - "architecture": "x86_64", - "notes": [] - }, - { - "name": "systemd", - "version": "252-18.el9", - "vulns": [], - "source": "OS", - "topCvss": 0, - "riskScore": 0, - "architecture": "x86_64", - "notes": ["UNSCANNED"] - } - ], - "notes": [] - }, - "lastUpdated": "2025-04-15T10:30:00.000Z", - "vsockCid": 3, - "state": "RUNNING" - }, - { - "id": "vm-002", - "namespace": "monitoring", - "name": "cypress-vm-2", - "clusterId": "cluster-001", - "clusterName": "production-cluster", - "facts": { - "guestOS": "Red Hat Enterprise Linux 8.8", - "ipAddresses": "10.0.1.51", - "nodeName": "worker-node-2", - "activePodCount": "1", - "bootOrder": "disk" - }, - "scan": { - "scanTime": "2025-04-14T08:15:00.000Z", - "operatingSystem": "rhel:8", - "components": [ - { - "name": "kernel", - "version": "4.18.0-477.el8", - "vulns": [ - { - "cve": "CVE-2024-0004", - "advisory": { - "name": "", - "link": "" - }, - "cvss": 4.0, - "summary": "Low severity kernel vulnerability", - "link": "https://nvd.nist.gov/vuln/detail/CVE-2024-0004", - "publishedOn": "2024-04-01T00:00:00.000Z", - "lastModified": "2024-04-15T00:00:00.000Z", - "firstSystemOccurrence": "2024-04-05T00:00:00.000Z", - "severity": "LOW_VULNERABILITY_SEVERITY", - "cvssMetrics": [], - "epss": { - "epssProbability": 0.02, - "epssPercentile": 0.15 - } - } - ], - "source": "OS", - "topCvss": 4.0, - "riskScore": 4.0, - "architecture": "x86_64", - "notes": [] - } - ], - "notes": [] - }, - "lastUpdated": "2025-04-14T08:15:00.000Z", - "vsockCid": 4, - "state": "RUNNING" - }, - { - "id": "vm-003", - "namespace": "default", - "name": "cypress-vm-3", - "clusterId": "cluster-002", - "clusterName": "staging-cluster", - "facts": { - "guestOS": "Red Hat Enterprise Linux 9.3" - }, - "lastUpdated": "2025-04-13T12:00:00.000Z", - "vsockCid": 5, - "state": "STOPPED" - } - ], - "totalCount": 3 -} diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts index e334b79b09101..56b48bb01c97c 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts @@ -4,35 +4,59 @@ import { visit, visitWithStaticResponseForPermissions } from '../../../helpers/v export const listVirtualMachinesAlias = 'listVirtualMachines'; export const getVirtualMachineAlias = 'getVirtualMachine'; +export const listVirtualMachineCvesAlias = 'listVirtualMachineCves'; +export const listVirtualMachineComponentsAlias = 'listVirtualMachineComponents'; export const routeMatcherMapForVirtualMachines = { [listVirtualMachinesAlias]: { method: 'GET', - url: '/v2/virtualmachines?*', + url: '/v2/virtualmachines/vms?*', }, }; export const routeMatcherMapForVirtualMachine = { [getVirtualMachineAlias]: { method: 'GET', - url: '/v2/virtualmachines/*', + url: '/v2/virtualmachines/vms/*', }, }; +export const routeMatcherMapForVirtualMachineVulnerabilities = { + ...routeMatcherMapForVirtualMachine, + [listVirtualMachineCvesAlias]: { + method: 'GET', + url: '/v2/virtualmachines/*/cves?*', + }, +}; + +export const routeMatcherMapForVirtualMachineComponents = { + [listVirtualMachineComponentsAlias]: { + method: 'GET', + url: '/v2/virtualmachines/*/components?*', + }, +}; + +function virtualMachineCvesOverviewPath(params?: Record): string { + const query = params ? `?${new URLSearchParams(params).toString()}` : ''; + return `/main/vulnerabilities/virtual-machine-cves${query}`; +} + export function visitVirtualMachineCvesOverviewPage( routeMatcherMap?: Record, - staticResponseMap?: Record + staticResponseMap?: Record, + params?: Record ) { - visit('/main/vulnerabilities/virtual-machine-cves', routeMatcherMap, staticResponseMap); + visit(virtualMachineCvesOverviewPath(params), routeMatcherMap, staticResponseMap); } export function visitVirtualMachineCvesOverviewPageWithStaticPermissions( resourceToAccess: Record, routeMatcherMap?: Record, - staticResponseMap?: Record + staticResponseMap?: Record, + params?: Record ) { visitWithStaticResponseForPermissions( - '/main/vulnerabilities/virtual-machine-cves', + virtualMachineCvesOverviewPath(params), { body: { resourceToAccess } }, routeMatcherMap, staticResponseMap diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts index 2687d8e7bfed6..68086e2308f10 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts @@ -11,7 +11,8 @@ import { visitVirtualMachineCvesOverviewPageWithStaticPermissions, } from './VirtualMachineCve.helpers'; -const fixturePathListVMs = 'vulnerabilities/virtualMachineCves/listVirtualMachines'; +const fixturePathListVMs = 'vulnerabilities/virtualMachineCves/listVMs'; +const virtualMachineTabParams = { entityTab: 'VirtualMachine' }; describe('Virtual Machine CVEs - Overview Page', () => { withAuth(); @@ -35,28 +36,37 @@ describe('Virtual Machine CVEs - Overview Page', () => { [listVirtualMachinesAlias]: { fixture: fixturePathListVMs, }, - } + }, + virtualMachineTabParams ); cy.get('h1').contains('Virtual machine vulnerabilities'); }); it('should render the overview page heading and description', () => { - visitVirtualMachineCvesOverviewPage(routeMatcherMapForVirtualMachines, { - [listVirtualMachinesAlias]: { - fixture: fixturePathListVMs, + visitVirtualMachineCvesOverviewPage( + routeMatcherMapForVirtualMachines, + { + [listVirtualMachinesAlias]: { + fixture: fixturePathListVMs, + }, }, - }); + virtualMachineTabParams + ); cy.get('h1').contains('Virtual machine vulnerabilities'); cy.get('body').contains('Prioritize and remediate observed CVEs across virtual machines'); }); it('should render VM rows from fixture data', () => { - visitVirtualMachineCvesOverviewPage(routeMatcherMapForVirtualMachines, { - [listVirtualMachinesAlias]: { - fixture: fixturePathListVMs, + visitVirtualMachineCvesOverviewPage( + routeMatcherMapForVirtualMachines, + { + [listVirtualMachinesAlias]: { + fixture: fixturePathListVMs, + }, }, - }); + virtualMachineTabParams + ); cy.get('tbody tr').should('have.length', 3); @@ -84,11 +94,15 @@ describe('Virtual Machine CVEs - Overview Page', () => { }); it('should link VM names to the correct detail page', () => { - visitVirtualMachineCvesOverviewPage(routeMatcherMapForVirtualMachines, { - [listVirtualMachinesAlias]: { - fixture: fixturePathListVMs, + visitVirtualMachineCvesOverviewPage( + routeMatcherMapForVirtualMachines, + { + [listVirtualMachinesAlias]: { + fixture: fixturePathListVMs, + }, }, - }); + virtualMachineTabParams + ); cy.get('tbody tr td[data-label="Virtual machine"] a') .first() @@ -101,11 +115,15 @@ describe('Virtual Machine CVEs - Overview Page', () => { }); it('should display an empty state when no VMs are returned', () => { - visitVirtualMachineCvesOverviewPage(routeMatcherMapForVirtualMachines, { - [listVirtualMachinesAlias]: { - body: { virtualMachines: [], totalCount: 0 }, + visitVirtualMachineCvesOverviewPage( + routeMatcherMapForVirtualMachines, + { + [listVirtualMachinesAlias]: { + body: { vms: [], totalCount: 0 }, + }, }, - }); + virtualMachineTabParams + ); cy.get('body').contains('No CVEs have been detected'); }); @@ -116,7 +134,7 @@ describe('Virtual Machine CVEs - Overview Page', () => { fixture: fixturePathListVMs, }, }).then(({ waitForRequests }) => { - visitVirtualMachineCvesOverviewPage(); + visitVirtualMachineCvesOverviewPage(undefined, undefined, virtualMachineTabParams); waitForRequests(); sortByTableHeader('Virtual machine'); @@ -130,15 +148,25 @@ describe('Virtual Machine CVEs - Overview Page', () => { it('should paginate through results', () => { const paginatedFixture = { body: { - virtualMachines: Array.from({ length: 20 }, (_, i) => ({ + vms: Array.from({ length: 20 }, (_, i) => ({ id: `vm-${String(i + 1).padStart(3, '0')}`, namespace: 'default', name: `cypress-vm-${i + 1}`, clusterId: 'cluster-001', clusterName: 'production-cluster', + guestOs: 'Red Hat Enterprise Linux 9.2', lastUpdated: '2025-04-15T10:30:00.000Z', + scanTime: '2025-04-15T10:30:00.000Z', vsockCid: i + 3, - state: 'RUNNING', + state: 'VM_STATE_RUNNING', + cveSeverityCounts: { + critical: { total: 0, fixable: 0 }, + important: { total: 0, fixable: 0 }, + moderate: { total: 0, fixable: 0 }, + low: { total: 0, fixable: 0 }, + unknown: { total: 0, fixable: 0 }, + }, + componentScanCount: { scanned: 0, total: 0 }, })), totalCount: 50, }, @@ -147,7 +175,7 @@ describe('Virtual Machine CVEs - Overview Page', () => { interceptAndWatchRequests(routeMatcherMapForVirtualMachines, { [listVirtualMachinesAlias]: paginatedFixture, }).then(({ waitForRequests }) => { - visitVirtualMachineCvesOverviewPage(); + visitVirtualMachineCvesOverviewPage(undefined, undefined, virtualMachineTabParams); waitForRequests(); paginateNext(); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts index 9ac3268dbb262..370893afdc89f 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts @@ -1,21 +1,43 @@ +import { interceptRequests } from '../../../helpers/request'; import withAuth from '../../../helpers/basicAuth'; import { hasFeatureFlag } from '../../../helpers/features'; import { assertCannotFindThePage } from '../../../helpers/visit'; import { getVirtualMachineAlias, - routeMatcherMapForVirtualMachine, + listVirtualMachineComponentsAlias, + listVirtualMachineCvesAlias, + routeMatcherMapForVirtualMachineComponents, + routeMatcherMapForVirtualMachineVulnerabilities, visitVirtualMachinePage, visitVirtualMachinePageWithStaticPermissions, } from './VirtualMachineCve.helpers'; const vmId = 'vm-001'; -const fixturePathGetVM = 'vulnerabilities/virtualMachineCves/getVirtualMachine'; +const fixturePathGetVM = 'vulnerabilities/virtualMachineCves/getVM'; +const fixturePathListCves = 'vulnerabilities/virtualMachineCves/listVirtualMachineCves'; +const fixturePathListComponents = 'vulnerabilities/virtualMachineCves/listVirtualMachineComponents'; + +const staticResponseMapForVirtualMachineVulnerabilities = { + [getVirtualMachineAlias]: { fixture: fixturePathGetVM }, + [listVirtualMachineCvesAlias]: { fixture: fixturePathListCves }, +}; + +function visitWithVulnFixtures() { + visitVirtualMachinePage( + vmId, + routeMatcherMapForVirtualMachineVulnerabilities, + staticResponseMapForVirtualMachineVulnerabilities + ); +} -function visitWithFixture() { - visitVirtualMachinePage(vmId, routeMatcherMapForVirtualMachine, { - [getVirtualMachineAlias]: { fixture: fixturePathGetVM }, +function visitComponentsTab() { + visitWithVulnFixtures(); + interceptRequests(routeMatcherMapForVirtualMachineComponents, { + [listVirtualMachineComponentsAlias]: { fixture: fixturePathListComponents }, }); + cy.get('button').contains('Components').click(); + cy.wait(`@${listVirtualMachineComponentsAlias}`); } describe('Virtual Machine CVEs - Virtual Machine Page', () => { @@ -36,19 +58,15 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { visitVirtualMachinePageWithStaticPermissions( vmId, { Cluster: 'READ_ACCESS' }, - routeMatcherMapForVirtualMachine, - { - [getVirtualMachineAlias]: { fixture: fixturePathGetVM }, - } + routeMatcherMapForVirtualMachineVulnerabilities, + staticResponseMapForVirtualMachineVulnerabilities ); cy.get('h1').contains('cypress-vm-1'); }); describe('Vulnerabilities tab', () => { - // TODO(ROX-36464): intercepts do not isolate live VM CVE data, so row-count - // assertions fail when scanning is on. - it.skip('should render CVE rows from fixture data', () => { - visitWithFixture(); + it('should render CVE rows from fixture data', () => { + visitWithVulnFixtures(); cy.get('tbody tr:not([class*="expandable"])').should('have.length', 3); @@ -56,49 +74,44 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { .eq(0) .within(() => { cy.get('td[data-label="CVE"]').contains('CVE-2024-0001'); - cy.get('td[data-label="CVE severity"]').should('exist'); - cy.get('td[data-label="CVE status"]').should('exist'); - cy.get('td[data-label="CVSS"]').should('exist'); - cy.get('td[data-label="EPSS probability"]').should('exist'); - cy.get('td[data-label="Affected components"]').contains('openssl'); + cy.get('td[data-label="CVE severity"]').contains('Critical'); + cy.get('td[data-label="CVE status"]').contains('Fixable'); + cy.get('td[data-label="CVSS"]').contains('9.8'); + cy.get('td[data-label="EPSS probability"]').contains('85.000%'); + cy.get('td[data-label="Affected components"]').contains('1 component'); }); }); - // TODO(ROX-36464): intercepts do not isolate live VM CVE data, so row-count - // assertions fail when scanning is on. - it.skip('should display the correct number of affected components', () => { - visitWithFixture(); + it('should display the correct number of affected components', () => { + visitWithVulnFixtures(); cy.get('tbody tr:not([class*="expandable"])') .eq(0) .within(() => { - cy.get('td[data-label="Affected components"]').contains('openssl'); + cy.get('td[data-label="CVE"]').contains('CVE-2024-0001'); + cy.get('td[data-label="Affected components"]').contains('1 component'); }); cy.get('tbody tr:not([class*="expandable"])') .eq(2) .within(() => { - cy.get('td[data-label="Affected components"]').contains('curl'); + cy.get('td[data-label="CVE"]').contains('CVE-2024-0003'); + cy.get('td[data-label="Affected components"]').contains('1 component'); }); }); - it('should expand a row to show the components sub-table', () => { - visitWithFixture(); + it('should expand a row to show affected component details', () => { + visitWithVulnFixtures(); cy.get('tbody tr:not([class*="expandable"])').eq(0).find('td button').first().click(); cy.get('tbody tr[class*="expandable"]') .eq(0) - .within(() => { - cy.get('td[data-label="Component"]').contains('openssl'); - cy.get('td[data-label="Version"]').contains('3.0.7-20.el9'); - cy.get('td[data-label="CVE fixed in"]').contains('3.0.7-25.el9'); - cy.get('td[data-label="Advisory"]').contains('RHSA-2024:0001'); - }); + .should('contain.text', 'Affected component details coming soon'); }); it('should display an empty state when the VM has no vulnerabilities', () => { - visitVirtualMachinePage(vmId, routeMatcherMapForVirtualMachine, { + visitVirtualMachinePage(vmId, routeMatcherMapForVirtualMachineVulnerabilities, { [getVirtualMachineAlias]: { body: { id: vmId, @@ -106,17 +119,20 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { name: 'empty-vm', clusterId: 'cluster-001', clusterName: 'production-cluster', - scan: { - scanTime: '2025-04-15T10:30:00.000Z', - operatingSystem: 'rhel:9', - components: [], - notes: [], - }, + guestOs: 'Red Hat Enterprise Linux 9.2', lastUpdated: '2025-04-15T10:30:00.000Z', + facts: {}, + annotations: {}, + labels: {}, vsockCid: 3, - state: 'RUNNING', + notes: [], + state: 'VM_STATE_RUNNING', + agentStatus: 'AGENT_STATUS_ACTIVE', }, }, + [listVirtualMachineCvesAlias]: { + body: { cves: [], totalCount: 0 }, + }, }); cy.get('body').contains('No CVEs were detected for this virtual machine'); @@ -125,25 +141,37 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { describe('Components tab', () => { it('should render component rows from fixture data', () => { - visitWithFixture(); - - cy.get('button').contains('Components').click(); + visitComponentsTab(); cy.get('tbody tr').should('have.length', 3); cy.get('tbody tr') .eq(0) .within(() => { - cy.get('td[data-label="Name"]').should('exist'); - cy.get('td[data-label="Version"]').should('exist'); - cy.get('td[data-label="Status"]').should('exist'); + cy.get('td[data-label="Name"]').contains('openssl'); + cy.get('td[data-label="Version"]').contains('3.0.7-20.el9'); + cy.get('td[data-label="Status"]').contains('Scanned'); + cy.get('td[data-label="Source"]').contains('OS'); + }); + + cy.get('tbody tr') + .eq(1) + .within(() => { + cy.get('td[data-label="Name"]').contains('curl'); + cy.get('td[data-label="Version"]').contains('7.76.1-26.el9'); + }); + + cy.get('tbody tr') + .eq(2) + .within(() => { + cy.get('td[data-label="Name"]').contains('systemd'); + cy.get('td[data-label="Version"]').contains('252-18.el9'); + cy.get('td[data-label="Status"]').contains('Not scanned'); }); }); it('should show scanned and unscanned statuses', () => { - visitWithFixture(); - - cy.get('button').contains('Components').click(); + visitComponentsTab(); cy.get('td[data-label="Status"]').then(($cells) => { const statuses = $cells.map((_, el) => el.innerText.trim()).get(); @@ -155,14 +183,14 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { describe('Breadcrumb navigation', () => { it('should display VM name in breadcrumb', () => { - visitWithFixture(); + visitWithVulnFixtures(); cy.get('.pf-v6-c-breadcrumb__item').should('contain.text', 'Virtual Machines'); cy.get('.pf-v6-c-breadcrumb__item').should('contain.text', 'cypress-vm-1'); }); it('should link back to the overview page', () => { - visitWithFixture(); + visitWithVulnFixtures(); cy.get('.pf-v6-c-breadcrumb__item a') .contains('Virtual Machines') From 1c608d89bda92639ee44c84ba7b84165c70f3e40 Mon Sep 17 00:00:00 2001 From: Yann Brillouet Date: Thu, 27 Aug 2026 10:02:05 +0200 Subject: [PATCH 106/638] ROX-33973: Add role resolver from ACM (#22244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann Co-authored-by: Claude Opus 4.6 (1M context) --- central/cluster/datastore/datastore.go | 1 + central/cluster/datastore/datastore_impl.go | 12 + central/cluster/datastore/mocks/datastore.go | 16 + pkg/auth/tokens/internal_role.go | 90 ++++ pkg/auth/tokens/internal_role_test.go | 442 ++++++++++++++++++ pkg/sac/externalrolebroker/acm_resolver.go | 67 +++ .../externalrolebroker/acm_resolver_test.go | 314 +++++++++++++ 7 files changed, 942 insertions(+) create mode 100644 pkg/sac/externalrolebroker/acm_resolver.go create mode 100644 pkg/sac/externalrolebroker/acm_resolver_test.go diff --git a/central/cluster/datastore/datastore.go b/central/cluster/datastore/datastore.go index 3efff3d52c9aa..1b9bc7a01b1c9 100644 --- a/central/cluster/datastore/datastore.go +++ b/central/cluster/datastore/datastore.go @@ -44,6 +44,7 @@ var ( //go:generate mockgen-wrapper type DataStore interface { GetCluster(ctx context.Context, id string) (*storage.Cluster, bool, error) + GetClusterID(ctx context.Context, name string) (string, bool, error) GetClusterName(ctx context.Context, id string) (string, bool, error) GetClusters(ctx context.Context) ([]*storage.Cluster, error) GetClustersForSAC() ([]effectiveaccessscope.Cluster, error) diff --git a/central/cluster/datastore/datastore_impl.go b/central/cluster/datastore/datastore_impl.go index 04afdcd772bbd..1783f867ed976 100644 --- a/central/cluster/datastore/datastore_impl.go +++ b/central/cluster/datastore/datastore_impl.go @@ -372,6 +372,18 @@ func (ds *datastoreImpl) GetClusterName(ctx context.Context, id string) (string, return val.(string), true, nil } +func (ds *datastoreImpl) GetClusterID(ctx context.Context, name string) (string, bool, error) { + idVal, ok := ds.nameToIDCache.Get(name) + if !ok { + return "", false, nil + } + id := idVal.(string) + if allowed, err := clusterSAC.ReadAllowed(ctx, sac.ClusterScopeKey(id)); err != nil || !allowed { + return "", false, err + } + return id, true, nil +} + // Figure out if an indicator matches provided namespace filter. We consider // SAC errors or failure to find the pre-compiled filter regex as not matching // cases. diff --git a/central/cluster/datastore/mocks/datastore.go b/central/cluster/datastore/mocks/datastore.go index 31785e5b30bde..ba643ce95df76 100644 --- a/central/cluster/datastore/mocks/datastore.go +++ b/central/cluster/datastore/mocks/datastore.go @@ -122,6 +122,22 @@ func (mr *MockDataStoreMockRecorder) GetCluster(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCluster", reflect.TypeOf((*MockDataStore)(nil).GetCluster), ctx, id) } +// GetClusterID mocks base method. +func (m *MockDataStore) GetClusterID(ctx context.Context, name string) (string, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterID", ctx, name) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetClusterID indicates an expected call of GetClusterID. +func (mr *MockDataStoreMockRecorder) GetClusterID(ctx, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterID", reflect.TypeOf((*MockDataStore)(nil).GetClusterID), ctx, name) +} + // GetClusterLabels mocks base method. func (m *MockDataStore) GetClusterLabels(ctx context.Context, clusterID string) (map[string]string, error) { m.ctrl.T.Helper() diff --git a/pkg/auth/tokens/internal_role.go b/pkg/auth/tokens/internal_role.go index ab2ceb96c6503..a35284713e72d 100644 --- a/pkg/auth/tokens/internal_role.go +++ b/pkg/auth/tokens/internal_role.go @@ -1,12 +1,16 @@ package tokens import ( + "context" "encoding/json" "slices" "strings" + "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/set" ) @@ -193,3 +197,89 @@ func (r *InternalRole) GetAccessScope() *storage.SimpleAccessScope { }, } } + +type ClusterResolver interface { + GetClusterID(ctx context.Context, name string) (string, bool, error) +} + +// NewInternalRoleFromPermissionsAndScope instantiates an InternalRole object +// based on the input PermissionSet and AccessScope. +func NewInternalRoleFromPermissionsAndScope( + ctx context.Context, + roleName string, + permissions *storage.PermissionSet, + scope *storage.SimpleAccessScope, + clusterIDResolver ClusterResolver, +) (*InternalRole, error) { + if clusterIDResolver == nil { + return nil, errors.New("missing cluster ID resolver") + } + + output := &InternalRole{ + RoleName: roleName, + } + + // Fill permission set + reversedPermissionSet := make(map[storage.Access]set.StringSet) + for permission, access := range permissions.GetResourceToAccess() { + if _, accessFound := reversedPermissionSet[access]; !accessFound { + reversedPermissionSet[access] = set.NewStringSet() + } + permissionsForAccess := reversedPermissionSet[access] + permissionsForAccess.Add(permission) + } + output.Permissions = make(map[storage.Access][]string) + for access, permissionSet := range reversedPermissionSet { + output.Permissions[access] = permissionSet.AsSortedSlice(func(i, j string) bool { + return i < j + }) + } + + // Elevate to READ on Cluster resources solely for name-to-ID resolution; this does not expand the caller's derived permissions. + clusterIDResolutionCtx := sac.WithGlobalAccessScopeChecker( + ctx, + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), + sac.ResourceScopeKeys(resources.Cluster), + ), + ) + // Resolve cluster scope + output.Clusters = make(ClusterScopes) + for _, clusterID := range scope.GetRules().GetIncludedClusterIds() { + if clusterID == "" { + continue + } + output.Clusters[clusterID] = []string{"*"} + } + for _, clusterName := range scope.GetRules().GetIncludedClusters() { + clusterID, found, err := clusterIDResolver.GetClusterID(clusterIDResolutionCtx, clusterName) + if err != nil { + return nil, errors.Wrap(err, "failed to resolve cluster ID") + } + if !found || clusterID == "" { + continue + } + output.Clusters[clusterID] = []string{"*"} + } + for _, namespaceRule := range scope.GetRules().GetIncludedNamespaces() { + ns := namespaceRule.GetNamespaceName() + clusterID := namespaceRule.GetClusterId() + if clusterID == "" { + clusterName := namespaceRule.GetClusterName() + idForName, found, err := clusterIDResolver.GetClusterID(clusterIDResolutionCtx, clusterName) + if err != nil { + return nil, errors.Wrap(err, "failed to resolve cluster ID") + } + if !found { + continue + } + clusterID = idForName + } + if clusterID == "" { + continue + } + output.Clusters[clusterID] = append(output.Clusters[clusterID], ns) + } + + return output, nil +} diff --git a/pkg/auth/tokens/internal_role_test.go b/pkg/auth/tokens/internal_role_test.go index 298d5ca30881c..bb590d64d4363 100644 --- a/pkg/auth/tokens/internal_role_test.go +++ b/pkg/auth/tokens/internal_role_test.go @@ -1,9 +1,11 @@ package tokens import ( + "context" "encoding/json" "testing" + "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/fixtures/fixtureconsts" "github.com/stackrox/rox/pkg/protoassert" @@ -378,3 +380,443 @@ func TestInternalRoleJSONEncoding(t *testing.T) { }) } } + +// mockClusterResolver is a test implementation of ClusterResolver +type mockClusterResolver struct { + clusters map[string]string // map of cluster name -> cluster ID + err error // error to return from GetClusterID +} + +func (m *mockClusterResolver) GetClusterID(ctx context.Context, name string) (string, bool, error) { + if m.err != nil { + return "", false, m.err + } + id, found := m.clusters[name] + return id, found, nil +} + +func TestNewInternalRoleFromPermissionsAndScope(t *testing.T) { + const deploymentResource = "Deployment" + const imageResource = "Image" + const namespaceA = "namespace-a" + const namespaceB = "namespace-b" + const clusterNameDev = "dev-cluster" + const clusterNameProd = "prod-cluster" + + for name, tc := range map[string]struct { + roleName string + permissions *storage.PermissionSet + scope *storage.SimpleAccessScope + resolver ClusterResolver + expectedRole *InternalRole + expectedError string + }{ + "Nil resolver returns error": { + roleName: "test-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{}, + }, + resolver: nil, + expectedError: "missing cluster ID resolver", + }, + "Empty permissions and scope": { + roleName: "empty-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{}, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{}, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "empty-role", + Permissions: map[storage.Access][]string{}, + Clusters: ClusterScopes{}, + }, + }, + "Role with read permissions only": { + roleName: "reader-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + imageResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{}, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "reader-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource, imageResource}, + }, + Clusters: ClusterScopes{}, + }, + }, + "Role with write permissions only": { + roleName: "writer-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_WRITE_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{}, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "writer-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_WRITE_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{}, + }, + }, + "Role with mixed read and write permissions": { + roleName: "mixed-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + imageResource: storage.Access_READ_WRITE_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{}, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "mixed-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + storage.Access_READ_WRITE_ACCESS: {imageResource}, + }, + Clusters: ClusterScopes{}, + }, + }, + "Scope with cluster IDs only": { + roleName: "cluster-id-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusterIds: []string{fixtureconsts.Cluster1, fixtureconsts.Cluster2}, + }, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "cluster-id-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {"*"}, + fixtureconsts.Cluster2: {"*"}, + }, + }, + }, + "Scope with cluster names that resolve": { + roleName: "cluster-name-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{clusterNameDev, clusterNameProd}, + }, + }, + resolver: &mockClusterResolver{ + clusters: map[string]string{ + clusterNameDev: fixtureconsts.Cluster1, + clusterNameProd: fixtureconsts.Cluster2, + }, + }, + expectedRole: &InternalRole{ + RoleName: "cluster-name-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {"*"}, + fixtureconsts.Cluster2: {"*"}, + }, + }, + }, + "Scope with cluster names that don't resolve are skipped": { + roleName: "missing-cluster-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{clusterNameDev, "unknown-cluster"}, + }, + }, + resolver: &mockClusterResolver{ + clusters: map[string]string{ + clusterNameDev: fixtureconsts.Cluster1, + // "unknown-cluster" not in map + }, + }, + expectedRole: &InternalRole{ + RoleName: "missing-cluster-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {"*"}, + // unknown-cluster is skipped + }, + }, + }, + "Cluster name resolution error propagates": { + roleName: "error-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{clusterNameDev}, + }, + }, + resolver: &mockClusterResolver{ + err: errors.New("resolver error"), + }, + expectedError: "failed to resolve cluster ID", + }, + "Scope with namespace rules using cluster IDs": { + roleName: "namespace-id-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterId: fixtureconsts.Cluster1, + NamespaceName: namespaceA, + }, + { + ClusterId: fixtureconsts.Cluster1, + NamespaceName: namespaceB, + }, + }, + }, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "namespace-id-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {namespaceA, namespaceB}, + }, + }, + }, + "Scope with namespace rules using cluster names": { + roleName: "namespace-name-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterName: clusterNameDev, + NamespaceName: namespaceA, + }, + { + ClusterName: clusterNameProd, + NamespaceName: namespaceB, + }, + }, + }, + }, + resolver: &mockClusterResolver{ + clusters: map[string]string{ + clusterNameDev: fixtureconsts.Cluster1, + clusterNameProd: fixtureconsts.Cluster2, + }, + }, + expectedRole: &InternalRole{ + RoleName: "namespace-name-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {namespaceA}, + fixtureconsts.Cluster2: {namespaceB}, + }, + }, + }, + "Namespace with unresolved cluster name is skipped": { + roleName: "namespace-missing-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterName: clusterNameDev, + NamespaceName: namespaceA, + }, + { + ClusterName: "unknown-cluster", + NamespaceName: namespaceB, + }, + }, + }, + }, + resolver: &mockClusterResolver{ + clusters: map[string]string{ + clusterNameDev: fixtureconsts.Cluster1, + // "unknown-cluster" not in map + }, + }, + expectedRole: &InternalRole{ + RoleName: "namespace-missing-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {namespaceA}, + // namespace with unknown cluster is skipped + }, + }, + }, + "Namespace cluster name resolution error propagates": { + roleName: "namespace-error-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterName: clusterNameDev, + NamespaceName: namespaceA, + }, + }, + }, + }, + resolver: &mockClusterResolver{ + err: errors.New("namespace resolver error"), + }, + expectedError: "failed to resolve cluster ID", + }, + "Mixed scope with cluster IDs, cluster names, and namespace rules": { + roleName: "complex-scope-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + imageResource: storage.Access_READ_WRITE_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusterIds: []string{fixtureconsts.Cluster1}, + IncludedClusters: []string{clusterNameProd}, + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterId: fixtureconsts.Cluster3, + NamespaceName: namespaceA, + }, + }, + }, + }, + resolver: &mockClusterResolver{ + clusters: map[string]string{ + clusterNameProd: fixtureconsts.Cluster2, + }, + }, + expectedRole: &InternalRole{ + RoleName: "complex-scope-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + storage.Access_READ_WRITE_ACCESS: {imageResource}, + }, + Clusters: ClusterScopes{ + fixtureconsts.Cluster1: {"*"}, + fixtureconsts.Cluster2: {"*"}, + fixtureconsts.Cluster3: {namespaceA}, + }, + }, + }, + "Namespace appends to existing cluster scope": { + roleName: "namespace-append-role", + permissions: &storage.PermissionSet{ + ResourceToAccess: map[string]storage.Access{ + deploymentResource: storage.Access_READ_ACCESS, + }, + }, + scope: &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusterIds: []string{fixtureconsts.Cluster1}, + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + { + ClusterId: fixtureconsts.Cluster1, + NamespaceName: namespaceA, + }, + }, + }, + }, + resolver: &mockClusterResolver{clusters: map[string]string{}}, + expectedRole: &InternalRole{ + RoleName: "namespace-append-role", + Permissions: map[storage.Access][]string{ + storage.Access_READ_ACCESS: {deploymentResource}, + }, + Clusters: ClusterScopes{ + // Cluster1 has both wildcard and namespace - appended + fixtureconsts.Cluster1: {"*", namespaceA}, + }, + }, + }, + } { + t.Run(name, func(it *testing.T) { + ctx := context.Background() + role, err := NewInternalRoleFromPermissionsAndScope( + ctx, + tc.roleName, + tc.permissions, + tc.scope, + tc.resolver, + ) + + if tc.expectedError != "" { + assert.Error(it, err) + assert.Contains(it, err.Error(), tc.expectedError) + return + } + + assert.NoError(it, err) + assert.Equal(it, tc.expectedRole.RoleName, role.RoleName) + assert.Equal(it, tc.expectedRole.Permissions, role.Permissions) + assert.Equal(it, tc.expectedRole.Clusters, role.Clusters) + }) + } +} diff --git a/pkg/sac/externalrolebroker/acm_resolver.go b/pkg/sac/externalrolebroker/acm_resolver.go new file mode 100644 index 0000000000000..3ebb0e8d8e0b9 --- /dev/null +++ b/pkg/sac/externalrolebroker/acm_resolver.go @@ -0,0 +1,67 @@ +package externalrolebroker + +import ( + "context" + + "github.com/pkg/errors" + "github.com/stackrox/rox/pkg/auth/tokens" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// acmClient is the consumer-side interface for acm.Client, scoped to what this package uses. +type acmClient interface { + ListUserPermissions(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) +} + +// GetResolvedRolesFromACM retrieves UserPermissions from ACM, filters them for supported Kubernetes resources, +// and converts each to a permissions.ResolvedRole. +// +// The function: +// - Calls ListUserPermissions on the ACM client +// - Filters the results to only include UserPermissions managing supported K8s resources +// - Converts each UserPermission's ClusterRoleDefinition to a PermissionSet +// - Converts each UserPermission's Bindings to a SimpleAccessScope +// - Creates a ResolvedRole for each UserPermission +// +// The role name is derived from the UserPermission metadata name +func GetResolvedRolesFromACM( + ctx context.Context, + client acmClient, + clusterIDResolver tokens.ClusterResolver, +) ([]*tokens.InternalRole, error) { + // Retrieve all user permissions from ACM + userPermissionList, err := client.ListUserPermissions(ctx, metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to list user permissions") + } + if userPermissionList == nil { + return nil, errors.New("received nil user permission list from ACM") + } + + // Filter to only permissions managing supported Kubernetes resources + filteredPermissions := FilterUserPermissionsForSupportedK8sResources(userPermissionList.Items) + + // Convert each filtered UserPermission to a ResolvedRole + resolvedRoles := make([]*tokens.InternalRole, 0, len(filteredPermissions)) + for _, userPermission := range filteredPermissions { + // Convert ClusterRoleDefinition to PermissionSet + permissionSet := ConvertClusterRoleToPermissionSet(userPermission.Status.ClusterRoleDefinition) + + // Convert Bindings to SimpleAccessScope + accessScope := ConvertBindingsToSimpleAccessScope(userPermission.Status.Bindings) + + resolvedRole, roleErr := tokens.NewInternalRoleFromPermissionsAndScope( + ctx, + userPermission.Name, + permissionSet, + accessScope, + clusterIDResolver, + ) + if roleErr != nil { + return nil, errors.Wrap(roleErr, "failed to create role") + } + resolvedRoles = append(resolvedRoles, resolvedRole) + } + return resolvedRoles, nil +} diff --git a/pkg/sac/externalrolebroker/acm_resolver_test.go b/pkg/sac/externalrolebroker/acm_resolver_test.go new file mode 100644 index 0000000000000..6b8b43d3489f6 --- /dev/null +++ b/pkg/sac/externalrolebroker/acm_resolver_test.go @@ -0,0 +1,314 @@ +package externalrolebroker + +import ( + "context" + "testing" + + "github.com/pkg/errors" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/sac/resources" + clusterviewv1alpha1 "github.com/stolostron/cluster-lifecycle-api/clusterview/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// mockACMClient is a test implementation of acmClient. +type mockACMClient struct { + listFunc func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) +} + +func (m *mockACMClient) ListUserPermissions(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return m.listFunc(ctx, opts) +} + +type mockClusterResolver struct{} + +func (m *mockClusterResolver) GetClusterID(_ context.Context, clusterName string) (string, bool, error) { + return clusterName, true, nil +} + +func TestGetResolvedRolesFromACM(t *testing.T) { + tests := map[string]struct { + mockListFunc func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) + expectedRoleCount int + expectedError bool + validateRoles func(t *testing.T, roles []permissions.ResolvedRole) + }{ + "empty list": { + mockListFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return &clusterviewv1alpha1.UserPermissionList{ + Items: []clusterviewv1alpha1.UserPermission{}, + }, nil + }, + expectedRoleCount: 0, + expectedError: false, + }, + "list with no base k8s resources": { + mockListFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return &clusterviewv1alpha1.UserPermissionList{ + Items: []clusterviewv1alpha1.UserPermission{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "non-base-permission", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"apps"}, + Resources: []string{"deployments", "statefulsets"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-1", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + }, + }, + }, + }, nil + }, + expectedRoleCount: 0, + expectedError: false, + }, + "list with base k8s resources": { + mockListFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return &clusterviewv1alpha1.UserPermissionList{ + Items: []clusterviewv1alpha1.UserPermission{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "secrets-reader", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-1", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + }, + }, + }, + }, nil + }, + expectedRoleCount: 1, + expectedError: false, + validateRoles: func(t *testing.T, roles []permissions.ResolvedRole) { + require.Len(t, roles, 1) + role := roles[0] + + // Verify role name + assert.Equal(t, "secrets-reader", role.GetRoleName()) + + // Verify permissions + permissions := role.GetPermissions() + require.NotNil(t, permissions) + assert.Equal(t, storage.Access_READ_ACCESS, permissions[string(resources.Secret.GetResource())]) + + // Verify access scope + accessScope := role.GetAccessScope() + require.NotNil(t, accessScope) + assert.Contains(t, accessScope.GetRules().GetIncludedClusterIds(), "cluster-1") + assert.Empty(t, accessScope.GetRules().GetIncludedNamespaces()) + }, + }, + "multiple permissions with different access levels": { + mockListFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return &clusterviewv1alpha1.UserPermissionList{ + Items: []clusterviewv1alpha1.UserPermission{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace-reader", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get", "list"}, + }, + }, + }, + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-a", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"default", "kube-system"}, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "secrets-admin", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"*"}, + }, + }, + }, + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "cluster-b", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + }, + }, + }, + }, + }, nil + }, + expectedRoleCount: 2, + expectedError: false, + validateRoles: func(t *testing.T, roles []permissions.ResolvedRole) { + require.Len(t, roles, 2) + + // Verify first role (namespace-reader) + var namespaceReader, secretsAdmin permissions.ResolvedRole + for _, role := range roles { + if role.GetRoleName() == "namespace-reader" { + namespaceReader = role + } else if role.GetRoleName() == "secrets-admin" { + secretsAdmin = role + } + } + + require.NotNil(t, namespaceReader) + assert.Equal(t, storage.Access_READ_ACCESS, + namespaceReader.GetPermissions()[string(resources.Namespace.GetResource())]) + assert.Len(t, namespaceReader.GetAccessScope().GetRules().GetIncludedNamespaces(), 2) + + require.NotNil(t, secretsAdmin) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, + secretsAdmin.GetPermissions()[string(resources.Secret.GetResource())]) + assert.Contains(t, secretsAdmin.GetAccessScope().GetRules().GetIncludedClusterIds(), "cluster-b") + }, + }, + "acm client returns error": { + mockListFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return nil, errors.New("ACM API unavailable") + }, + expectedRoleCount: 0, + expectedError: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + client := &mockACMClient{ + listFunc: tc.mockListFunc, + } + + roles, err := GetResolvedRolesFromACM(context.Background(), client, &mockClusterResolver{}) + + if tc.expectedError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Len(t, roles, tc.expectedRoleCount) + + if tc.validateRoles != nil { + resolvedRoles := make([]permissions.ResolvedRole, 0, len(roles)) + for _, role := range roles { + resolvedRoles = append(resolvedRoles, role) + } + tc.validateRoles(t, resolvedRoles) + } + }) + } +} + +func TestGetResolvedRolesFromACM_IntegrationExample(t *testing.T) { + // This test demonstrates the full integration of all conversion functions + client := &mockACMClient{ + listFunc: func(ctx context.Context, opts metav1.ListOptions) (*clusterviewv1alpha1.UserPermissionList, error) { + return &clusterviewv1alpha1.UserPermissionList{ + Items: []clusterviewv1alpha1.UserPermission{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "rbac-admin", + }, + Status: clusterviewv1alpha1.UserPermissionStatus{ + ClusterRoleDefinition: clusterviewv1alpha1.ClusterRoleDefinition{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"namespaces", "secrets", "serviceaccounts"}, + Verbs: []string{"get", "list", "create", "update"}, + }, + { + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles", "rolebindings"}, + Verbs: []string{"*"}, + }, + }, + }, + Bindings: []clusterviewv1alpha1.ClusterBinding{ + { + Cluster: "prod-cluster", + Scope: clusterviewv1alpha1.BindingScopeCluster, + Namespaces: []string{"*"}, + }, + { + Cluster: "dev-cluster", + Scope: clusterviewv1alpha1.BindingScopeNamespace, + Namespaces: []string{"team-a", "team-b"}, + }, + }, + }, + }, + }, + }, nil + }, + } + + roles, err := GetResolvedRolesFromACM(context.Background(), client, &mockClusterResolver{}) + + require.NoError(t, err) + require.Len(t, roles, 1) + + role := roles[0] + assert.Equal(t, "rbac-admin", role.GetRoleName()) + + // Verify all 5 base resources have permissions + perms := role.GetPermissions() + assert.Len(t, perms, 5) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, perms[string(resources.Namespace.GetResource())]) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, perms[string(resources.Secret.GetResource())]) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, perms[string(resources.ServiceAccount.GetResource())]) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, perms[string(resources.K8sRole.GetResource())]) + assert.Equal(t, storage.Access_READ_WRITE_ACCESS, perms[string(resources.K8sRoleBinding.GetResource())]) + + // Verify access scope includes cluster and namespaces + scope := role.GetAccessScope() + require.NotNil(t, scope) + assert.Contains(t, scope.GetRules().GetIncludedClusterIds(), "prod-cluster") + assert.Len(t, scope.GetRules().GetIncludedNamespaces(), 2) +} From 326c8334044b56ff3da7c04289161667ec23cbb2 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:29:32 +0200 Subject: [PATCH 107/638] ROX-33621: split VSOCK pull outcome metrics by layer (#22270) Co-authored-by: Cursor --- .../dashboards/vm-index-report-dashboard.json | 108 ++++++++- go.mod | 2 +- .../common/virtualmachine/metrics/metrics.go | 80 +++++-- .../virtualmachine/vmscraper/scraper.go | 80 +++++-- .../virtualmachine/vmscraper/scraper_test.go | 216 +++++++++++++++++- .../virtualmachine/vsockclient/client.go | 13 +- .../virtualmachine/vsockclient/client_test.go | 24 +- .../virtualmachine/vsockdialer/dialer.go | 73 +++++- .../virtualmachine/vsockdialer/dialer_test.go | 57 +++++ 9 files changed, 595 insertions(+), 58 deletions(-) create mode 100644 sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go diff --git a/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json b/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json index 6a30ac0930fac..a48011b513d2b 100644 --- a/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json +++ b/deploy/charts/monitoring/dashboards/vm-index-report-dashboard.json @@ -261,7 +261,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"success\"})", + "expr": "sum(rox_sensor_vsock_pull_scrape_total{status=\"success\"})", "legendFormat": "Success", "range": true, "refId": "A" @@ -326,7 +326,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"unchanged\"})", + "expr": "sum(rox_sensor_vsock_pull_get_report_total{status=\"unchanged\"})", "legendFormat": "Unchanged", "range": true, "refId": "A" @@ -483,7 +483,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Running total of full index reports Sensor sent to Central (rox_sensor_vsock_pull_requests_total{status=\"success\"}). Each step up is one or more forwards. A paced wave is a run of +1 steps a few seconds apart. A dump is one large jump. A Sensor restart resets the counter to 0. Unchanged pulls are not counted.", + "description": "Running total of full index reports Sensor sent to Central (rox_sensor_vsock_pull_scrape_total{status=\"success\"}). Each step up is one or more forwards. A paced wave is a run of +1 steps a few seconds apart. A dump is one large jump. A Sensor restart resets the counter to 0. Unchanged pulls are not counted.", "fieldConfig": { "defaults": { "color": { @@ -581,7 +581,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rox_sensor_vsock_pull_requests_total{status=\"success\"})", + "expr": "sum(rox_sensor_vsock_pull_scrape_total{status=\"success\"})", "legendFormat": "success", "range": true, "refId": "A" @@ -2517,7 +2517,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Rate of scrape results. success = new report sent to Central. unchanged = agent had nothing new. The rest are errors (dial, timeout, not_ready, busy, and so on). Retryable errors should continue at backoff, not at 1 per tick for the whole fleet.", + "description": "Rate of scrape results across the three layered counters (transport, GetReport, scrape). success = new report sent to Central. unchanged = agent had nothing new. The rest are errors (dial, timeout, not_ready, busy, and so on). Retryable errors should continue at backoff, not at 1 per tick for the whole fleet.", "fieldConfig": { "defaults": { "color": { @@ -2717,6 +2717,96 @@ } } ] + }, + { + "matcher": { + "id": "byName", + "options": "abnormal_close" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "unexpected" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "internal_error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "unknown_agent_error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "malformed_request" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "request_too_large" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] } ] }, @@ -2750,7 +2840,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum by (status) (rate(rox_sensor_vsock_pull_requests_total[$__rate_interval]))", + "expr": "sum by (status) (rate(rox_sensor_vsock_pull_transport_total[$__rate_interval]) or rate(rox_sensor_vsock_pull_get_report_total[$__rate_interval]) or rate(rox_sensor_vsock_pull_scrape_total[$__rate_interval]))", "legendFormat": "{{status}}", "range": true, "refId": "A" @@ -3019,7 +3109,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"success|unchanged\"}[$__rate_interval]))", + "expr": "sum(rate(rox_sensor_vsock_pull_scrape_total{status=\"success\"}[$__rate_interval]) or rate(rox_sensor_vsock_pull_get_report_total{status=\"unchanged\"}[$__rate_interval]))", "legendFormat": "success+unchanged", "range": true, "refId": "A" @@ -3030,7 +3120,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"dial_error|timeout|read_error|not_ready|busy|send_error\"}[$__rate_interval]))", + "expr": "sum(rate(rox_sensor_vsock_pull_transport_total{status=~\"dial_error|timeout|read_error|abnormal_close|unexpected\"}[$__rate_interval]) or rate(rox_sensor_vsock_pull_get_report_total{status=~\"not_ready|busy|internal_error|unknown_agent_error\"}[$__rate_interval]) or rate(rox_sensor_vsock_pull_scrape_total{status=\"send_error\"}[$__rate_interval]))", "legendFormat": "retryable errors", "range": true, "refId": "B" @@ -3041,7 +3131,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(rox_sensor_vsock_pull_requests_total{status=~\"invalid_report|unknown_method\"}[$__rate_interval]))", + "expr": "sum(rate(rox_sensor_vsock_pull_scrape_total{status=\"invalid_report\"}[$__rate_interval]) or rate(rox_sensor_vsock_pull_get_report_total{status=~\"unknown_method|malformed_request|request_too_large\"}[$__rate_interval]))", "legendFormat": "permanent failures", "range": true, "refId": "C" diff --git a/go.mod b/go.mod index b6fba624d881b..2f078d8395624 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.23.0 github.com/gorilla/schema v1.4.1 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/pyroscope-go v1.4.2 github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 @@ -320,7 +321,6 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index 35bcb8530a988..57af412f01cd5 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -105,18 +105,37 @@ var IndexReportAcksReceived = prometheus.NewCounterVec( []string{"action"}, // "ACK" or "NACK" ) -// Pull-mode request status label values for PullRequestsTotal. +// Pull-mode outcome labels are split across three counters so transport, +// GetReport protocol, and scrape-pipeline results stay distinct as more +// VSOCK RPC methods are added later. Each per-VM pull attempt increments +// exactly one of these counters (mutually exclusive partition). + +// Transport-layer status values for PullTransportTotal. +const ( + PullTransportDialError = "dial_error" + PullTransportTimeout = "timeout" + PullTransportReadError = "read_error" + PullTransportAbnormalClose = "abnormal_close" + PullTransportUnexpected = "unexpected" +) + +// GetReport protocol status values for PullGetReportTotal. const ( - PullStatusSuccess = "success" - PullStatusUnchanged = "unchanged" - PullStatusDialError = "dial_error" - PullStatusReadError = "read_error" - PullStatusInvalidReport = "invalid_report" - PullStatusSendError = "send_error" - PullStatusNotReady = "not_ready" - PullStatusUnknownMethod = "unknown_method" - PullStatusTimeout = "timeout" - PullStatusBusy = "busy" + PullGetReportUnchanged = "unchanged" + PullGetReportNotReady = "not_ready" + PullGetReportUnknownMethod = "unknown_method" + PullGetReportBusy = "busy" + PullGetReportInternalError = "internal_error" + PullGetReportMalformedRequest = "malformed_request" + PullGetReportRequestTooLarge = "request_too_large" + PullGetReportUnknownAgentError = "unknown_agent_error" +) + +// Scrape-pipeline status values for PullScrapeTotal (post-GetReport). +const ( + PullScrapeSuccess = "success" + PullScrapeInvalidReport = "invalid_report" + PullScrapeSendError = "send_error" ) // PullDialDurationSeconds measures time to establish a websocket connection per VM. @@ -191,13 +210,40 @@ var PullReportPackages = prometheus.NewHistogram( }, ) -// PullRequestsTotal counts per-VM pull attempts by status. -var PullRequestsTotal = prometheus.NewCounterVec( +// PullTransportTotal counts per-VM pull attempts that failed at the VSOCK +// transport layer (dial / read / abnormal close) before a protocol result. +var PullTransportTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_transport_total", + Help: "Per-VM pull attempts that failed at the VSOCK transport layer", + }, + []string{"status"}, +) + +// PullGetReportTotal counts per-VM GetReport protocol outcomes (Unchanged, +// ErrorCode sentinels). Transport failures are counted on PullTransportTotal +// instead; successful full reports continue into the scrape pipeline and are +// counted on PullScrapeTotal. +var PullGetReportTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_get_report_total", + Help: "Per-VM GetReport protocol outcomes (unchanged and agent ErrorCode results)", + }, + []string{"status"}, +) + +// PullScrapeTotal counts per-VM scrape-pipeline outcomes after a full +// GetReport payload was received (viability check, send to Central, success). +var PullScrapeTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: metrics.PrometheusNamespace, Subsystem: metrics.SensorSubsystem.String(), - Name: "vsock_pull_requests_total", - Help: "Per-VM pull attempts by outcome status", + Name: "vsock_pull_scrape_total", + Help: "Per-VM scrape-pipeline outcomes after a full GetReport payload was received", }, []string{"status"}, ) @@ -291,7 +337,9 @@ func init() { PullTickDurationSeconds, PullReportBytes, PullReportPackages, - PullRequestsTotal, + PullTransportTotal, + PullGetReportTotal, + PullScrapeTotal, PullTicksTotal, PullTrackedVMs, PullDueVMs, diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 49a034fa62ce9..a7703d4c26f12 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -76,6 +76,14 @@ type ProtocolClient interface { GetReport(ctx context.Context, stream io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) } +// closeCoder is satisfied by transport errors carrying a structured close +// code. Declared locally so VMScraper doesn't need to import a concrete +// dialer's error type to recognize one. +type closeCoder interface { + error + CloseCode() (code int, reason string) +} + // VMScraper polls running VMs and pulls their scan reports via VSOCK. type VMScraper struct { store RunningVMStore @@ -453,7 +461,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool next := s.scheduleAfterAttempt(key, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=unchanged next=%s", key, next) log.Debugf("VMScraper: unchanged report from roxagent on %q (token=%s)", key, snap.lastToken) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusUnchanged).Inc() + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportUnchanged).Inc() return true } @@ -462,7 +470,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool log.Warnf("VM report from %q: %s", key, warning) } if !viable { - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusInvalidReport).Inc() + metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeInvalidReport).Inc() next := s.scheduleAfterAttempt(key, scrapeNonRetryable) log.Infof("VMScraper: scrape %q failed non-retryable next=%s", key, next) return false @@ -474,7 +482,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) if err := s.sender.Send(vmCtx, vm, result.IndexReport); err != nil { - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusSendError).Inc() + metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeSendError).Inc() outcome := scrapeRetryable if errors.Is(err, errox.NotImplemented) { logging.GetRateLimitedLogger().WarnL(sendNotImplementedLogLimiter, @@ -504,7 +512,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool log.Debugf("VMScraper: successfully pulled report for %q: token=%s, packages=%d, size=%d bytes, total=%s", key, newToken, len(result.IndexReport.GetContents().GetPackages()), reportSize, totalElapsed.Truncate(time.Millisecond)) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusSuccess).Inc() + metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeSuccess).Inc() metrics.PullTotalDurationSeconds.Observe(totalElapsed.Seconds()) return true } @@ -566,7 +574,7 @@ func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Inf if err != nil { if ctx.Err() != nil { log.Warnf("VMScraper: dialing roxagent on %q timed out: %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusTimeout).Inc() + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout).Inc() if errors.Is(ctx.Err(), context.Canceled) { // Parent cancel (Sensor stop): do not keep retrying on the short tick. return nil, scrapeNonRetryable @@ -574,7 +582,7 @@ func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Inf return nil, scrapeRetryable } log.Warnf("VMScraper: dialing roxagent on %q failed: %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusDialError).Inc() + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportDialError).Inc() return nil, scrapeRetryable } defer func() { _ = stream.Close() }() @@ -594,42 +602,76 @@ func (s *VMScraper) handleGetReportError(ctx context.Context, key string, err er // as timeout (matching the dial path) rather than as a protocol/read error. if ctx.Err() != nil { log.Warnf("VMScraper: reading report from %q timed out: %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusTimeout).Inc() + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout).Inc() if errors.Is(ctx.Err(), context.Canceled) { return scrapeNonRetryable } return scrapeRetryable } + var closeErr closeCoder switch { case errors.Is(err, vsockclient.ErrNotReady): log.Debugf("VMScraper: roxagent on %q has not yet generated a report", key) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusNotReady).Inc() + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportNotReady).Inc() return scrapeRetryable case errors.Is(err, vsockclient.ErrUnknownMethod): log.Warnf("VMScraper: roxagent on %q does not support the GetReport method", key) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusUnknownMethod).Inc() + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportUnknownMethod).Inc() return scrapeNonRetryable + case errors.Is(err, vsockclient.ErrBusy): + log.Infof("VMScraper: roxagent on %q is busy with another request: %v", key, err) + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportBusy).Inc() + return scrapeRetryable case errors.Is(err, vsockclient.ErrInternal): log.Warnf("VMScraper: roxagent on %q reported an internal error: %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError).Inc() + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportInternalError).Inc() + return scrapeRetryable + case errors.Is(err, vsockclient.ErrMalformedRequest): + log.Warnf("VMScraper: roxagent on %q rejected the request as malformed: %v", key, err) + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportMalformedRequest).Inc() + return scrapeNonRetryable + case errors.Is(err, vsockclient.ErrRequestTooLarge): + log.Warnf("VMScraper: roxagent on %q rejected the request as too large: %v", key, err) + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportRequestTooLarge).Inc() + return scrapeNonRetryable + case errors.Is(err, vsockclient.ErrUnknownAgentError): + log.Warnf("VMScraper: roxagent on %q returned an error code this Sensor version doesn't recognize (possible version mismatch): %v", key, err) + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportUnknownAgentError).Inc() + return scrapeRetryable + case isAbnormalClose(err, &closeErr): + // e.g. close code 1006, which is what a TLS handshake failure on the + // agent's side looks like from Sensor's end. + code, reason := closeErr.CloseCode() + log.Warnf("VMScraper: roxagent on %q connection closed abnormally (websocket close code %d: %s) — check roxagent's own logs on the VM for the cause", + key, code, reason) + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportAbnormalClose).Inc() return scrapeRetryable case errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF): log.Debugf("VMScraper: roxagent on %q connection closed (agent may be down or restarting): %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError).Inc() - return scrapeRetryable - case errors.Is(err, vsockclient.ErrBusy): - log.Infof("VMScraper: roxagent on %q is busy with another request: %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy).Inc() + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError).Inc() return scrapeRetryable default: - log.Warnf("VMScraper: protocol error for %q (possible version mismatch): %v", key, err) - metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError).Inc() - // ErrUnknownMethod, the only permanent sentinel, is handled above, so - // any error reaching here is treated as transient. + log.Warnf("VMScraper: unexpected transport or framing error for %q: %v", key, err) + metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportUnexpected).Inc() return scrapeRetryable } } +// closeCodeNormalClosure is the RFC 6455 status code (1000) for a graceful +// websocket close. Declared to keep vmscraper decoupled from the transport +// package. +const closeCodeNormalClosure = 1000 + +// isAbnormalClose reports a closeCoder whose close code is neither 0 nor +// closeCodeNormalClosure (those fall through to the ordinary io.EOF branch). +func isAbnormalClose(err error, target *closeCoder) bool { + if !errors.As(err, target) { + return false + } + code, _ := (*target).CloseCode() + return code != 0 && code != closeCodeNormalClosure +} + type vmStateSnapshot struct { lastToken string lastForwardedAt time.Time diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 6df670a5857ac..5c2fa24e4a798 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -10,6 +10,7 @@ import ( "testing/synctest" "time" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stackrox/rox/generated/internalapi/central" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" @@ -92,6 +93,15 @@ func (nopCloser) Read([]byte) (int, error) { return 0, io.EOF } func (nopCloser) Write([]byte) (int, error) { return 0, nil } func (nopCloser) Close() error { return nil } +// fakeCloseCoder is a minimal closeCoder for testing without a real dialer. +type fakeCloseCoder struct { + code int + reason string +} + +func (e *fakeCloseCoder) Error() string { return "connection closed" } +func (e *fakeCloseCoder) CloseCode() (int, string) { return e.code, e.reason } + type mockProtocolClient struct { mu sync.Mutex resultQueue []*vsockclient.GetReportResult @@ -662,8 +672,8 @@ func TestVMScraper_GetReportTimeoutClassified(t *testing.T) { } s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) - timeoutBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusTimeout)) - readErrBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)) + timeoutBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout)) + readErrBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)) ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -671,8 +681,8 @@ func TestVMScraper_GetReportTimeoutClassified(t *testing.T) { assert.Equal(t, scrapeNonRetryable, outcome, "parent cancellation must not schedule a retry on the short tick") - assert.Equal(t, timeoutBefore+1, testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusTimeout))) - assert.Equal(t, readErrBefore, testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)), + assert.Equal(t, timeoutBefore+1, testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout))) + assert.Equal(t, readErrBefore, testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)), "timed-out read must not be counted as a protocol/read error") } @@ -703,14 +713,14 @@ func TestVMScraper_GetReportBusyClassified(t *testing.T) { } s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) - busyBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy)) - readErrBefore := testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)) + busyBefore := testutil.ToFloat64(metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportBusy)) + readErrBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)) _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") assert.Equal(t, scrapeRetryable, outcome) - assert.Equal(t, busyBefore+1, testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusBusy))) - assert.Equal(t, readErrBefore, testutil.ToFloat64(metrics.PullRequestsTotal.WithLabelValues(metrics.PullStatusReadError)), + assert.Equal(t, busyBefore+1, testutil.ToFloat64(metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportBusy))) + assert.Equal(t, readErrBefore, testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)), "a busy response must not be counted as a generic protocol/read error") } @@ -762,6 +772,184 @@ func cachedToken(t *testing.T, s *VMScraper, key string) string { }) } +type pullOutcomeSample struct { + vec *prometheus.CounterVec + label string +} + +func allPullOutcomeSamples() []pullOutcomeSample { + var out []pullOutcomeSample + for _, label := range []string{ + metrics.PullTransportDialError, + metrics.PullTransportTimeout, + metrics.PullTransportReadError, + metrics.PullTransportAbnormalClose, + metrics.PullTransportUnexpected, + } { + out = append(out, pullOutcomeSample{metrics.PullTransportTotal, label}) + } + for _, label := range []string{ + metrics.PullGetReportUnchanged, + metrics.PullGetReportNotReady, + metrics.PullGetReportUnknownMethod, + metrics.PullGetReportBusy, + metrics.PullGetReportInternalError, + metrics.PullGetReportMalformedRequest, + metrics.PullGetReportRequestTooLarge, + metrics.PullGetReportUnknownAgentError, + } { + out = append(out, pullOutcomeSample{metrics.PullGetReportTotal, label}) + } + for _, label := range []string{ + metrics.PullScrapeSuccess, + metrics.PullScrapeInvalidReport, + metrics.PullScrapeSendError, + } { + out = append(out, pullOutcomeSample{metrics.PullScrapeTotal, label}) + } + return out +} + +func snapshotPullOutcomes() map[pullOutcomeSample]float64 { + snap := make(map[pullOutcomeSample]float64, len(allPullOutcomeSamples())) + for _, sample := range allPullOutcomeSamples() { + snap[sample] = testutil.ToFloat64(sample.vec.WithLabelValues(sample.label)) + } + return snap +} + +func assertOnlyPullOutcomeIncremented(t *testing.T, before map[pullOutcomeSample]float64, wantVec *prometheus.CounterVec, wantLabel string) { + t.Helper() + for _, sample := range allPullOutcomeSamples() { + got := testutil.ToFloat64(sample.vec.WithLabelValues(sample.label)) + want := before[sample] + if sample.vec == wantVec && sample.label == wantLabel { + want++ + } + assert.Equal(t, want, got, "status %q", sample.label) + } +} + +// TestHandleGetReportError_ClassifiesEveryErrorCode locks the mapping from +// each error to its metric label and retry classification. +func TestHandleGetReportError_ClassifiesEveryErrorCode(t *testing.T) { + cases := map[string]struct { + err error + wantMetric *prometheus.CounterVec + wantLabel string + wantOutcome scrapeOutcome + }{ + "NOT_READY maps to get_report not_ready": { + err: fmt.Errorf("%w: still scanning", vsockclient.ErrNotReady), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportNotReady, + wantOutcome: scrapeRetryable, + }, + "UNKNOWN_METHOD maps to get_report unknown_method": { + err: fmt.Errorf("%w: no get_report", vsockclient.ErrUnknownMethod), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportUnknownMethod, + wantOutcome: scrapeNonRetryable, + }, + "BUSY maps to get_report busy": { + err: fmt.Errorf("%w: another request in flight", vsockclient.ErrBusy), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportBusy, + wantOutcome: scrapeRetryable, + }, + "INTERNAL maps to get_report internal_error": { + err: fmt.Errorf("%w: panic recovered", vsockclient.ErrInternal), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportInternalError, + wantOutcome: scrapeRetryable, + }, + "MALFORMED_REQUEST maps to get_report malformed_request": { + err: fmt.Errorf("%w: empty request_id", vsockclient.ErrMalformedRequest), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportMalformedRequest, + wantOutcome: scrapeNonRetryable, + }, + "REQUEST_TOO_LARGE maps to get_report request_too_large": { + err: fmt.Errorf("%w: payload too big", vsockclient.ErrRequestTooLarge), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportRequestTooLarge, + wantOutcome: scrapeNonRetryable, + }, + "unrecognized agent error code maps to get_report unknown_agent_error": { + err: fmt.Errorf("%w: agent error (99): ?", vsockclient.ErrUnknownAgentError), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportUnknownAgentError, + wantOutcome: scrapeRetryable, + }, + "abnormal websocket close maps to transport abnormal_close": { + err: fmt.Errorf("reading response: %w", &fakeCloseCoder{code: 1006, reason: "abnormal closure"}), + wantMetric: metrics.PullTransportTotal, + wantLabel: metrics.PullTransportAbnormalClose, + wantOutcome: scrapeRetryable, + }, + "plain io.EOF maps to transport read_error": { + err: io.EOF, + wantMetric: metrics.PullTransportTotal, + wantLabel: metrics.PullTransportReadError, + wantOutcome: scrapeRetryable, + }, + "io.ErrUnexpectedEOF maps to transport read_error": { + err: io.ErrUnexpectedEOF, + wantMetric: metrics.PullTransportTotal, + wantLabel: metrics.PullTransportReadError, + wantOutcome: scrapeRetryable, + }, + "unrecognized transport/framing error maps to transport unexpected": { + err: errors.New("unmarshaling response: unexpected EOF in the middle of a varint"), + wantMetric: metrics.PullTransportTotal, + wantLabel: metrics.PullTransportUnexpected, + wantOutcome: scrapeRetryable, + }, + } + + s := &VMScraper{} + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + before := snapshotPullOutcomes() + outcome := s.handleGetReportError(t.Context(), "ns/vm", tc.err) + assert.Equal(t, tc.wantOutcome, outcome) + assertOnlyPullOutcomeIncremented(t, before, tc.wantMetric, tc.wantLabel) + }) + } +} + +// TestIsAbnormalClose locks which close codes take the abnormal-close path +// versus the ordinary EOF path. +func TestIsAbnormalClose(t *testing.T) { + cases := map[string]struct { + err error + want bool + }{ + "normal closure (1000) is not abnormal": { + err: &fakeCloseCoder{code: closeCodeNormalClosure, reason: "bye"}, + want: false, + }, + "zero code (no structured signal) is not abnormal": { + err: &fakeCloseCoder{code: 0}, + want: false, + }, + "abnormal closure (1006) is abnormal": { + err: &fakeCloseCoder{code: 1006, reason: "abnormal closure"}, + want: true, + }, + "a plain io.EOF is not a closeCoder at all": { + err: io.EOF, + want: false, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + var target closeCoder + assert.Equal(t, tc.want, isAbnormalClose(tc.err, &target)) + }) + } +} + func newTestScraper(t *testing.T, store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { t.Helper() centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) @@ -981,6 +1169,18 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { wantBackoff: 0, wantGap: 5 * time.Minute, // matches newTestScraper interval }, + "ErrMalformedRequest should not retry using backoff": { + client: &mockProtocolClient{errQueue: []error{vsockclient.ErrMalformedRequest}}, + sender: &mockSender{}, + wantBackoff: 0, + wantGap: 5 * time.Minute, + }, + "ErrRequestTooLarge should not retry using backoff": { + client: &mockProtocolClient{errQueue: []error{vsockclient.ErrRequestTooLarge}}, + sender: &mockSender{}, + wantBackoff: 0, + wantGap: 5 * time.Minute, + }, "invalid report should not retry using backoff": { client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{{ Meta: &pb.ResponseMeta{ReportToken: "1"}, diff --git a/sensor/common/virtualmachine/vsockclient/client.go b/sensor/common/virtualmachine/vsockclient/client.go index 499e7575f6c20..06a27954e4199 100644 --- a/sensor/common/virtualmachine/vsockclient/client.go +++ b/sensor/common/virtualmachine/vsockclient/client.go @@ -27,8 +27,15 @@ var ( ErrUnknownMethod = errors.New("agent does not support the requested method") // ErrInternal indicates the agent encountered an internal error. ErrInternal = errors.New("agent internal error") + // ErrMalformedRequest indicates the agent rejected the request as invalid. + ErrMalformedRequest = errors.New("agent rejected request as malformed") + // ErrRequestTooLarge indicates the request exceeded the agent's size limit. + ErrRequestTooLarge = errors.New("agent rejected request as too large") // ErrBusy indicates the agent's single connection slot is held by another request. ErrBusy = errors.New("agent is busy with another request") + // ErrUnknownAgentError indicates a well-formed ErrorResponse whose code + // this client doesn't recognize (e.g. a future ErrorCode value). + ErrUnknownAgentError = errors.New("unrecognized agent error code") ) // GetReportResult holds the parsed response from a GetReport call. @@ -141,9 +148,13 @@ func errorFromResponse(e *pb.ErrorResponse) error { return fmt.Errorf("%w: %s", ErrUnknownMethod, e.GetMessage()) case pb.ErrorCode_ERROR_CODE_INTERNAL: return fmt.Errorf("%w: %s", ErrInternal, e.GetMessage()) + case pb.ErrorCode_ERROR_CODE_MALFORMED_REQUEST: + return fmt.Errorf("%w: %s", ErrMalformedRequest, e.GetMessage()) + case pb.ErrorCode_ERROR_CODE_REQUEST_TOO_LARGE: + return fmt.Errorf("%w: %s", ErrRequestTooLarge, e.GetMessage()) case pb.ErrorCode_ERROR_CODE_BUSY: return fmt.Errorf("%w: %s", ErrBusy, e.GetMessage()) default: - return fmt.Errorf("agent error (%s): %s", e.GetCode(), e.GetMessage()) + return fmt.Errorf("%w: agent error (%s): %s", ErrUnknownAgentError, e.GetCode(), e.GetMessage()) } } diff --git a/sensor/common/virtualmachine/vsockclient/client_test.go b/sensor/common/virtualmachine/vsockclient/client_test.go index 18b7d0c252cdf..2c1dc85ef3726 100644 --- a/sensor/common/virtualmachine/vsockclient/client_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_test.go @@ -120,10 +120,28 @@ func TestSendGetReport_ErrorCodes(t *testing.T) { wantErr: ErrInternal, wantInMsg: "scan crashed", }, + "should wrap ErrMalformedRequest for MALFORMED_REQUEST": { + code: pb.ErrorCode_ERROR_CODE_MALFORMED_REQUEST, + message: "empty request_id", + wantErr: ErrMalformedRequest, + wantInMsg: "empty request_id", + }, + "should wrap ErrRequestTooLarge for REQUEST_TOO_LARGE": { + code: pb.ErrorCode_ERROR_CODE_REQUEST_TOO_LARGE, + message: "payload exceeds 10MB limit", + wantErr: ErrRequestTooLarge, + wantInMsg: "10MB", + }, "should wrap ErrBusy for BUSY": { - code: pb.ErrorCode_ERROR_CODE_BUSY, - message: "agent is already serving another request", - wantErr: ErrBusy, + code: pb.ErrorCode_ERROR_CODE_BUSY, + message: "agent is already serving another request", + wantErr: ErrBusy, + wantInMsg: "another request", + }, + "should wrap ErrUnknownAgentError for UNSPECIFIED": { + code: pb.ErrorCode_ERROR_CODE_UNSPECIFIED, + message: "", + wantErr: ErrUnknownAgentError, }, } for name, tc := range cases { diff --git a/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go b/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go index 0161e4a61fc7a..13db8a99b8a2b 100644 --- a/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go +++ b/sensor/kubernetes/virtualmachine/vsockdialer/dialer.go @@ -6,11 +6,14 @@ package vsockdialer import ( "context" + "errors" "fmt" "io" + "net" "net/url" "strconv" + "github.com/gorilla/websocket" "github.com/stackrox/rox/pkg/k8sutil" "k8s.io/client-go/rest" kvcorev1 "kubevirt.io/client-go/kubevirt/typed/core/v1" @@ -53,5 +56,73 @@ func (d *MultiDialer) Dial(ctx context.Context, namespace, name string, port uin if deadline, ok := ctx.Deadline(); ok { _ = conn.SetDeadline(deadline) } - return conn, nil + return &closeCodeConn{Conn: conn}, nil +} + +// closeCodeConn remaps kubevirt AsConn() close errors into closedError so +// callers can recover the websocket close code via errors.As against their +// own CloseCode() interface, without importing this package. +type closeCodeConn struct { + net.Conn +} + +func (c *closeCodeConn) Read(p []byte) (int, error) { + n, err := c.Conn.Read(p) + if closeErr := transportClosedErr(err); closeErr != nil { + return n, closeErr + } + return n, err //nolint:wrapcheck // implements io.Reader +} + +func (c *closeCodeConn) Write(p []byte) (int, error) { + n, err := c.Conn.Write(p) + if closeErr := transportClosedErr(err); closeErr != nil { + return n, closeErr + } + return n, err //nolint:wrapcheck // implements io.Writer +} + +// transportClosedErr returns a *closedError if err represents a closed +// connection, or nil if it's some other read failure. +func transportClosedErr(err error) *closedError { + if err == nil { + return nil + } + if ce, ok := errors.AsType[*websocket.CloseError](err); ok { + return &closedError{code: ce.Code, reason: ce.Text} + } + if errors.Is(err, io.EOF) { + return &closedError{} + } + return nil +} + +// closedError indicates the websocket connection closed before a complete +// VMServiceResponse could be read. Kept unexported and dependency-free so +// callers recover it structurally (via errors.As against their own +// CloseCode() interface) rather than importing this package. +type closedError struct { + code int + reason string +} + +func (e *closedError) Error() string { + if e.code == 0 { + return "websocket connection closed" + } + return fmt.Sprintf("websocket connection closed (code %d): %s", e.code, e.reason) +} + +// Is reports whether this close is an io.EOF-equivalent (no code, or a +// normal 1000 close). Abnormal codes such as 1006 are not EOF. +func (e *closedError) Is(target error) bool { + if target != io.EOF { + return false + } + return e.code == 0 || e.code == websocket.CloseNormalClosure +} + +// CloseCode returns the close code and reason; code is 0 if unstructured. +func (e *closedError) CloseCode() (code int, reason string) { + return e.code, e.reason } diff --git a/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go b/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go new file mode 100644 index 0000000000000..1a68da0c9a93e --- /dev/null +++ b/sensor/kubernetes/virtualmachine/vsockdialer/dialer_test.go @@ -0,0 +1,57 @@ +package vsockdialer + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTransportClosedErr(t *testing.T) { + cases := map[string]struct { + err error + wantNil bool + wantCode int + wantEOF bool + }{ + "should return nil for a non-close error": { + err: errors.New("boom"), + wantNil: true, + }, + "should classify a plain io.EOF with no structured code": { + err: io.EOF, + wantCode: 0, + wantEOF: true, + }, + "should preserve the close code from a websocket.CloseError": { + err: &websocket.CloseError{Code: websocket.CloseAbnormalClosure, Text: "abnormal closure"}, + wantCode: websocket.CloseAbnormalClosure, + }, + "should treat a normal closure as io.EOF": { + err: fmt.Errorf("read: %w", &websocket.CloseError{Code: websocket.CloseNormalClosure, Text: "bye"}), + wantCode: websocket.CloseNormalClosure, + wantEOF: true, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := transportClosedErr(tc.err) + if tc.wantNil { + assert.Nil(t, got) + return + } + require.NotNil(t, got) + code, _ := got.CloseCode() + assert.Equal(t, tc.wantCode, code) + if tc.wantEOF { + assert.ErrorIs(t, got, io.EOF) + return + } + assert.NotErrorIs(t, got, io.EOF) + }) + } +} From 424aed7339d8e213034407dd27d2f208a537cb3d Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Thu, 27 Aug 2026 08:43:14 +0000 Subject: [PATCH 108/638] ROX-36554: wait for deployment teardown to sync (#22473) --- tests/excluded_scopes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/excluded_scopes_test.go b/tests/excluded_scopes_test.go index b21757b007652..e65841ee177e5 100644 --- a/tests/excluded_scopes_test.go +++ b/tests/excluded_scopes_test.go @@ -20,7 +20,7 @@ func TestExcludedScopes(t *testing.T) { deploymentName := fmt.Sprintf("test-excluded-scopes-%d", rand.Intn(10000)) setupDeploymentInNamespace(t, "quay.io/rhacs-eng/qa-multi-arch-nginx:latest", deploymentName, "default") - defer teardownDeploymentWithoutCheck(t, deploymentName, "default") + defer teardownDeployment(t, deploymentName, "default") waitForDeploymentInCentral(t, deploymentName) verifyNoAlertForExcludedScopes(t, deploymentName) From ef79fa18427d6e6ce9b1b94c7e10410bc7403eed Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Thu, 27 Aug 2026 08:48:43 +0000 Subject: [PATCH 109/638] ROX-36538: increase waitForClusterHealthy retry (#22472) --- qa-tests-backend/src/test/groovy/BaseSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa-tests-backend/src/test/groovy/BaseSpecification.groovy b/qa-tests-backend/src/test/groovy/BaseSpecification.groovy index b9fd54897e044..eb3302abc7d13 100644 --- a/qa-tests-backend/src/test/groovy/BaseSpecification.groovy +++ b/qa-tests-backend/src/test/groovy/BaseSpecification.groovy @@ -459,7 +459,7 @@ class BaseSpecification extends Specification { return Env.get("IS_RACE_BUILD", null) == "true" || Env.CI_JOB_NAME == "race-condition-qa-e2e-tests" } - @Retry(attempts = 30, delay = 3) + @Retry(attempts = 60, delay = 5) static void waitForClusterHealthy() { ClusterOuterClass.ClusterHealthStatus status = ClusterService.getCluster().healthStatus assert status.overallHealthStatus == ClusterOuterClass.ClusterHealthStatus.HealthStatusLabel.HEALTHY From e3d8bc5573378ae98f65d0201c92e0bfa1872fe1 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:02:53 +0200 Subject: [PATCH 110/638] ROX-36555: Drain persist goroutines before TempDir cleanup (#22499) --- .../roxagent/vsockserver/sensor_updater.go | 14 +++- .../vsockserver/sensor_updater_test.go | 78 +++++++++---------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go index 9e81e9afa5a2f..6a9055779197f 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go @@ -34,6 +34,9 @@ type SensorUpdater struct { // persistMu serializes cachePath writes. persistActive is the only // acquirer; it takes persistMu, then mu, never the reverse. persistMu sync.Mutex + // persistWG tracks persistAndNotify's background writes so tests can + // drain them before TempDir cleanup. + persistWG sync.WaitGroup } // NewSensorUpdater seeds active from cachePath, else bundledPath, else @@ -189,16 +192,23 @@ func (u *SensorUpdater) applyLocked(content []byte, hash string) { // background. Path is the write barrier if the file is needed before // that goroutine finishes. func (u *SensorUpdater) persistAndNotify() { - go func() { + u.persistWG.Go(func() { if _, err := u.persistActive(); err != nil { log.Warnf("Persisting repo-to-CPE mapping cache to %q: %v", u.cachePath, err) } - }() + }) if u.onChange != nil { u.onChange() } } +// waitPersist blocks until every persistAndNotify goroutine has finished +// writing cachePath. Tests register it with t.Cleanup so TempDir removal +// cannot race AtomicWriteFile's sibling .tmp files. +func (u *SensorUpdater) waitPersist() { + u.persistWG.Wait() +} + // persistActive writes a copy of the current active mapping to cachePath. // It takes persistMu, then mu, so a late persist cannot overwrite the // file with a superseded mapping. diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go index aa77bcae032c2..294e2b1898ef1 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go @@ -4,7 +4,6 @@ import ( "os" "path/filepath" "testing" - "time" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" @@ -16,13 +15,6 @@ const validMappingJSON = `{"data":{"rhel-8-server-rpms":{"cpes":["cpe:/o:redhat: const otherValidMappingJSON = `{"data":{"rhel-9-server-rpms":{"cpes":["cpe:/o:redhat:enterprise_linux:9"]}}}` const invalidMappingJSON = `{not json` -// waitTimeout/waitTick bound assert.Eventually polling for the -// fire-and-forget cache-persistence goroutine shared by both updaters' tests. -const ( - waitTimeout = 2 * time.Second - waitTick = 10 * time.Millisecond -) - // onChangeCounter is a test double for a MappingProvider's onChange // callback that records how many times it fired. type onChangeCounter struct { @@ -36,22 +28,28 @@ func writeFile(t *testing.T, path, content string) { require.NoError(t, os.WriteFile(path, []byte(content), 0600)) } -// waitForCacheContent polls cachePath for the persist goroutine an applied -// Update starts in the background, so tests can drain it deterministically -// instead of letting it race the tempdir's end-of-test cleanup. -func waitForCacheContent(t *testing.T, cachePath, want string) { +func newSensorUpdater(t *testing.T, cachePath, bundledPath string, onChange func()) *SensorUpdater { + t.Helper() + u := NewSensorUpdater(cachePath, bundledPath, onChange) + t.Cleanup(u.waitPersist) + return u +} + +// waitForCacheContent drains persistAndNotify's background write, then +// checks cachePath, so the assertion cannot race AtomicWriteFile. +func waitForCacheContent(t *testing.T, u *SensorUpdater, cachePath, want string) { t.Helper() - assert.Eventually(t, func() bool { - b, err := os.ReadFile(cachePath) - return err == nil && string(b) == want - }, waitTimeout, waitTick, "expected %q to be persisted to %s", want, cachePath) + u.waitPersist() + b, err := os.ReadFile(cachePath) + require.NoError(t, err) + assert.Equal(t, want, string(b), "expected %q to be persisted to %s", want, cachePath) } func TestNewSensorUpdater_EmptyStart_NotReady(t *testing.T) { dir := t.TempDir() counter := &onChangeCounter{} - u := NewSensorUpdater(filepath.Join(dir, "cache.json"), "", counter.fn) + u := newSensorUpdater(t, filepath.Join(dir, "cache.json"), "", counter.fn) assert.False(t, u.Ready()) assert.Empty(t, u.Hash()) @@ -69,7 +67,7 @@ func TestNewSensorUpdater_BootstrapFromCache(t *testing.T) { writeFile(t, cachePath, validMappingJSON) counter := &onChangeCounter{} - u := NewSensorUpdater(cachePath, "", counter.fn) + u := newSensorUpdater(t, cachePath, "", counter.fn) require.True(t, u.Ready()) assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) @@ -85,7 +83,7 @@ func TestNewSensorUpdater_BootstrapFromBundled_WhenNoCache(t *testing.T) { writeFile(t, bundledPath, validMappingJSON) counter := &onChangeCounter{} - u := NewSensorUpdater(filepath.Join(dir, "cache.json"), bundledPath, counter.fn) + u := newSensorUpdater(t, filepath.Join(dir, "cache.json"), bundledPath, counter.fn) require.True(t, u.Ready()) assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) @@ -99,7 +97,7 @@ func TestNewSensorUpdater_CacheTakesPrecedenceOverBundled(t *testing.T) { writeFile(t, cachePath, validMappingJSON) writeFile(t, bundledPath, otherValidMappingJSON) - u := NewSensorUpdater(cachePath, bundledPath, func() {}) + u := newSensorUpdater(t, cachePath, bundledPath, func() {}) b, err := u.Bytes() require.NoError(t, err) @@ -113,7 +111,7 @@ func TestNewSensorUpdater_InvalidCacheFallsBackToBundled(t *testing.T) { writeFile(t, cachePath, invalidMappingJSON) writeFile(t, bundledPath, validMappingJSON) - u := NewSensorUpdater(cachePath, bundledPath, func() {}) + u := newSensorUpdater(t, cachePath, bundledPath, func() {}) require.True(t, u.Ready()) b, err := u.Bytes() @@ -127,7 +125,7 @@ func TestNewSensorUpdater_InvalidCacheNoBundled_NotReady(t *testing.T) { writeFile(t, cachePath, invalidMappingJSON) counter := &onChangeCounter{} - u := NewSensorUpdater(cachePath, "", counter.fn) + u := newSensorUpdater(t, cachePath, "", counter.fn) assert.False(t, u.Ready()) assert.Equal(t, 0, counter.count) @@ -135,7 +133,7 @@ func TestNewSensorUpdater_InvalidCacheNoBundled_NotReady(t *testing.T) { func TestSensorUpdater_Update_OwnsContent(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "cache.json") - u := NewSensorUpdater(cachePath, "", func() {}) + u := newSensorUpdater(t, cachePath, "", func() {}) content := []byte(validMappingJSON) updated, err := u.Update(content) @@ -146,12 +144,12 @@ func TestSensorUpdater_Update_OwnsContent(t *testing.T) { b, err := u.Bytes() require.NoError(t, err) assert.Equal(t, validMappingJSON, string(b), "mutating the caller's buffer must not change the active mapping") - waitForCacheContent(t, cachePath, validMappingJSON) + waitForCacheContent(t, u, cachePath, validMappingJSON) } func TestSensorUpdater_Path_WritesCurrentActive(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "cache.json") - u := NewSensorUpdater(cachePath, "", func() {}) + u := newSensorUpdater(t, cachePath, "", func() {}) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) _, err = u.Update([]byte(otherValidMappingJSON)) @@ -163,14 +161,14 @@ func TestSensorUpdater_Path_WritesCurrentActive(t *testing.T) { require.NoError(t, err) assert.Equal(t, otherValidMappingJSON, string(onDisk), "Path must persist the current active mapping, not an in-flight older write") - waitForCacheContent(t, cachePath, otherValidMappingJSON) + waitForCacheContent(t, u, cachePath, otherValidMappingJSON) } func TestSensorUpdater_Update_AppliesWhenIdle(t *testing.T) { dir := t.TempDir() cachePath := filepath.Join(dir, "cache.json") counter := &onChangeCounter{} - u := NewSensorUpdater(cachePath, "", counter.fn) + u := newSensorUpdater(t, cachePath, "", counter.fn) updated, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) @@ -178,15 +176,15 @@ func TestSensorUpdater_Update_AppliesWhenIdle(t *testing.T) { assert.True(t, u.Ready()) assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), u.Hash()) assert.Equal(t, 1, counter.count) - waitForCacheContent(t, cachePath, validMappingJSON) + waitForCacheContent(t, u, cachePath, validMappingJSON) } func TestSensorUpdater_Update_NoOpWhenUnchanged(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "cache.json") - u := NewSensorUpdater(cachePath, "", func() {}) + u := newSensorUpdater(t, cachePath, "", func() {}) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) - waitForCacheContent(t, cachePath, validMappingJSON) + waitForCacheContent(t, u, cachePath, validMappingJSON) updated, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) @@ -195,11 +193,11 @@ func TestSensorUpdater_Update_NoOpWhenUnchanged(t *testing.T) { func TestSensorUpdater_Update_RejectsInvalidKeepsLastGood(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "cache.json") - u := NewSensorUpdater(cachePath, "", func() {}) + u := newSensorUpdater(t, cachePath, "", func() {}) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) wantHash := u.Hash() - waitForCacheContent(t, cachePath, validMappingJSON) + waitForCacheContent(t, u, cachePath, validMappingJSON) updated, err := u.Update([]byte(invalidMappingJSON)) assert.Error(t, err) @@ -208,7 +206,7 @@ func TestSensorUpdater_Update_RejectsInvalidKeepsLastGood(t *testing.T) { } func TestSensorUpdater_Update_OversizeRejected(t *testing.T) { - u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + u := newSensorUpdater(t, filepath.Join(t.TempDir(), "cache.json"), "", func() {}) oversized := make([]byte, cpemapping.MaxMappingBytes+1) updated, err := u.Update(oversized) @@ -221,7 +219,7 @@ func TestSensorUpdater_UpdateWhileBusy_DefersUntilIdle(t *testing.T) { dir := t.TempDir() cachePath := filepath.Join(dir, "cache.json") counter := &onChangeCounter{} - u := NewSensorUpdater(cachePath, "", counter.fn) + u := newSensorUpdater(t, cachePath, "", counter.fn) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) require.Equal(t, 1, counter.count) @@ -250,11 +248,11 @@ func TestSensorUpdater_UpdateWhileBusy_DefersUntilIdle(t *testing.T) { require.NoError(t, err) assert.Equal(t, otherValidMappingJSON, string(b)) assert.Equal(t, 2, counter.count, "onChange must fire once the pending mapping is promoted") - waitForCacheContent(t, cachePath, otherValidMappingJSON) + waitForCacheContent(t, u, cachePath, otherValidMappingJSON) } func TestSensorUpdater_UpdateWhileBusy_SameAsPending_NoOp(t *testing.T) { - u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + u := newSensorUpdater(t, filepath.Join(t.TempDir(), "cache.json"), "", func() {}) u.MarkScanBusy() updated, err := u.Update([]byte(validMappingJSON)) @@ -271,7 +269,7 @@ func TestSensorUpdater_UpdateWhileBusy_SameAsPending_NoOp(t *testing.T) { // are ordered, so the newest push (matching active) must win, not the // earlier, now-stale pending content. func TestSensorUpdater_UpdateWhileBusy_RevertToActive_ClearsStalePending(t *testing.T) { - u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + u := newSensorUpdater(t, filepath.Join(t.TempDir(), "cache.json"), "", func() {}) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) u.MarkScanBusy() @@ -293,11 +291,11 @@ func TestSensorUpdater_UpdateWhileBusy_RevertToActive_ClearsStalePending(t *test func TestSensorUpdater_MarkScanIdleAndApplyPending_NoPending_NoOp(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "cache.json") counter := &onChangeCounter{} - u := NewSensorUpdater(cachePath, "", counter.fn) + u := newSensorUpdater(t, cachePath, "", counter.fn) _, err := u.Update([]byte(validMappingJSON)) require.NoError(t, err) require.Equal(t, 1, counter.count) - waitForCacheContent(t, cachePath, validMappingJSON) + waitForCacheContent(t, u, cachePath, validMappingJSON) u.MarkScanBusy() u.MarkScanIdleAndApplyPending() @@ -307,6 +305,6 @@ func TestSensorUpdater_MarkScanIdleAndApplyPending_NoPending_NoOp(t *testing.T) } func TestSensorUpdater_UpdatePath_IsSensor(t *testing.T) { - u := NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", func() {}) + u := newSensorUpdater(t, filepath.Join(t.TempDir(), "cache.json"), "", func() {}) assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, u.UpdatePath()) } From 5246cb9c0b6aea273f0054628aca1fae84a03552 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:11:24 +0000 Subject: [PATCH 111/638] Update Konflux references (#22429) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 16 ++++++++-------- .tekton/main-pipeline.yaml | 16 ++++++++-------- .tekton/operator-bundle-pipeline.yaml | 16 ++++++++-------- .tekton/scanner-v4-pipeline.yaml | 16 ++++++++-------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 9f07200935c6a..3009aaa8b8efd 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -379,7 +379,7 @@ spec: operator: in values: ["false"] - - name: clair-scan + - name: roxctl-scan matrix: params: - name: image-platform @@ -393,9 +393,9 @@ spec: taskRef: params: - name: name - value: clair-scan + value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 - name: kind value: task resolver: bundles @@ -444,7 +444,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:beb05ae2fad733783b3b17e05871e0eaf527a07c904cf3fb2cb551025007eec1 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 - name: kind value: task resolver: bundles @@ -470,7 +470,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:d09f717cb84f0699a531773bc498745deef2c006a81918aa73b3be6d2f4778bd + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 - name: kind value: task resolver: bundles @@ -496,7 +496,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:b78e9c05c5126613eddee53f2f3534a9b86bfca17d903599f2fa465b7938e241 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 - name: kind value: task resolver: bundles @@ -521,7 +521,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.1@sha256:53a02326bfb930ca5ef6bfa7a33acca833d57752f34f3cb79255fe2e25e7d217 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 - name: kind value: task resolver: bundles @@ -541,7 +541,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index c0cb4afdac167..8f6136f7dfa8a 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -401,7 +401,7 @@ spec: operator: in values: ["false"] - - name: clair-scan + - name: roxctl-scan matrix: params: - name: image-platform @@ -415,9 +415,9 @@ spec: taskRef: params: - name: name - value: clair-scan + value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 - name: kind value: task resolver: bundles @@ -466,7 +466,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:beb05ae2fad733783b3b17e05871e0eaf527a07c904cf3fb2cb551025007eec1 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 - name: kind value: task resolver: bundles @@ -492,7 +492,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:d09f717cb84f0699a531773bc498745deef2c006a81918aa73b3be6d2f4778bd + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 - name: kind value: task resolver: bundles @@ -518,7 +518,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:b78e9c05c5126613eddee53f2f3534a9b86bfca17d903599f2fa465b7938e241 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 - name: kind value: task resolver: bundles @@ -543,7 +543,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.1@sha256:53a02326bfb930ca5ef6bfa7a33acca833d57752f34f3cb79255fe2e25e7d217 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 - name: kind value: task resolver: bundles @@ -563,7 +563,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 2cf18c446b124..77847a6a21aa3 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -585,7 +585,7 @@ spec: operator: in values: ["false"] - - name: clair-scan + - name: roxctl-scan params: - name: image-digest value: $(tasks.build-container.results.IMAGE_DIGEST) @@ -594,9 +594,9 @@ spec: taskRef: params: - name: name - value: clair-scan + value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 - name: kind value: task resolver: bundles @@ -644,7 +644,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:beb05ae2fad733783b3b17e05871e0eaf527a07c904cf3fb2cb551025007eec1 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 - name: kind value: task resolver: bundles @@ -670,7 +670,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:d09f717cb84f0699a531773bc498745deef2c006a81918aa73b3be6d2f4778bd + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 - name: kind value: task resolver: bundles @@ -696,7 +696,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:b78e9c05c5126613eddee53f2f3534a9b86bfca17d903599f2fa465b7938e241 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 - name: kind value: task resolver: bundles @@ -716,7 +716,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.1@sha256:53a02326bfb930ca5ef6bfa7a33acca833d57752f34f3cb79255fe2e25e7d217 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 - name: kind value: task resolver: bundles @@ -736,7 +736,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 83074834a4ae4..e3838e0ab07c4 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -399,7 +399,7 @@ spec: operator: in values: ["false"] - - name: clair-scan + - name: roxctl-scan matrix: params: - name: image-platform @@ -413,9 +413,9 @@ spec: taskRef: params: - name: name - value: clair-scan + value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.0@sha256:dcc6ca58d3ec03d05ce21c03b4f51b58cac9878caa932ad06a9fdf4bc422aaab + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 - name: kind value: task resolver: bundles @@ -464,7 +464,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:beb05ae2fad733783b3b17e05871e0eaf527a07c904cf3fb2cb551025007eec1 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 - name: kind value: task resolver: bundles @@ -490,7 +490,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:d09f717cb84f0699a531773bc498745deef2c006a81918aa73b3be6d2f4778bd + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 - name: kind value: task resolver: bundles @@ -516,7 +516,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:b78e9c05c5126613eddee53f2f3534a9b86bfca17d903599f2fa465b7938e241 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 - name: kind value: task resolver: bundles @@ -541,7 +541,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.1@sha256:53a02326bfb930ca5ef6bfa7a33acca833d57752f34f3cb79255fe2e25e7d217 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 - name: kind value: task resolver: bundles @@ -561,7 +561,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.1@sha256:538a853c4a299216c43258ccc9894215f5440dc0c70c9c99fe22fc9633a8e79d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d - name: kind value: task resolver: bundles From 7f49801343105c79d39c121fd718916ebbb5369f Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:19:08 +0000 Subject: [PATCH 112/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to ba2197b (#22489) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 3009aaa8b8efd..57a648834e04f 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 751e04259fbe8..a9e0837bcaed4 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 5ac9f65d1e2e5..50f920d5d9aae 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 8f6136f7dfa8a..90f89c4426bcd 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 77847a6a21aa3..695ff6fa8eb5e 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 66345daafd02e..ef0036ce0e046 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 71af227a56948..9aee8d90173af 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index e3838e0ab07c4..87d402795b8ec 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d6819ce66939d63ae878e97ab26665872570179ccd43843b93b3009f44f7e401 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:ba2197bef9231168c5c51f1f3afb297396ac433f0538d40b2c63d84db1f01f82 - name: kind value: task resolver: bundles From 8fd8c0ebb81a2498a3c199d73d3bff6a9017c66b Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:01:53 +0200 Subject: [PATCH 113/638] ROX-35448: add VM fleet telemetry via ClusterMetrics (#22311) Co-authored-by: Cursor --- central/cluster/datastore/telemetry.go | 59 +- central/cluster/datastore/telemetry_test.go | 86 +++ .../pipeline/clustermetrics/pipeline.go | 10 +- .../pipeline/clustermetrics/pipeline_test.go | 58 ++ .../internalapi/central/cluster_metrics.pb.go | 107 ++- .../central/cluster_metrics_vtproto.pb.go | 641 ++++++++++++++++++ pkg/centralsensor/caps_list.go | 4 + .../internalapi/central/cluster_metrics.proto | 9 + .../sensor/central_communication_impl.go | 1 + .../virtualmachine/vmscraper/scraper.go | 48 +- .../virtualmachine/vmscraper/scraper_test.go | 145 +++- .../common/virtualmachine/vmscraper/stats.go | 81 +++ .../virtualmachine/vmscraper/stats_test.go | 108 +++ .../clustermetrics/cluster_metrics.go | 41 +- .../clustermetrics/cluster_metrics_test.go | 65 +- sensor/kubernetes/sensor/sensor.go | 38 +- 16 files changed, 1421 insertions(+), 80 deletions(-) create mode 100644 central/cluster/datastore/telemetry_test.go create mode 100644 sensor/common/virtualmachine/vmscraper/stats.go create mode 100644 sensor/common/virtualmachine/vmscraper/stats_test.go diff --git a/central/cluster/datastore/telemetry.go b/central/cluster/datastore/telemetry.go index e75f00e398ff6..6d8186d474456 100644 --- a/central/cluster/datastore/telemetry.go +++ b/central/cluster/datastore/telemetry.go @@ -2,6 +2,10 @@ package datastore import ( "context" + "encoding/json" + "maps" + "slices" + "strings" "time" "github.com/stackrox/rox/central/telemetry/centralclient" @@ -79,9 +83,58 @@ var Gather phonehome.GatherFunc = func(ctx context.Context) (map[string]any, err return props, nil } +type versionEntry struct { + Version string `json:"version"` + Count int32 `json:"count"` +} + +// buildVMTraits returns Segment trait key/value pairs for VM telemetry. +// Returns nil when the Sensor lacks VirtualMachineTelemetryCap (old Sensor, +// leave existing traits untouched). +func buildVMTraits(hasCapability bool, vmm *central.VirtualMachineMetrics) map[string]any { + if !hasCapability { + return nil + } + if vmm == nil { + return map[string]any{ + "VM Scanning Enabled": false, + "VM Tracked Count": int32(0), + "VM Scanned Count": int32(0), + "VM Unscanned Count": int32(0), + "Roxagent Version Counts": "[]", + } + } + + traits := map[string]any{ + "VM Scanning Enabled": true, + "VM Tracked Count": vmm.GetTrackedVms(), + "VM Scanned Count": vmm.GetVmsScanned(), + "VM Unscanned Count": max(int32(0), vmm.GetTrackedVms()-vmm.GetVmsScanned()), + "Roxagent Version Counts": "[]", + } + + if vc := vmm.GetRoxagentVersionCounts(); len(vc) > 0 { + entries := make([]versionEntry, 0, len(vc)) + for v, c := range vc { + entries = append(entries, versionEntry{Version: v, Count: c}) + } + slices.SortFunc(entries, func(a, b versionEntry) int { + return strings.Compare(a.Version, b.Version) + }) + raw, err := json.Marshal(entries) + if err != nil { + log.Warnf("Failed to marshal roxagent version counts: %v", err) + return traits + } + traits["Roxagent Version Counts"] = string(raw) + } + + return traits +} + // UpdateSecuredClusterIdentity is called by the clustermetrics pipeline on // the reception of the cluster metrics from a sensor. -func UpdateSecuredClusterIdentity(ctx context.Context, clusterID string, metrics *central.ClusterMetrics) { +func UpdateSecuredClusterIdentity(ctx context.Context, clusterID string, metrics *central.ClusterMetrics, hasVMTelemetryCap bool) { c := centralclient.Singleton() // This is a shortcut to avoid calling the cluster datastore in case // telemetry is for sure not enabled. @@ -127,6 +180,10 @@ func UpdateSecuredClusterIdentity(ctx context.Context, clusterID string, metrics } props["Orchestrator Version"] = omd.GetVersion() + if vmTraits := buildVMTraits(hasVMTelemetryCap, metrics.GetVirtualMachineMetrics()); vmTraits != nil { + maps.Copy(props, vmTraits) + } + c.Track("Updated Secured Cluster Identity", nil, append( c.WithGroups(), telemeter.WithClient(clusterID, securedClusterClient, cluster.GetMainImage()), diff --git a/central/cluster/datastore/telemetry_test.go b/central/cluster/datastore/telemetry_test.go new file mode 100644 index 0000000000000..9ff1275ad590b --- /dev/null +++ b/central/cluster/datastore/telemetry_test.go @@ -0,0 +1,86 @@ +package datastore + +import ( + "testing" + + "github.com/stackrox/rox/generated/internalapi/central" + "github.com/stretchr/testify/assert" +) + +func TestBuildVMTraits(t *testing.T) { + tests := map[string]struct { + hasCapability bool + metrics *central.VirtualMachineMetrics + wantTraits map[string]any + }{ + "should set VM Scanning Enabled true with sorted version JSON when metrics are non-nil": { + hasCapability: true, + metrics: ¢ral.VirtualMachineMetrics{ + TrackedVms: 10, + VmsScanned: 7, + RoxagentVersionCounts: map[string]int32{ + "4.7.0-12-gabcdef0123": 4, + "4.8.0-3-gfedcba9876": 2, + "unknown": 1, + }, + }, + wantTraits: map[string]any{ + "VM Scanning Enabled": true, + "VM Tracked Count": int32(10), + "VM Scanned Count": int32(7), + "VM Unscanned Count": int32(3), + "Roxagent Version Counts": `[{"version":"4.7.0-12-gabcdef0123","count":4},{"version":"4.8.0-3-gfedcba9876","count":2},{"version":"unknown","count":1}]`, + }, + }, + "should zero traits including version counts when capable Sensor sends nil metrics (feature off)": { + hasCapability: true, + metrics: nil, + wantTraits: map[string]any{ + "VM Scanning Enabled": false, + "VM Tracked Count": int32(0), + "VM Scanned Count": int32(0), + "VM Unscanned Count": int32(0), + "Roxagent Version Counts": "[]", + }, + }, + "should set empty version counts for non-nil-but-all-zero metrics (feature on, zero VMs)": { + hasCapability: true, + metrics: ¢ral.VirtualMachineMetrics{}, + wantTraits: map[string]any{ + "VM Scanning Enabled": true, + "VM Tracked Count": int32(0), + "VM Scanned Count": int32(0), + "VM Unscanned Count": int32(0), + "Roxagent Version Counts": "[]", + }, + }, + "should omit unknown from version JSON when the map has no unknown bucket": { + hasCapability: true, + metrics: ¢ral.VirtualMachineMetrics{ + TrackedVms: 4, + VmsScanned: 4, + RoxagentVersionCounts: map[string]int32{ + "4.7.0-12-gabcdef0123": 4, + }, + }, + wantTraits: map[string]any{ + "VM Scanning Enabled": true, + "VM Tracked Count": int32(4), + "VM Scanned Count": int32(4), + "VM Unscanned Count": int32(0), + "Roxagent Version Counts": `[{"version":"4.7.0-12-gabcdef0123","count":4}]`, + }, + }, + "should return nil traits when Sensor lacks capability (old Sensor)": { + hasCapability: false, + metrics: nil, + wantTraits: nil, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.wantTraits, buildVMTraits(tc.hasCapability, tc.metrics)) + }) + } +} diff --git a/central/sensor/service/pipeline/clustermetrics/pipeline.go b/central/sensor/service/pipeline/clustermetrics/pipeline.go index 2e7f9e258140d..6051a4fc326ad 100644 --- a/central/sensor/service/pipeline/clustermetrics/pipeline.go +++ b/central/sensor/service/pipeline/clustermetrics/pipeline.go @@ -79,7 +79,7 @@ func (p *pipelineImpl) Run( ctx context.Context, clusterID string, msg *central.MsgFromSensor, - _ common.MessageInjector, + injector common.MessageInjector, ) error { clusterMetrics := msg.GetClusterMetrics() p.metricsStore.Set(clusterID, clusterMetrics) @@ -93,7 +93,7 @@ func (p *pipelineImpl) Run( logging.GetRateLimitedLogger().Warn( "Error while trying to update secured units usage: ", err.Error()) } - go clusterTelemetry.UpdateSecuredClusterIdentity(ctx, clusterID, clusterMetrics) + go clusterTelemetry.UpdateSecuredClusterIdentity(ctx, clusterID, clusterMetrics, hasVMTelemetryCap(injector)) return nil } @@ -101,3 +101,9 @@ func (p *pipelineImpl) OnFinish(clusterID string) { p.metricsStore.Set(clusterID, ¢ral.ClusterMetrics{}) p.telemetryMetrics.DeleteClusterMetrics(clusterID) } + +// hasVMTelemetryCap is true when the Sensor advertised VirtualMachineTelemetryCap. +// A nil injector is treated as no capability. +func hasVMTelemetryCap(injector common.MessageInjector) bool { + return injector != nil && injector.HasCapability(centralsensor.VirtualMachineTelemetryCap) +} diff --git a/central/sensor/service/pipeline/clustermetrics/pipeline_test.go b/central/sensor/service/pipeline/clustermetrics/pipeline_test.go index d6c8e2e62a941..2e7708398dcaa 100644 --- a/central/sensor/service/pipeline/clustermetrics/pipeline_test.go +++ b/central/sensor/service/pipeline/clustermetrics/pipeline_test.go @@ -6,9 +6,12 @@ import ( usageMocks "github.com/stackrox/rox/central/administration/usage/datastore/securedunits/mocks" telemetryMocks "github.com/stackrox/rox/central/metrics/telemetry/mocks" + "github.com/stackrox/rox/central/sensor/service/common" metricsMocks "github.com/stackrox/rox/central/sensor/service/pipeline/clustermetrics/mocks" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/centralsensor" + "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/fixtures" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" @@ -70,3 +73,58 @@ func (suite *PipelineTestSuite) TestClusterMetricsResetOnPipelineFinish() { suite.pipeline.OnFinish(clusterID) } + +func (suite *PipelineTestSuite) TestClusterMetricsMessageFromSensor_QueriesVMTelemetryCap() { + tests := map[string]struct { + caps map[centralsensor.SensorCapability]bool + }{ + "should query VirtualMachineTelemetryCap when Sensor advertises it": { + caps: map[centralsensor.SensorCapability]bool{ + centralsensor.VirtualMachineTelemetryCap: true, + }, + }, + "should query VirtualMachineTelemetryCap when Sensor lacks it": { + caps: map[centralsensor.SensorCapability]bool{}, + }, + } + for name, tc := range tests { + suite.Run(name, func() { + clusterID := fixtures.GetDeployment().GetClusterId() + expectedMetrics := ¢ral.ClusterMetrics{NodeCount: 1, CpuCapacity: 10} + + suite.metricsStore.EXPECT().Set(clusterID, expectedMetrics) + suite.telemetryMetrics.EXPECT().SetClusterMetrics(clusterID, expectedMetrics) + suite.usageStore.EXPECT().UpdateUsage(gomock.Any(), clusterID, &storage.SecuredUnits{ + NumNodes: expectedMetrics.GetNodeCount(), + NumCpuUnits: expectedMetrics.GetCpuCapacity(), + }).Return(nil) + + injector := &fakeInjector{caps: tc.caps} + err := suite.pipeline.Run(suite.T().Context(), clusterID, ¢ral.MsgFromSensor{ + Msg: ¢ral.MsgFromSensor_ClusterMetrics{ + ClusterMetrics: expectedMetrics, + }, + }, injector) + suite.NoError(err) + suite.Equal([]centralsensor.SensorCapability{centralsensor.VirtualMachineTelemetryCap}, injector.queried) + }) + } +} + +var _ common.MessageInjector = (*fakeInjector)(nil) + +type fakeInjector struct { + caps map[centralsensor.SensorCapability]bool + queried []centralsensor.SensorCapability +} + +func (f *fakeInjector) HasCapability(cap centralsensor.SensorCapability) bool { + f.queried = append(f.queried, cap) + return f.caps[cap] +} + +func (f *fakeInjector) InjectMessage(concurrency.Waitable, *central.MsgToSensor) error { + return nil +} + +func (f *fakeInjector) InjectMessageIntoQueue(*central.MsgFromSensor) {} diff --git a/generated/internalapi/central/cluster_metrics.pb.go b/generated/internalapi/central/cluster_metrics.pb.go index 0f499f4c5facf..db9f16eb351f9 100644 --- a/generated/internalapi/central/cluster_metrics.pb.go +++ b/generated/internalapi/central/cluster_metrics.pb.go @@ -28,8 +28,10 @@ type ClusterMetrics struct { NodeCount int64 `protobuf:"varint,1,opt,name=node_count,json=nodeCount,proto3" json:"node_count,omitempty"` // The number of nodes in the cluster accessible by Sensor. CpuCapacity int64 `protobuf:"varint,2,opt,name=cpu_capacity,json=cpuCapacity,proto3" json:"cpu_capacity,omitempty"` // The total cpu capacity of all nodes accessible by Sensor. ComplianceOperatorVersion string `protobuf:"bytes,3,opt,name=compliance_operator_version,json=complianceOperatorVersion,proto3" json:"compliance_operator_version,omitempty"` // Compliance operator version discovered by this Sensor. - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Filled only when ROX_VIRTUAL_MACHINES is enabled on this Sensor. + VirtualMachineMetrics *VirtualMachineMetrics `protobuf:"bytes,4,opt,name=virtual_machine_metrics,json=virtualMachineMetrics,proto3" json:"virtual_machine_metrics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ClusterMetrics) Reset() { @@ -83,16 +85,93 @@ func (x *ClusterMetrics) GetComplianceOperatorVersion() string { return "" } +func (x *ClusterMetrics) GetVirtualMachineMetrics() *VirtualMachineMetrics { + if x != nil { + return x.VirtualMachineMetrics + } + return nil +} + +type VirtualMachineMetrics struct { + state protoimpl.MessageState `protogen:"open.v1"` + TrackedVms int32 `protobuf:"varint,1,opt,name=tracked_vms,json=trackedVms,proto3" json:"tracked_vms,omitempty"` // VMs currently tracked for scraping. + VmsScanned int32 `protobuf:"varint,2,opt,name=vms_scanned,json=vmsScanned,proto3" json:"vms_scanned,omitempty"` // Tracked VMs with at least one successful scrape ever. + RoxagentVersionCounts map[string]int32 `protobuf:"bytes,3,rep,name=roxagent_version_counts,json=roxagentVersionCounts,proto3" json:"roxagent_version_counts,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // Version (or "unknown"/"other") -> count of scanned VMs. Unscanned VMs are omitted. + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VirtualMachineMetrics) Reset() { + *x = VirtualMachineMetrics{} + mi := &file_internalapi_central_cluster_metrics_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VirtualMachineMetrics) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VirtualMachineMetrics) ProtoMessage() {} + +func (x *VirtualMachineMetrics) ProtoReflect() protoreflect.Message { + mi := &file_internalapi_central_cluster_metrics_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VirtualMachineMetrics.ProtoReflect.Descriptor instead. +func (*VirtualMachineMetrics) Descriptor() ([]byte, []int) { + return file_internalapi_central_cluster_metrics_proto_rawDescGZIP(), []int{1} +} + +func (x *VirtualMachineMetrics) GetTrackedVms() int32 { + if x != nil { + return x.TrackedVms + } + return 0 +} + +func (x *VirtualMachineMetrics) GetVmsScanned() int32 { + if x != nil { + return x.VmsScanned + } + return 0 +} + +func (x *VirtualMachineMetrics) GetRoxagentVersionCounts() map[string]int32 { + if x != nil { + return x.RoxagentVersionCounts + } + return nil +} + var File_internalapi_central_cluster_metrics_proto protoreflect.FileDescriptor const file_internalapi_central_cluster_metrics_proto_rawDesc = "" + "\n" + - ")internalapi/central/cluster_metrics.proto\x12\acentral\"\x92\x01\n" + + ")internalapi/central/cluster_metrics.proto\x12\acentral\"\xea\x01\n" + "\x0eClusterMetrics\x12\x1d\n" + "\n" + "node_count\x18\x01 \x01(\x03R\tnodeCount\x12!\n" + "\fcpu_capacity\x18\x02 \x01(\x03R\vcpuCapacity\x12>\n" + - "\x1bcompliance_operator_version\x18\x03 \x01(\tR\x19complianceOperatorVersionB\x1fZ\x1d./internalapi/central;centralb\x06proto3" + "\x1bcompliance_operator_version\x18\x03 \x01(\tR\x19complianceOperatorVersion\x12V\n" + + "\x17virtual_machine_metrics\x18\x04 \x01(\v2\x1e.central.VirtualMachineMetricsR\x15virtualMachineMetrics\"\x96\x02\n" + + "\x15VirtualMachineMetrics\x12\x1f\n" + + "\vtracked_vms\x18\x01 \x01(\x05R\n" + + "trackedVms\x12\x1f\n" + + "\vvms_scanned\x18\x02 \x01(\x05R\n" + + "vmsScanned\x12q\n" + + "\x17roxagent_version_counts\x18\x03 \x03(\v29.central.VirtualMachineMetrics.RoxagentVersionCountsEntryR\x15roxagentVersionCounts\x1aH\n" + + "\x1aRoxagentVersionCountsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x05R\x05value:\x028\x01B\x1fZ\x1d./internalapi/central;centralb\x06proto3" var ( file_internalapi_central_cluster_metrics_proto_rawDescOnce sync.Once @@ -106,16 +185,20 @@ func file_internalapi_central_cluster_metrics_proto_rawDescGZIP() []byte { return file_internalapi_central_cluster_metrics_proto_rawDescData } -var file_internalapi_central_cluster_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_internalapi_central_cluster_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_internalapi_central_cluster_metrics_proto_goTypes = []any{ - (*ClusterMetrics)(nil), // 0: central.ClusterMetrics + (*ClusterMetrics)(nil), // 0: central.ClusterMetrics + (*VirtualMachineMetrics)(nil), // 1: central.VirtualMachineMetrics + nil, // 2: central.VirtualMachineMetrics.RoxagentVersionCountsEntry } var file_internalapi_central_cluster_metrics_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: central.ClusterMetrics.virtual_machine_metrics:type_name -> central.VirtualMachineMetrics + 2, // 1: central.VirtualMachineMetrics.roxagent_version_counts:type_name -> central.VirtualMachineMetrics.RoxagentVersionCountsEntry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_internalapi_central_cluster_metrics_proto_init() } @@ -129,7 +212,7 @@ func file_internalapi_central_cluster_metrics_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_internalapi_central_cluster_metrics_proto_rawDesc), len(file_internalapi_central_cluster_metrics_proto_rawDesc)), NumEnums: 0, - NumMessages: 1, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/generated/internalapi/central/cluster_metrics_vtproto.pb.go b/generated/internalapi/central/cluster_metrics_vtproto.pb.go index 9aaf2dc21c058..26c0472555395 100644 --- a/generated/internalapi/central/cluster_metrics_vtproto.pb.go +++ b/generated/internalapi/central/cluster_metrics_vtproto.pb.go @@ -28,6 +28,7 @@ func (m *ClusterMetrics) CloneVT() *ClusterMetrics { r.NodeCount = m.NodeCount r.CpuCapacity = m.CpuCapacity r.ComplianceOperatorVersion = m.ComplianceOperatorVersion + r.VirtualMachineMetrics = m.VirtualMachineMetrics.CloneVT() if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -39,6 +40,31 @@ func (m *ClusterMetrics) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *VirtualMachineMetrics) CloneVT() *VirtualMachineMetrics { + if m == nil { + return (*VirtualMachineMetrics)(nil) + } + r := new(VirtualMachineMetrics) + r.TrackedVms = m.TrackedVms + r.VmsScanned = m.VmsScanned + if rhs := m.RoxagentVersionCounts; rhs != nil { + tmpContainer := make(map[string]int32, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v + } + r.RoxagentVersionCounts = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *VirtualMachineMetrics) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *ClusterMetrics) EqualVT(that *ClusterMetrics) bool { if this == that { return true @@ -54,6 +80,9 @@ func (this *ClusterMetrics) EqualVT(that *ClusterMetrics) bool { if this.ComplianceOperatorVersion != that.ComplianceOperatorVersion { return false } + if !this.VirtualMachineMetrics.EqualVT(that.VirtualMachineMetrics) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -64,6 +93,40 @@ func (this *ClusterMetrics) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *VirtualMachineMetrics) EqualVT(that *VirtualMachineMetrics) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.TrackedVms != that.TrackedVms { + return false + } + if this.VmsScanned != that.VmsScanned { + return false + } + if len(this.RoxagentVersionCounts) != len(that.RoxagentVersionCounts) { + return false + } + for i, vx := range this.RoxagentVersionCounts { + vy, ok := that.RoxagentVersionCounts[i] + if !ok { + return false + } + if vx != vy { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *VirtualMachineMetrics) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*VirtualMachineMetrics) + if !ok { + return false + } + return this.EqualVT(that) +} func (m *ClusterMetrics) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -94,6 +157,16 @@ func (m *ClusterMetrics) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.VirtualMachineMetrics != nil { + size, err := m.VirtualMachineMetrics.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } if len(m.ComplianceOperatorVersion) > 0 { i -= len(m.ComplianceOperatorVersion) copy(dAtA[i:], m.ComplianceOperatorVersion) @@ -114,6 +187,66 @@ func (m *ClusterMetrics) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *VirtualMachineMetrics) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VirtualMachineMetrics) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *VirtualMachineMetrics) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.RoxagentVersionCounts) > 0 { + for k := range m.RoxagentVersionCounts { + v := m.RoxagentVersionCounts[k] + baseI := i + i = protohelpers.EncodeVarint(dAtA, i, uint64(v)) + i-- + dAtA[i] = 0x10 + i -= len(k) + copy(dAtA[i:], k) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = protohelpers.EncodeVarint(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x1a + } + } + if m.VmsScanned != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.VmsScanned)) + i-- + dAtA[i] = 0x10 + } + if m.TrackedVms != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TrackedVms)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *ClusterMetrics) SizeVT() (n int) { if m == nil { return 0 @@ -130,6 +263,34 @@ func (m *ClusterMetrics) SizeVT() (n int) { if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if m.VirtualMachineMetrics != nil { + l = m.VirtualMachineMetrics.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *VirtualMachineMetrics) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TrackedVms != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.TrackedVms)) + } + if m.VmsScanned != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.VmsScanned)) + } + if len(m.RoxagentVersionCounts) > 0 { + for k, v := range m.RoxagentVersionCounts { + _ = k + _ = v + mapEntrySize := 1 + len(k) + protohelpers.SizeOfVarint(uint64(len(k))) + 1 + protohelpers.SizeOfVarint(uint64(v)) + n += mapEntrySize + 1 + protohelpers.SizeOfVarint(uint64(mapEntrySize)) + } + } n += len(m.unknownFields) return n } @@ -233,6 +394,244 @@ func (m *ClusterMetrics) UnmarshalVT(dAtA []byte) error { } m.ComplianceOperatorVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VirtualMachineMetrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VirtualMachineMetrics == nil { + m.VirtualMachineMetrics = &VirtualMachineMetrics{} + } + if err := m.VirtualMachineMetrics.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VirtualMachineMetrics) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VirtualMachineMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VirtualMachineMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TrackedVms", wireType) + } + m.TrackedVms = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TrackedVms |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VmsScanned", wireType) + } + m.VmsScanned = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VmsScanned |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoxagentVersionCounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RoxagentVersionCounts == nil { + m.RoxagentVersionCounts = make(map[string]int32) + } + var mapkey string + var mapvalue int32 + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protohelpers.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protohelpers.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapvalue |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + } else { + iNdEx = entryPreIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.RoxagentVersionCounts[mapkey] = mapvalue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -358,6 +757,248 @@ func (m *ClusterMetrics) UnmarshalVTUnsafe(dAtA []byte) error { } m.ComplianceOperatorVersion = stringValue iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VirtualMachineMetrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VirtualMachineMetrics == nil { + m.VirtualMachineMetrics = &VirtualMachineMetrics{} + } + if err := m.VirtualMachineMetrics.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VirtualMachineMetrics) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VirtualMachineMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VirtualMachineMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TrackedVms", wireType) + } + m.TrackedVms = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TrackedVms |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VmsScanned", wireType) + } + m.VmsScanned = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VmsScanned |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoxagentVersionCounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RoxagentVersionCounts == nil { + m.RoxagentVersionCounts = make(map[string]int32) + } + var mapkey string + var mapvalue int32 + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protohelpers.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protohelpers.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + if intStringLenmapkey == 0 { + mapkey = "" + } else { + mapkey = unsafe.String(&dAtA[iNdEx], intStringLenmapkey) + } + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapvalue |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + } else { + iNdEx = entryPreIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.RoxagentVersionCounts[mapkey] = mapvalue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pkg/centralsensor/caps_list.go b/pkg/centralsensor/caps_list.go index 26f34c65e40f8..6a90ecbaa50b8 100644 --- a/pkg/centralsensor/caps_list.go +++ b/pkg/centralsensor/caps_list.go @@ -105,4 +105,8 @@ const ( // InitContainerSupport identifies the capability of Sensor to extract init containers from pod specs // and of Central to handle init container data in deployments. InitContainerSupport = "InitContainerSupport" + + // VirtualMachineTelemetryCap lets Central distinguish "VM scanning off on + // a new Sensor" from "old Sensor that predates VM telemetry fields." + VirtualMachineTelemetryCap SensorCapability = "VirtualMachineTelemetry" ) diff --git a/proto/internalapi/central/cluster_metrics.proto b/proto/internalapi/central/cluster_metrics.proto index a555dbe7eb3d4..6f42e586c599a 100644 --- a/proto/internalapi/central/cluster_metrics.proto +++ b/proto/internalapi/central/cluster_metrics.proto @@ -10,4 +10,13 @@ message ClusterMetrics { int64 node_count = 1; // The number of nodes in the cluster accessible by Sensor. int64 cpu_capacity = 2; // The total cpu capacity of all nodes accessible by Sensor. string compliance_operator_version = 3; // Compliance operator version discovered by this Sensor. + + // Filled only when ROX_VIRTUAL_MACHINES is enabled on this Sensor. + VirtualMachineMetrics virtual_machine_metrics = 4; +} + +message VirtualMachineMetrics { + int32 tracked_vms = 1; // VMs currently tracked for scraping. + int32 vms_scanned = 2; // Tracked VMs with at least one successful scrape ever. + map roxagent_version_counts = 3; // Version (or "unknown"/"other") -> count of scanned VMs. Unscanned VMs are omitted. } diff --git a/sensor/common/sensor/central_communication_impl.go b/sensor/common/sensor/central_communication_impl.go index 532fa07ce434b..0a60dc70c884d 100644 --- a/sensor/common/sensor/central_communication_impl.go +++ b/sensor/common/sensor/central_communication_impl.go @@ -155,6 +155,7 @@ func (s *centralCommunicationImpl) sendEvents(client central.SensorServiceClient capsSet.AddAll(component.Capabilities()...) } capsSet.Add(centralsensor.SendDeduperStateOnReconnect) + capsSet.Add(centralsensor.VirtualMachineTelemetryCap) if features.FlattenImageData.Enabled() { capsSet.Add(centralsensor.FlattenImageData) } diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index a7703d4c26f12..5e912fe03cd2e 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -117,11 +117,12 @@ type VMScraper struct { } type vmState struct { - lastToken string - lastForwardedAt time.Time - nextAttemptAt time.Time - backoff time.Duration - vmID virtualmachine.VMID + lastToken string + lastForwardedAt time.Time + nextAttemptAt time.Time + backoff time.Duration + vmID virtualmachine.VMID + lastAgentVersion string } var _ common.SensorComponent = (*VMScraper)(nil) @@ -346,8 +347,11 @@ func (s *VMScraper) reconcile() { if !ok { st = &vmState{nextAttemptAt: now.Add(randOffset(newVMWindow, s.randFloat64())), vmID: vm.ID} s.vmState[key] = st + } else if st.vmID != vm.ID { + // namespace/name can outlive a KubeVirt recreate; do not inherit scrape state. + st = &vmState{nextAttemptAt: now, vmID: vm.ID} + s.vmState[key] = st } - st.vmID = vm.ID } for key := range s.vmState { if !liveKeys.Contains(key) { @@ -414,7 +418,9 @@ func (s *VMScraper) scrapeKey(ctx context.Context, key string) bool { vm := s.store.Get(vmID) if vm == nil || !vm.Running { concurrency.WithLock(&s.mu, func() { - delete(s.vmState, key) + if st, ok := s.vmState[key]; ok && st.vmID == vmID { + delete(s.vmState, key) + } }) return false } @@ -448,7 +454,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool log.Debugf("VMScraper: dialing roxagent on %q with TLS", key) result, outcome := s.dialAndGetReport(vmCtx, vm, key, port, lastKnownToken) if outcome != scrapeOK { - next := s.scheduleAfterAttempt(key, outcome) + next := s.scheduleAfterAttempt(key, vm.ID, outcome) kind := "retryable" if outcome == scrapeNonRetryable { kind = "non-retryable" @@ -458,7 +464,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } if result.Unchanged { - next := s.scheduleAfterAttempt(key, scrapeOK) + next := s.scheduleAfterAttempt(key, vm.ID, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=unchanged next=%s", key, next) log.Debugf("VMScraper: unchanged report from roxagent on %q (token=%s)", key, snap.lastToken) metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportUnchanged).Inc() @@ -471,7 +477,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } if !viable { metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeInvalidReport).Inc() - next := s.scheduleAfterAttempt(key, scrapeNonRetryable) + next := s.scheduleAfterAttempt(key, vm.ID, scrapeNonRetryable) log.Infof("VMScraper: scrape %q failed non-retryable next=%s", key, next) return false } @@ -493,7 +499,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } // Send failures are typically a transient Central connection issue, so // retry on the short backoff rather than waiting a full poll interval. - next := s.scheduleAfterAttempt(key, outcome) + next := s.scheduleAfterAttempt(key, vm.ID, outcome) kind := "retryable" if outcome == scrapeNonRetryable { kind = "non-retryable" @@ -503,9 +509,9 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } newToken := result.Meta.GetReportToken() - s.commitVMState(key, newToken) + s.commitVMState(key, vm.ID, newToken, result.Meta.GetAgentVersion()) s.observeForwardInterarrival() - next := s.scheduleAfterAttempt(key, scrapeOK) + next := s.scheduleAfterAttempt(key, vm.ID, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=forwarded next=%s", key, next) totalElapsed := s.now().Sub(totalStart) @@ -537,13 +543,14 @@ const ( scrapeNonRetryable ) -// scheduleAfterAttempt sets when this VM may be tried again and returns that -// delay. Retries use short exponential backoff; success / permanent failure -// return to poll cadence with a random offset in [0, steadyWidth]. -func (s *VMScraper) scheduleAfterAttempt(key string, outcome scrapeOutcome) time.Duration { +// scheduleAfterAttempt updates nextAttemptAt/backoff for key and returns the +// delay until the next attempt (0 if the slot was dropped or belongs to another VM). +// Retries use short exponential backoff; success / permanent failure return to +// poll cadence with a random offset in [0, steadyWidth]. +func (s *VMScraper) scheduleAfterAttempt(key string, vmID virtualmachine.VMID, outcome scrapeOutcome) time.Duration { return concurrency.WithLock1(&s.mu, func() time.Duration { st, ok := s.vmState[key] - if !ok { + if !ok || st.vmID != vmID { return 0 } now := s.now() @@ -706,13 +713,14 @@ func (s *VMScraper) snapshotVMState(key string) vmStateSnapshot { // then the NACK will overwrite lastToken / backoff / nextAttemptAt, and then this code // will set the token (and a later scheduleAfterAttempt(scrapeOK) will reset backoff schedule). // This race is accepted for v1. -func (s *VMScraper) commitVMState(key string, newToken string) { +func (s *VMScraper) commitVMState(key string, vmID virtualmachine.VMID, newToken string, agentVersion string) { concurrency.WithLock(&s.mu, func() { state, ok := s.vmState[key] - if !ok { + if !ok || state.vmID != vmID { return } state.lastToken = newToken state.lastForwardedAt = s.now() + state.lastAgentVersion = agentVersion }) } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 5c2fa24e4a798..06b8c855f8766 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -724,32 +724,131 @@ func TestVMScraper_GetReportBusyClassified(t *testing.T) { "a busy response must not be counted as a generic protocol/read error") } -func TestVMScraper_PrunesStaleState(t *testing.T) { - store := &mockStore{vms: []*virtualmachine.Info{ - makeVM("ns1", "vm-a", 100), - makeVM("ns2", "vm-b", 200), - }} - sender := &mockSender{} - dialer := &mockDialer{} - client := &mockProtocolClient{ - resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, - } +func TestVMScraper_ReconcileVMState(t *testing.T) { + t.Run("should prune keys no longer in the running set", func(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + makeVM("ns2", "vm-b", 200), + }} + sender := &mockSender{} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, + } - s, clock := newTestScraper(t, store, sender, dialer, client) - s.pollOnce(context.Background()) - assert.Len(t, s.vmState, 2) - assert.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) + s, clock := newTestScraper(t, store, sender, dialer, client) + s.pollOnce(context.Background()) + assert.Len(t, s.vmState, 2) + assert.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) - // Remove vm-a from running set - store.vms = []*virtualmachine.Info{makeVM("ns2", "vm-b", 200)} - client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} - clock.Advance(s.interval) - s.pollOnce(context.Background()) + store.vms = []*virtualmachine.Info{makeVM("ns2", "vm-b", 200)} + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + + assert.Len(t, s.vmState, 1, "stale vm-a state should be pruned") + assert.False(t, hasScheduleSlot(t, s, "ns1/vm-a"), "vm-a should no longer be scheduled") + assert.True(t, hasScheduleSlot(t, s, "ns2/vm-b")) + }) + + t.Run("should reset scrape state when VM ID changes at the same key", func(t *testing.T) { + const key = "ns1/vm-a" + oldVM := &virtualmachine.Info{ + ID: "uid-old", + Namespace: "ns1", + Name: "vm-a", + VSOCKCID: new(uint32(100)), + Running: true, + } + store := &mockStore{vms: []*virtualmachine.Info{oldVM}} + s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + }) - assert.Len(t, s.vmState, 1, "stale vm-a state should be pruned") - assert.False(t, hasScheduleSlot(t, s, "ns1/vm-a"), "vm-a should no longer be scheduled") - assert.True(t, hasScheduleSlot(t, s, "ns2/vm-b")) + s.pollOnce(context.Background()) + require.Equal(t, "1", cachedToken(t, s, key)) + require.Equal(t, 1, s.Stats().VMsScanned) + require.Equal(t, clock.Now().Add(s.interval), cachedNextAttemptAt(t, s, key), + "successful scrape schedules the next poll") + + store.vms = []*virtualmachine.Info{{ + ID: "uid-new", + Namespace: "ns1", + Name: "vm-a", + VSOCKCID: new(uint32(101)), + Running: true, + }} + s.reconcile() + + require.Equal(t, virtualmachine.VMID("uid-new"), concurrency.WithLock1(&s.mu, func() virtualmachine.VMID { + return s.vmState[key].vmID + })) + assert.Empty(t, cachedToken(t, s, key)) + assert.Equal(t, 1, s.Stats().TrackedVMs) + assert.Zero(t, s.Stats().VMsScanned, "replacement must not inherit prior scrape state") + assert.Equal(t, clock.Now(), cachedNextAttemptAt(t, s, key), + "replacement must be due immediately, not on the prior poll timer") + assert.Contains(t, s.dueKeys(), key) + }) +} + +// TestVMScraper_IgnoresLateScrapeAfterVMReplacement covers a KubeVirt recreate +// while a scrape of the predecessor is still in Send: the late commit must not +// mark the replacement as already scanned or push its next poll out by interval. +func TestVMScraper_IgnoresLateScrapeAfterVMReplacement(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + const key = "ns1/vm-a" + oldVM := &virtualmachine.Info{ + ID: "uid-old", + Namespace: "ns1", + Name: "vm-a", + VSOCKCID: new(uint32(100)), + Running: true, + } + store := &mockStore{vms: []*virtualmachine.Info{oldVM}} + gate := &gateSender{ + blockAt: 1, + release: make(chan struct{}), + } + s, clock := newTestScraper(t, store, gate, &mockDialer{}, &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("99")}, + }) + + done := make(chan struct{}) + go func() { + defer close(done) + s.pollOnce(t.Context()) + }() + synctest.Wait() + + store.vms = []*virtualmachine.Info{{ + ID: "uid-new", + Namespace: "ns1", + Name: "vm-a", + VSOCKCID: new(uint32(101)), + Running: true, + }} + s.reconcile() + + require.Equal(t, virtualmachine.VMID("uid-new"), concurrency.WithLock1(&s.mu, func() virtualmachine.VMID { + return s.vmState[key].vmID + })) + require.Zero(t, s.Stats().VMsScanned) + require.Equal(t, clock.Now(), cachedNextAttemptAt(t, s, key)) + + close(gate.release) + <-done + + assert.Equal(t, virtualmachine.VMID("uid-new"), concurrency.WithLock1(&s.mu, func() virtualmachine.VMID { + return s.vmState[key].vmID + })) + assert.Empty(t, cachedToken(t, s, key), "late commit must not stamp the replacement") + assert.Zero(t, s.Stats().VMsScanned, "replacement must stay unscanned until its own scrape") + assert.Equal(t, clock.Now(), cachedNextAttemptAt(t, s, key), + "late schedule must not inherit the predecessor's poll timer") + assert.Contains(t, s.dueKeys(), key) + }) } // hasScheduleSlot reports whether key has a vmState slot (reconcile membership). diff --git a/sensor/common/virtualmachine/vmscraper/stats.go b/sensor/common/virtualmachine/vmscraper/stats.go new file mode 100644 index 0000000000000..87e3609b08930 --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/stats.go @@ -0,0 +1,81 @@ +package vmscraper + +import ( + "slices" + "strings" +) + +const ( + maxVersionBuckets = 20 + unknownAgentVersion = "unknown" + otherAgentVersion = "other" +) + +// Stats is a point-in-time snapshot of the scraper's fleet state. +type Stats struct { + TrackedVMs int + VMsScanned int + VersionCounts map[string]int // scanned VMs only; empty AgentVersion is "unknown" +} + +// Stats returns a point-in-time snapshot of fleet statistics. +// VersionCounts omits never-scraped VMs: those have no agent version to report. +func (s *VMScraper) Stats() Stats { + s.mu.Lock() + defer s.mu.Unlock() + return computeStats(s.vmState) +} + +func computeStats(vmState map[string]*vmState) Stats { + st := Stats{ + TrackedVMs: len(vmState), + VersionCounts: make(map[string]int, min(len(vmState), maxVersionBuckets+1)), + } + + for _, vs := range vmState { + if vs.lastForwardedAt.IsZero() { + continue + } + st.VMsScanned++ + ver := vs.lastAgentVersion + if ver == "" { + ver = unknownAgentVersion + } + st.VersionCounts[ver]++ + } + + capVersionCounts(st.VersionCounts) + return st +} + +// capVersionCounts keeps only the top-N versions by count, folding the rest +// into an "other" bucket so the map size is bounded regardless of fleet size. +func capVersionCounts(counts map[string]int) { + if len(counts) <= maxVersionBuckets { + return + } + + type entry struct { + ver string + count int + } + entries := make([]entry, 0, len(counts)) + for v, c := range counts { + entries = append(entries, entry{v, c}) + } + slices.SortFunc(entries, func(a, b entry) int { + if a.count != b.count { + return b.count - a.count // descending by count + } + return strings.Compare(a.ver, b.ver) // stable tiebreak + }) + + otherTotal := 0 + for _, e := range entries[maxVersionBuckets:] { + otherTotal += e.count + delete(counts, e.ver) + } + if otherTotal > 0 { + counts[otherAgentVersion] += otherTotal + } +} diff --git a/sensor/common/virtualmachine/vmscraper/stats_test.go b/sensor/common/virtualmachine/vmscraper/stats_test.go new file mode 100644 index 0000000000000..b89818699eabe --- /dev/null +++ b/sensor/common/virtualmachine/vmscraper/stats_test.go @@ -0,0 +1,108 @@ +package vmscraper + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + testAgentVersion470 = "4.7.0-12-gabcdef0123" // typical ldflags / git describe build + testAgentVersion480 = "4.8.0-3-gfedcba9876" + testAgentDevelopment = "development" // local default before ldflags injection +) + +// testGitDescribeBuild returns git-describe-shaped version strings; commitNum is +// zero-padded so lexical order matches numeric order in the cap tie-break test. +func testGitDescribeBuild(commitNum int) string { + return fmt.Sprintf("4.7.0-%02d-g01234567%02d", commitNum, commitNum) +} + +func TestVMScraper_Stats(t *testing.T) { + tests := map[string]struct { + setup func(s *VMScraper) + wantTracked int + wantScanned int + wantVersions map[string]int + }{ + "should return zeros when no VMs are tracked": { + setup: func(_ *VMScraper) {}, + wantTracked: 0, + wantScanned: 0, + wantVersions: map[string]int{}, + }, + "should count tracked and scanned VMs correctly": { + setup: func(s *VMScraper) { + now := s.now() + s.vmState["ns1/vm-a"] = &vmState{ + lastForwardedAt: now, + lastAgentVersion: testAgentVersion470, + } + s.vmState["ns1/vm-b"] = &vmState{ + lastAgentVersion: testAgentVersion470, + } + s.vmState["ns2/vm-c"] = &vmState{ + lastForwardedAt: now, + lastAgentVersion: testAgentVersion480, + } + }, + wantTracked: 3, + wantScanned: 2, + wantVersions: map[string]int{ + testAgentVersion470: 1, + testAgentVersion480: 1, + }, + }, + "should bucket empty agent version of a scanned VM as unknown": { + setup: func(s *VMScraper) { + s.vmState["ns1/vm-a"] = &vmState{ + lastForwardedAt: s.now(), + } + s.vmState["ns1/vm-b"] = &vmState{ + lastAgentVersion: testAgentDevelopment, + lastForwardedAt: s.now(), + } + }, + wantTracked: 2, + wantScanned: 2, + wantVersions: map[string]int{ + unknownAgentVersion: 1, + testAgentDevelopment: 1, + }, + }, + "should cap version map to top N and fold remainder into other": { + setup: func(s *VMScraper) { + for i := range maxVersionBuckets + 5 { + s.vmState[fmt.Sprintf("ns/vm-%d", i)] = &vmState{ + lastAgentVersion: testGitDescribeBuild(i), + lastForwardedAt: s.now(), + } + } + }, + wantTracked: maxVersionBuckets + 5, + wantScanned: maxVersionBuckets + 5, + wantVersions: func() map[string]int { + m := make(map[string]int, maxVersionBuckets+1) + for i := range maxVersionBuckets { + m[testGitDescribeBuild(i)] = 1 + } + m[otherAgentVersion] = 5 + return m + }(), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + tc.setup(s) + stats := s.Stats() + + assert.Equal(t, tc.wantTracked, stats.TrackedVMs) + assert.Equal(t, tc.wantScanned, stats.VMsScanned) + + assert.Equal(t, tc.wantVersions, stats.VersionCounts) + }) + } +} diff --git a/sensor/kubernetes/clustermetrics/cluster_metrics.go b/sensor/kubernetes/clustermetrics/cluster_metrics.go index 5ac9ad01b2443..3954955f7a1c7 100644 --- a/sensor/kubernetes/clustermetrics/cluster_metrics.go +++ b/sensor/kubernetes/clustermetrics/cluster_metrics.go @@ -10,10 +10,12 @@ import ( "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/logging" + "github.com/stackrox/rox/pkg/reflectutils" "github.com/stackrox/rox/sensor/common" "github.com/stackrox/rox/sensor/common/message" metricsPkg "github.com/stackrox/rox/sensor/common/metrics" "github.com/stackrox/rox/sensor/common/unimplemented" + "github.com/stackrox/rox/sensor/common/virtualmachine/vmscraper" "github.com/stackrox/rox/sensor/kubernetes/complianceoperator" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -41,13 +43,14 @@ type ClusterMetrics interface { common.SensorComponent } -// New returns a new cluster metrics Sensor component. -func New(clusterID clusterIDPeeker, k8sClient kubernetes.Interface) ClusterMetrics { - return NewWithInterval(clusterID, k8sClient, defaultInterval) +// New returns a cluster metrics component using defaultInterval. +// A nil vmStats omits VirtualMachineMetrics from each snapshot. +func New(clusterID clusterIDPeeker, k8sClient kubernetes.Interface, vmStats VMStatsSource) ClusterMetrics { + return NewWithInterval(clusterID, k8sClient, defaultInterval, vmStats) } // NewWithInterval returns a new cluster metrics Sensor component. -func NewWithInterval(clusterID clusterIDPeeker, k8sClient kubernetes.Interface, pollInterval time.Duration) ClusterMetrics { +func NewWithInterval(clusterID clusterIDPeeker, k8sClient kubernetes.Interface, pollInterval time.Duration, vmStats VMStatsSource) ClusterMetrics { ticker := time.NewTicker(pollInterval) ticker.Stop() return &clusterMetricsImpl{ @@ -58,6 +61,7 @@ func NewWithInterval(clusterID clusterIDPeeker, k8sClient kubernetes.Interface, k8sClient: k8sClient, pollTicker: ticker, clusterID: clusterID, + vmStats: vmStats, } } @@ -65,6 +69,11 @@ type clusterIDPeeker interface { GetNoWait() string } +// VMStatsSource provides a snapshot of VM scraper statistics for telemetry. +type VMStatsSource interface { + Stats() vmscraper.Stats +} + type clusterMetricsImpl struct { unimplemented.Receiver @@ -78,6 +87,7 @@ type clusterMetricsImpl struct { pollTicker *time.Ticker clusterID clusterIDPeeker + vmStats VMStatsSource } func (cm *clusterMetricsImpl) Name() string { @@ -174,5 +184,26 @@ func (cm *clusterMetricsImpl) collectMetrics() (*central.ClusterMetrics, error) coVersion = complianceOperUnavailable } } - return ¢ral.ClusterMetrics{NodeCount: nodeCount, CpuCapacity: capacity, ComplianceOperatorVersion: coVersion}, nil + result := ¢ral.ClusterMetrics{NodeCount: nodeCount, CpuCapacity: capacity, ComplianceOperatorVersion: coVersion} + if stats, ok := vmStatsSnapshot(cm.vmStats); ok { + versionCounts := make(map[string]int32, len(stats.VersionCounts)) + for v, c := range stats.VersionCounts { + versionCounts[v] = int32(c) + } + result.VirtualMachineMetrics = ¢ral.VirtualMachineMetrics{ + TrackedVms: int32(stats.TrackedVMs), + VmsScanned: int32(stats.VMsScanned), + RoxagentVersionCounts: versionCounts, + } + } + return result, nil +} + +// vmStatsSnapshot returns fleet stats when src is a usable VMStatsSource. +// A nil interface or typed-nil pointer stored in it is treated as absent. +func vmStatsSnapshot(src VMStatsSource) (vmscraper.Stats, bool) { + if reflectutils.IsNil(src) { + return vmscraper.Stats{}, false + } + return src.Stats(), true } diff --git a/sensor/kubernetes/clustermetrics/cluster_metrics_test.go b/sensor/kubernetes/clustermetrics/cluster_metrics_test.go index fd5c29f3a95f3..f6f52a5508264 100644 --- a/sensor/kubernetes/clustermetrics/cluster_metrics_test.go +++ b/sensor/kubernetes/clustermetrics/cluster_metrics_test.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/pkg/protoassert" "github.com/stackrox/rox/sensor/common" + "github.com/stackrox/rox/sensor/common/virtualmachine/vmscraper" "github.com/stretchr/testify/suite" coreV1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -19,6 +20,8 @@ const ( // Must be larger than defaultInterval. You may want to increase it if you plan // to step through the code with a debugger. metricsTimeout = 300 * time.Millisecond + + testAgentVersion470 = "4.7.0-12-gabcdef0123" ) func TestClusterMetrics(t *testing.T) { @@ -96,7 +99,7 @@ func (f *fakeClusterIDPeeker) GetNoWait() string { } func (s *ClusterMetricsTestSuite) createNewClusterMetrics(interval time.Duration) *clusterMetricsImpl { - metricsComponent := NewWithInterval(&fakeClusterIDPeeker{}, s.client, interval) + metricsComponent := NewWithInterval(&fakeClusterIDPeeker{}, s.client, interval, nil) metrics, ok := metricsComponent.(*clusterMetricsImpl) s.Require().True(ok, "New should return a struct of type *clusterMetricsImpl") return metrics @@ -123,7 +126,7 @@ func (s *ClusterMetricsTestSuite) assertOfflineMode(state common.SensorComponent func (s *ClusterMetricsTestSuite) getClusterMetrics() *central.ClusterMetrics { timer := time.NewTimer(metricsTimeout) - clusterMetricsStream := New(&fakeClusterIDPeeker{}, s.client) + clusterMetricsStream := New(&fakeClusterIDPeeker{}, s.client, nil) clusterMetricsStream.Notify(common.SensorComponentEventCentralReachable) err := clusterMetricsStream.Start() @@ -151,3 +154,61 @@ func (s *ClusterMetricsTestSuite) addNode(name coreV1.ResourceName, cpu resource }, metaV1.CreateOptions{}) s.Require().NoError(err) } + +type fakeVMStatsSource struct { + stats vmscraper.Stats +} + +func (f *fakeVMStatsSource) Stats() vmscraper.Stats { + return f.stats +} + +func (s *ClusterMetricsTestSuite) TestNilVMStatsSource() { + metrics := NewWithInterval(&fakeClusterIDPeeker{}, s.client, defaultInterval, nil) + impl, ok := metrics.(*clusterMetricsImpl) + s.Require().True(ok) + + cm, err := impl.collectMetrics() + s.Require().NoError(err) + s.Nil(cm.GetVirtualMachineMetrics(), + "nil vmStatsSource must produce nil virtual_machine_metrics, not a zero-valued one") +} + +func (s *ClusterMetricsTestSuite) TestTypedNilVMStatsSource() { + metrics := NewWithInterval(&fakeClusterIDPeeker{}, s.client, defaultInterval, (*vmscraper.VMScraper)(nil)) + impl, ok := metrics.(*clusterMetricsImpl) + s.Require().True(ok) + + cm, err := impl.collectMetrics() + s.Require().NoError(err) + s.Nil(cm.GetVirtualMachineMetrics(), + "typed-nil *VMScraper must not panic and must omit virtual_machine_metrics") +} + +func (s *ClusterMetricsTestSuite) TestVMMetricsEndToEnd() { + src := &fakeVMStatsSource{ + stats: vmscraper.Stats{ + TrackedVMs: 5, + VMsScanned: 3, + VersionCounts: map[string]int{ + testAgentVersion470: 2, + "unknown": 1, + }, + }, + } + cm := NewWithInterval(&fakeClusterIDPeeker{}, s.client, 10*time.Millisecond, src) + s.Require().NoError(cm.Start()) + defer cm.Stop() + cm.Notify(common.SensorComponentEventCentralReachable) + + select { + case msg := <-cm.ResponsesC(): + vmm := msg.GetClusterMetrics().GetVirtualMachineMetrics() + s.Require().NotNil(vmm, "VirtualMachineMetrics must be present when vmStatsSource is non-nil") + s.Equal(int32(5), vmm.GetTrackedVms()) + s.Equal(int32(3), vmm.GetVmsScanned()) + s.Equal(map[string]int32{testAgentVersion470: 2, "unknown": 1}, vmm.GetRoxagentVersionCounts()) + case <-time.After(metricsTimeout): + s.Fail("timeout waiting for cluster metrics with VM data") + } +} diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index 006543e04fbbf..443803a93df1c 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -178,6 +178,24 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { networkFlowManager := manager.NewManager(storeProvider.Entities(), externalsrcs.StoreInstance(), policyDetector, pubSub, internalMessageDispatcher, updatecomputer.New(), manager.WithEnrichTicker(cfg.networkFlowTicker)) enhancer := deploymentenhancer.CreateEnhancer(storeProvider) + + var virtualMachineHandler vmIndex.Handler + var vmScraper *vmscraper.VMScraper + var vmStats clustermetrics.VMStatsSource + if features.VirtualMachines.Enabled() { + virtualMachineHandler = vmIndex.NewHandler(storeProvider.VirtualMachines()) + + pullMaxBytes := int64(env.VirtualMachinesPullMaxResponseSizeKB.IntegerSetting()) * 1024 + vmDial, err := vsockdialer.NewMultiDialer() + if err != nil { + log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) + } else { + vmProtoClient := vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) + vmScraper = vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient) + vmStats = vmScraper + } + } + components := []common.SensorComponent{ admCtrlMsgForwarder, enforcer, @@ -185,7 +203,7 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { networkpolicies.NewCommandHandler(cfg.k8sClient.Kubernetes()), clusterstatus.NewUpdater(cfg.k8sClient), clusterhealth.NewUpdater(cfg.k8sClient.Kubernetes(), 0), - clustermetrics.New(clusterID, cfg.k8sClient.Kubernetes()), + clustermetrics.New(clusterID, cfg.k8sClient.Kubernetes(), vmStats), complianceCommandHandler, processSignals, telemetry.NewCommandHandler(cfg.k8sClient.Kubernetes(), storeProvider), @@ -198,21 +216,11 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { enhancer, complianceService, } - - var virtualMachineHandler vmIndex.Handler - if features.VirtualMachines.Enabled() { - virtualMachineHandler = vmIndex.NewHandler(storeProvider.VirtualMachines()) + if virtualMachineHandler != nil { components = append(components, virtualMachineHandler) - - pullMaxBytes := int64(env.VirtualMachinesPullMaxResponseSizeKB.IntegerSetting()) * 1024 - vmDial, err := vsockdialer.NewMultiDialer() - if err != nil { - log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) - } else { - vmProtoClient := vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) - scraper := vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient) - components = append(components, scraper) - } + } + if vmScraper != nil { + components = append(components, vmScraper) } matcher := compliance.NewNodeIDMatcher(storeProvider.Nodes()) From 55812be5f99cb1f1d3a7e711b953c04008096be6 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 27 Aug 2026 08:04:31 -0400 Subject: [PATCH 114/638] ROX-35131: Adapt from image to node report steps and views (#22426) --- .../Components/Reports/Step/FiltersQuery.tsx | 39 ++++---- .../Step3/ImageVulnerabilityFiltersStep.tsx | 13 ++- .../View/NodeVulnerabilityFiltersView.tsx | 58 ++++++++++++ .../View/NodeVulnerabilityReportView.tsx | 59 ++++++++++++ .../View/NodeVulnerabilityResourcesView.tsx | 42 +++++++++ .../Step2/NodeVulnerabilityResourcesStep.tsx | 93 +++++++++++++++++++ .../Step3/NodeVulnerabilityFiltersStep.tsx | 58 ++++++++++++ .../Step5/NodeVulnerabilityReviewStep.tsx | 52 +++++++++++ ...VirtualMachineVulnerabilityFiltersView.tsx | 58 ++++++++++++ .../VirtualMachineVulnerabilityReportView.tsx | 59 ++++++++++++ ...rtualMachineVulnerabilityResourcesView.tsx | 42 +++++++++ ...rtualMachineVulnerabilityResourcesStep.tsx | 93 +++++++++++++++++++ ...VirtualMachineVulnerabilityFiltersStep.tsx | 58 ++++++++++++ .../VirtualMachineVulnerabilityReviewStep.tsx | 52 +++++++++++ .../src/services/ReportsService.types.ts | 58 ++++++++++-- 15 files changed, 802 insertions(+), 32 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityFiltersView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityResourcesView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step2/NodeVulnerabilityResourcesStep.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step3/NodeVulnerabilityFiltersStep.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step5/NodeVulnerabilityReviewStep.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityFiltersView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityReportView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityResourcesView.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step2/VirtualMachineVulnerabilityResourcesStep.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step3/VirtualMachineVulnerabilityFiltersStep.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step5/VirtualMachineVulnerabilityReviewStep.tsx diff --git a/ui/apps/platform/src/Components/Reports/Step/FiltersQuery.tsx b/ui/apps/platform/src/Components/Reports/Step/FiltersQuery.tsx index d3923721d07c5..0c989a0528ce5 100644 --- a/ui/apps/platform/src/Components/Reports/Step/FiltersQuery.tsx +++ b/ui/apps/platform/src/Components/Reports/Step/FiltersQuery.tsx @@ -6,8 +6,6 @@ import { HelperText, HelperTextItem, } from '@patternfly/react-core'; -import { getIn } from 'formik'; -import type { FormikProps } from 'formik'; import CompoundSearchFilter from 'Components/CompoundSearchFilter/components/CompoundSearchFilter'; import CompoundSearchFilterLabels from 'Components/CompoundSearchFilter/components/CompoundSearchFilterLabels'; @@ -25,28 +23,29 @@ import { getSearchFilterFromSearchString, } from 'utils/searchUtils'; -export type FiltersQueryConfiguration = { - vulnReportFilters: { - query: string; - }; -}; - -export type FiltersQueryProps = { +// Because filter property name differs for different report types, +// renderer is responsible to provide values from formik object. +export type FiltersQueryProps = { attributesSeparateFromConfig: SelectSearchFilterAttribute[]; - formik: FormikProps; + error: string | undefined; + query: string; searchFilterConfig: CompoundSearchFilterConfig; + setQueryValue: (value: string, shouldValidate?: boolean) => void; + touched: boolean | undefined; }; -function FiltersQuery({ +function FiltersQuery({ attributesSeparateFromConfig, - formik, + error, + query, searchFilterConfig, -}: FiltersQueryProps): ReactElement { - const searchFilter = getSearchFilterFromSearchString(formik.values.vulnReportFilters.query); + setQueryValue, + touched, +}: FiltersQueryProps): ReactElement { + const searchFilter = getSearchFilterFromSearchString(query); function onFilterChange(searchFilterChanged: SearchFilter) { - formik.setFieldValue( - 'vulnReportFilters.query', + setQueryValue( getRequestQueryStringForSearchFilter(applyRegexSearchModifiers(searchFilterChanged)) ); } @@ -81,13 +80,11 @@ function FiltersQuery ) : ( - getIn(formik.touched, 'vulnReportFilters.query') && - getIn(formik.errors, 'vulnReportFilters.query') && ( + touched && + error && ( - - {getIn(formik.errors, 'vulnReportFilters.query')} - + {error} ) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx index 9bc20c5516647..e1124ae780ef0 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx @@ -1,5 +1,6 @@ import type { ReactElement } from 'react'; import { Flex, Form, PageSection, Title } from '@patternfly/react-core'; +import { getIn } from 'formik'; import type { FormikProps } from 'formik'; import type { @@ -30,6 +31,13 @@ function ImageVulnerabilityFiltersStep< formik, searchFilterConfig, }: ImageVulnerabilityFiltersStepProps): ReactElement { + const queryPath = 'vulnReportFilters.query'; + + function setQueryValue(value: string, shouldValidate?: boolean) { + formik.setFieldValue(queryPath, value, shouldValidate); + } + + // getIn call prevents TypeScript error for optional chaining in errors and touched objects. return ( @@ -38,8 +46,11 @@ function ImageVulnerabilityFiltersStep< diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityFiltersView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityFiltersView.tsx new file mode 100644 index 0000000000000..e879ea52f9e26 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityFiltersView.tsx @@ -0,0 +1,58 @@ +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import CompoundSearchFilterDescriptionListGroups from 'Components/CompoundSearchFilter/components/CompoundSearchFilterDescriptionListGroups'; +import type { + CompoundSearchFilterAttribute, + CompoundSearchFilterConfig, +} from 'Components/CompoundSearchFilter/types'; +import { getSearchFilterFromSearchString } from 'utils/searchUtils'; + +import type { NodeVulnerabilityReportFiltersConfiguration } from 'services/ReportsService.types'; + +export type NodeVulnerabilityFiltersViewProps = { + attributesSeparateFromConfig: CompoundSearchFilterAttribute[]; + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + searchFilterConfig: CompoundSearchFilterConfig; + values: NodeVulnerabilityReportFiltersConfiguration; +}; + +function NodeVulnerabilityFiltersView({ + attributesSeparateFromConfig, + headingLevel, + horizontalTermWidthModifier, + searchFilterConfig, + values, +}: NodeVulnerabilityFiltersViewProps): ReactElement { + // Render separate attributes (more likely to be specified) preceding config. + const attributesFromConfig = searchFilterConfig.flatMap(({ attributes }) => attributes); + const attributes = [...attributesSeparateFromConfig, ...attributesFromConfig]; + + const { nodeVulnReportFilters } = values; + const { query } = nodeVulnReportFilters; + const searchFilter = getSearchFilterFromSearchString(query); + + return ( + + + Filters + + + + + + + + ); +} + +export default NodeVulnerabilityFiltersView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportView.tsx new file mode 100644 index 0000000000000..70793d3840ad3 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportView.tsx @@ -0,0 +1,59 @@ +import type { ReactElement } from 'react'; +import { Flex } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import type { + CompoundSearchFilterAttribute, + CompoundSearchFilterConfig, +} from 'Components/CompoundSearchFilter/types'; +import DeliveryView from 'Components/Reports/View/DeliveryView'; +import DetailsView from 'Components/Reports/View/DetailsView'; +import type { NodeVulnerabilityReportConfiguration } from 'services/ReportsService.types'; + +import NodeVulnerabilityFiltersView from './NodeVulnerabilityFiltersView'; +import NodeVulnerabilityResourcesView from './NodeVulnerabilityResourcesView'; + +export type NodeVulnerabilityReportViewProps = { + attributesSeparateFromConfig: CompoundSearchFilterAttribute[]; + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + searchFilterConfig: CompoundSearchFilterConfig; + values: NodeVulnerabilityReportConfiguration; +}; + +function NodeVulnerabilityReportView({ + attributesSeparateFromConfig, + headingLevel, + horizontalTermWidthModifier, + searchFilterConfig, + values, +}: NodeVulnerabilityReportViewProps): ReactElement { + return ( + + + + + + + ); +} + +export default NodeVulnerabilityReportView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityResourcesView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityResourcesView.tsx new file mode 100644 index 0000000000000..db170edaddf8c --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityResourcesView.tsx @@ -0,0 +1,42 @@ +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import EntityScopeDescriptionListGroups from 'Components/EntityScope/EntityScopeDescriptionListGroups'; + +import type { NodeVulnerabilityReportResourcesConfiguration } from 'services/ReportsService.types'; + +export type NodeVulnerabilityResourcesViewProps = { + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + values: NodeVulnerabilityReportResourcesConfiguration; +}; + +function NodeVulnerabilityResourcesView({ + headingLevel, + horizontalTermWidthModifier, + values, +}: NodeVulnerabilityResourcesViewProps): ReactElement { + return ( + + + Resources + + + + {'entityScope' in values.resourceScope && ( + + )} + + + + ); +} + +export default NodeVulnerabilityResourcesView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step2/NodeVulnerabilityResourcesStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step2/NodeVulnerabilityResourcesStep.tsx new file mode 100644 index 0000000000000..23dc7270de214 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step2/NodeVulnerabilityResourcesStep.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; +import type { ReactElement } from 'react'; +import { Flex, Form, PageSection, Radio, Title } from '@patternfly/react-core'; +import type { FormikProps } from 'formik'; + +import EntityScopeCompoundSearchFilter from 'Components/EntityScope/EntityScopeCompoundSearchFilter'; +import { + getEntityScopeRulesFromSearchFilterForCluster, + getSearchFilterFromEntityScopeRulesForCluster, +} from 'Components/EntityScope/utils'; +import { searchFilterConfigForCluster } from 'Components/EntityScope/searchFilterConfig'; +import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; +import type { + EntityScope, + NodeVulnerabilityReportResourcesConfiguration, +} from 'services/ReportsService.types'; + +export type NodeVulnerabilityResourcesStepProps< + T extends NodeVulnerabilityReportResourcesConfiguration = + NodeVulnerabilityReportResourcesConfiguration, +> = { + formik: FormikProps; +}; + +function NodeVulnerabilityResourcesStep< + T extends NodeVulnerabilityReportResourcesConfiguration = + NodeVulnerabilityReportResourcesConfiguration, +>({ formik }: NodeVulnerabilityResourcesStepProps): ReactElement { + const [entityScopeSelected, setEntityScopeSelected] = useState( + 'entityScope' in formik.values.resourceScope + ? formik.values.resourceScope.entityScope + : { rules: [] } + ); + + function onChangeToEntityScope() { + // Set resourceScope to remove any other (future) scope property. + formik.setFieldValue('resourceScope', { entityScope: entityScopeSelected }); + } + + function setEntityScope(entityScope: EntityScope) { + setEntityScopeSelected(entityScope); + formik.setFieldValue('resourceScope.entityScope', entityScope); + } + + return ( + + + Resources +
    + + + + } + /> + + +
    +
    +
    + ); +} + +export default NodeVulnerabilityResourcesStep; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step3/NodeVulnerabilityFiltersStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step3/NodeVulnerabilityFiltersStep.tsx new file mode 100644 index 0000000000000..10ea5e546116e --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step3/NodeVulnerabilityFiltersStep.tsx @@ -0,0 +1,58 @@ +import type { ReactElement } from 'react'; +import { Flex, Form, PageSection, Title } from '@patternfly/react-core'; +import { getIn } from 'formik'; +import type { FormikProps } from 'formik'; + +import type { + CompoundSearchFilterConfig, + SelectSearchFilterAttribute, +} from 'Components/CompoundSearchFilter/types'; + +import FiltersQuery from 'Components/Reports/Step/FiltersQuery'; + +import type { NodeVulnerabilityReportFiltersConfiguration } from 'services/ReportsService.types'; + +export type NodeVulnerabilityFiltersStepProps< + T extends NodeVulnerabilityReportFiltersConfiguration = + NodeVulnerabilityReportFiltersConfiguration, +> = { + attributesSeparateFromConfig: SelectSearchFilterAttribute[]; + formik: FormikProps; + searchFilterConfig: CompoundSearchFilterConfig; +}; + +function NodeVulnerabilityFiltersStep< + T extends NodeVulnerabilityReportFiltersConfiguration = + NodeVulnerabilityReportFiltersConfiguration, +>({ + attributesSeparateFromConfig, + formik, + searchFilterConfig, +}: NodeVulnerabilityFiltersStepProps): ReactElement { + const queryPath = 'nodeVulnReportFilters.query'; + + function setQueryValue(value: string, shouldValidate?: boolean) { + formik.setFieldValue(queryPath, value, shouldValidate); + } + + // getIn call prevents TypeScript error for optional chaining in errors and touched objects. + return ( + + + Filters +
    + + +
    +
    + ); +} + +export default NodeVulnerabilityFiltersStep; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step5/NodeVulnerabilityReviewStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step5/NodeVulnerabilityReviewStep.tsx new file mode 100644 index 0000000000000..b404241385039 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/Step5/NodeVulnerabilityReviewStep.tsx @@ -0,0 +1,52 @@ +import type { ReactElement } from 'react'; +import { Alert, Flex, PageSection, Title } from '@patternfly/react-core'; + +import type { + CompoundSearchFilterConfig, + SelectSearchFilterAttribute, +} from 'Components/CompoundSearchFilter/types'; +import type { NodeVulnerabilityReportConfiguration } from 'services/ReportsService.types'; +import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; + +import NodeVulnerabilityReportView from '../../View/NodeVulnerabilityReportView'; + +export type NodeVulnerabilityReviewStepProps = { + attributesSeparateFromConfig: SelectSearchFilterAttribute[]; + error: Error | null; + searchFilterConfig: CompoundSearchFilterConfig; + values: NodeVulnerabilityReportConfiguration; +}; + +function NodeVulnerabilityReviewStep({ + attributesSeparateFromConfig, + error, + searchFilterConfig, + values, +}: NodeVulnerabilityReviewStepProps): ReactElement { + return ( + + + Review + {error && ( + + {getAxiosErrorMessage(error)} + + )} + + + + ); +} + +export default NodeVulnerabilityReviewStep; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityFiltersView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityFiltersView.tsx new file mode 100644 index 0000000000000..a3d3916137231 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityFiltersView.tsx @@ -0,0 +1,58 @@ +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import CompoundSearchFilterDescriptionListGroups from 'Components/CompoundSearchFilter/components/CompoundSearchFilterDescriptionListGroups'; +import type { + CompoundSearchFilterAttribute, + CompoundSearchFilterConfig, +} from 'Components/CompoundSearchFilter/types'; +import { getSearchFilterFromSearchString } from 'utils/searchUtils'; + +import type { VirtualMachineVulnerabilityReportFiltersConfiguration } from 'services/ReportsService.types'; + +export type VirtualMachineVulnerabilityFiltersViewProps = { + attributesSeparateFromConfig: CompoundSearchFilterAttribute[]; + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + searchFilterConfig: CompoundSearchFilterConfig; + values: VirtualMachineVulnerabilityReportFiltersConfiguration; +}; + +function VirtualMachineVulnerabilityFiltersView({ + attributesSeparateFromConfig, + headingLevel, + horizontalTermWidthModifier, + searchFilterConfig, + values, +}: VirtualMachineVulnerabilityFiltersViewProps): ReactElement { + // Render separate attributes (more likely to be specified) preceding config. + const attributesFromConfig = searchFilterConfig.flatMap(({ attributes }) => attributes); + const attributes = [...attributesSeparateFromConfig, ...attributesFromConfig]; + + const { virtualMachineVulnReportFilters } = values; + const { query } = virtualMachineVulnReportFilters; + const searchFilter = getSearchFilterFromSearchString(query); + + return ( + + + Filters + + + + + + + + ); +} + +export default VirtualMachineVulnerabilityFiltersView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityReportView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityReportView.tsx new file mode 100644 index 0000000000000..bc2fd614e57cb --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityReportView.tsx @@ -0,0 +1,59 @@ +import type { ReactElement } from 'react'; +import { Flex } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import type { + CompoundSearchFilterAttribute, + CompoundSearchFilterConfig, +} from 'Components/CompoundSearchFilter/types'; +import DeliveryView from 'Components/Reports/View/DeliveryView'; +import DetailsView from 'Components/Reports/View/DetailsView'; +import type { VirtualMachineVulnerabilityReportConfiguration } from 'services/ReportsService.types'; + +import VirtualMachineVulnerabilityFiltersView from './VirtualMachineVulnerabilityFiltersView'; +import VirtualMachineVulnerabilityResourcesView from './VirtualMachineVulnerabilityResourcesView'; + +export type VirtualMachineVulnerabilityReportViewProps = { + attributesSeparateFromConfig: CompoundSearchFilterAttribute[]; + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + searchFilterConfig: CompoundSearchFilterConfig; + values: VirtualMachineVulnerabilityReportConfiguration; +}; + +function VirtualMachineVulnerabilityReportView({ + attributesSeparateFromConfig, + headingLevel, + horizontalTermWidthModifier, + searchFilterConfig, + values, +}: VirtualMachineVulnerabilityReportViewProps): ReactElement { + return ( + + + + + + + ); +} + +export default VirtualMachineVulnerabilityReportView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityResourcesView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityResourcesView.tsx new file mode 100644 index 0000000000000..41268b84e1324 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/View/VirtualMachineVulnerabilityResourcesView.tsx @@ -0,0 +1,42 @@ +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; +import type { BreakpointModifiers } from '@patternfly/react-core'; + +import EntityScopeDescriptionListGroups from 'Components/EntityScope/EntityScopeDescriptionListGroups'; + +import type { VirtualMachineVulnerabilityReportResourcesConfiguration } from 'services/ReportsService.types'; + +export type VirtualMachineVulnerabilityResourcesViewProps = { + headingLevel: 'h2' | 'h3'; + horizontalTermWidthModifier: BreakpointModifiers; + values: VirtualMachineVulnerabilityReportResourcesConfiguration; +}; + +function VirtualMachineVulnerabilityResourcesView({ + headingLevel, + horizontalTermWidthModifier, + values, +}: VirtualMachineVulnerabilityResourcesViewProps): ReactElement { + return ( + + + Resources + + + + {'entityScope' in values.resourceScope && ( + + )} + + + + ); +} + +export default VirtualMachineVulnerabilityResourcesView; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step2/VirtualMachineVulnerabilityResourcesStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step2/VirtualMachineVulnerabilityResourcesStep.tsx new file mode 100644 index 0000000000000..2b3499feb72c4 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step2/VirtualMachineVulnerabilityResourcesStep.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; +import type { ReactElement } from 'react'; +import { Flex, Form, PageSection, Radio, Title } from '@patternfly/react-core'; +import type { FormikProps } from 'formik'; + +import EntityScopeCompoundSearchFilter from 'Components/EntityScope/EntityScopeCompoundSearchFilter'; +import { + getEntityScopeRulesFromSearchFilterForClusterNamespace, + getSearchFilterFromEntityScopeRulesForClusterNamespace, +} from 'Components/EntityScope/utils'; +import { searchFilterConfigForClusterNamespace } from 'Components/EntityScope/searchFilterConfig'; +import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; +import type { + EntityScope, + VirtualMachineVulnerabilityReportResourcesConfiguration, +} from 'services/ReportsService.types'; + +export type VirtualMachineVulnerabilityResourcesStepProps< + T extends VirtualMachineVulnerabilityReportResourcesConfiguration = + VirtualMachineVulnerabilityReportResourcesConfiguration, +> = { + formik: FormikProps; +}; + +function VirtualMachineVulnerabilityResourcesStep< + T extends VirtualMachineVulnerabilityReportResourcesConfiguration = + VirtualMachineVulnerabilityReportResourcesConfiguration, +>({ formik }: VirtualMachineVulnerabilityResourcesStepProps): ReactElement { + const [entityScopeSelected, setEntityScopeSelected] = useState( + 'entityScope' in formik.values.resourceScope + ? formik.values.resourceScope.entityScope + : { rules: [] } + ); + + function onChangeToEntityScope() { + // Set resourceScope to remove any other (future) scope property. + formik.setFieldValue('resourceScope', { entityScope: entityScopeSelected }); + } + + function setEntityScope(entityScope: EntityScope) { + setEntityScopeSelected(entityScope); + formik.setFieldValue('resourceScope.entityScope', entityScope); + } + + return ( + + + Resources +
    + + + + } + /> + + +
    +
    +
    + ); +} + +export default VirtualMachineVulnerabilityResourcesStep; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step3/VirtualMachineVulnerabilityFiltersStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step3/VirtualMachineVulnerabilityFiltersStep.tsx new file mode 100644 index 0000000000000..63ff404e76c3c --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step3/VirtualMachineVulnerabilityFiltersStep.tsx @@ -0,0 +1,58 @@ +import type { ReactElement } from 'react'; +import { Flex, Form, PageSection, Title } from '@patternfly/react-core'; +import { getIn } from 'formik'; +import type { FormikProps } from 'formik'; + +import type { + CompoundSearchFilterConfig, + SelectSearchFilterAttribute, +} from 'Components/CompoundSearchFilter/types'; + +import FiltersQuery from 'Components/Reports/Step/FiltersQuery'; + +import type { VirtualMachineVulnerabilityReportFiltersConfiguration } from 'services/ReportsService.types'; + +export type VirtualMachineVulnerabilityFiltersStepProps< + T extends VirtualMachineVulnerabilityReportFiltersConfiguration = + VirtualMachineVulnerabilityReportFiltersConfiguration, +> = { + attributesSeparateFromConfig: SelectSearchFilterAttribute[]; + formik: FormikProps; + searchFilterConfig: CompoundSearchFilterConfig; +}; + +function VirtualMachineVulnerabilityFiltersStep< + T extends VirtualMachineVulnerabilityReportFiltersConfiguration = + VirtualMachineVulnerabilityReportFiltersConfiguration, +>({ + attributesSeparateFromConfig, + formik, + searchFilterConfig, +}: VirtualMachineVulnerabilityFiltersStepProps): ReactElement { + const queryPath = 'virtualMachineVulnReportFilters.query'; + + function setQueryValue(value: string, shouldValidate?: boolean) { + formik.setFieldValue(queryPath, value, shouldValidate); + } + + // getIn call prevents TypeScript error for optional chaining in errors and touched objects. + return ( + + + Filters +
    + + +
    +
    + ); +} + +export default VirtualMachineVulnerabilityFiltersStep; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step5/VirtualMachineVulnerabilityReviewStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step5/VirtualMachineVulnerabilityReviewStep.tsx new file mode 100644 index 0000000000000..a7faf25350e96 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/VirtualMachineVulnerabilityReports/Configurations/Wizard/Step5/VirtualMachineVulnerabilityReviewStep.tsx @@ -0,0 +1,52 @@ +import type { ReactElement } from 'react'; +import { Alert, Flex, PageSection, Title } from '@patternfly/react-core'; + +import type { + CompoundSearchFilterConfig, + SelectSearchFilterAttribute, +} from 'Components/CompoundSearchFilter/types'; +import type { VirtualMachineVulnerabilityReportConfiguration } from 'services/ReportsService.types'; +import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; + +import VirtualMachineVulnerabilityReportView from '../../View/VirtualMachineVulnerabilityReportView'; + +export type VirtualMachineVulnerabilityReviewStepProps = { + attributesSeparateFromConfig: SelectSearchFilterAttribute[]; + error: Error | null; + searchFilterConfig: CompoundSearchFilterConfig; + values: VirtualMachineVulnerabilityReportConfiguration; +}; + +function VirtualMachineVulnerabilityReviewStep({ + attributesSeparateFromConfig, + error, + searchFilterConfig, + values, +}: VirtualMachineVulnerabilityReviewStepProps): ReactElement { + return ( + + + Review + {error && ( + + {getAxiosErrorMessage(error)} + + )} + + + + ); +} + +export default VirtualMachineVulnerabilityReviewStep; diff --git a/ui/apps/platform/src/services/ReportsService.types.ts b/ui/apps/platform/src/services/ReportsService.types.ts index e3cb5cce3972e..a825f0d148954 100644 --- a/ui/apps/platform/src/services/ReportsService.types.ts +++ b/ui/apps/platform/src/services/ReportsService.types.ts @@ -13,21 +13,59 @@ export type ReportConfigurationBase = { schedule: ReportSchedule | null; }; -export type NodeVulnerabilityReportFilters = { - allVuln: boolean; - query: string; -}; - // Not exactly 1:1 with the proto, which uses a oneOf that technically allows mismatched // type/filters (e.g. node type with image filters). Backend confirmed that can't happen, so // we pin `type` to its matching filters to simplify the typing. export type NodeVulnerabilityReportConfiguration = { type: 'NODE_VULNERABILITY'; +} & ReportConfigurationBase & + NodeVulnerabilityReportFiltersConfiguration & + NodeVulnerabilityReportResourcesConfiguration; + +// Types of views and steps with only properties that they need to know: +export type NodeVulnerabilityReportFiltersConfiguration = { nodeVulnReportFilters: NodeVulnerabilityReportFilters; - resourceScope: { - entityScope: EntityScope; - }; -} & ReportConfigurationBase; +}; + +export type NodeVulnerabilityReportResourcesConfiguration = { + resourceScope: NodeVulnerabilityReportResourceScope; +}; + +// Types of properties: +export type NodeVulnerabilityReportFilters = { + allVuln: boolean; + query: string; +}; + +export type NodeVulnerabilityReportResourceScope = { + entityScope: EntityScope; // Cluster +}; + +// Draft of future configuration type. +export type VirtualMachineVulnerabilityReportConfiguration = { + type: 'VIRTUAL_MACHINE_VULNERABILITY'; +} & ReportConfigurationBase & + VirtualMachineVulnerabilityReportFiltersConfiguration & + VirtualMachineVulnerabilityReportResourcesConfiguration; + +// Types of views and steps with only properties that they need to know: +export type VirtualMachineVulnerabilityReportFiltersConfiguration = { + virtualMachineVulnReportFilters: VirtualMachineVulnerabilityReportFilters; +}; + +export type VirtualMachineVulnerabilityReportResourcesConfiguration = { + resourceScope: VirtualMachineVulnerabilityReportResourceScope; +}; + +// Types of properties: +export type VirtualMachineVulnerabilityReportFilters = { + allVuln: boolean; + query: string; +}; + +export type VirtualMachineVulnerabilityReportResourceScope = { + entityScope: EntityScope; // Cluster and Namespace +}; // TODO temporary alias to limit changed files that might be superseded later anyway export type ReportConfiguration = ImageVulnerabilityReportConfiguration; @@ -195,7 +233,7 @@ export type ViewBasedReportSnapshot = Snapshot & { areaOfConcern: string; }; -// TODO temporary disjunction until snamshot has type property. +// TODO temporary disjunction until snapshot has type property. type VulnerabilityReportFilters = | NodeVulnerabilityReportFilters | ImageVulnerabilityReportFiltersForCollection From e9aaa7742fbb50c0e0809a818d330e2c71b581e8 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 27 Aug 2026 08:06:21 -0400 Subject: [PATCH 115/638] ROX-36566: Make filterChipLabel consistent for CVSS search field (#22459) --- .../src/Components/CompoundSearchFilter/attributes/nodeCVE.ts | 2 +- .../Components/CompoundSearchFilter/attributes/platformCVE.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/nodeCVE.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/nodeCVE.ts index 4c01db84b81f6..089c86950eefb 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/nodeCVE.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/nodeCVE.ts @@ -18,7 +18,7 @@ export const DiscoveredTime: CompoundSearchFilterAttribute = { export const CVSS: CompoundSearchFilterAttribute = { displayName: 'CVSS', - filterChipLabel: 'CVE CVSS', + filterChipLabel: 'CVSS', searchTerm: 'CVSS', inputType: 'condition-number', }; diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/platformCVE.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/platformCVE.ts index 3fe885ed1e870..e71333f8dba9e 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/platformCVE.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/platformCVE.ts @@ -18,7 +18,7 @@ export const DiscoveredTime: CompoundSearchFilterAttribute = { export const CVSS: CompoundSearchFilterAttribute = { displayName: 'CVSS', - filterChipLabel: 'CVE CVSS', + filterChipLabel: 'CVSS', searchTerm: 'CVSS', inputType: 'condition-number', }; From 91f0c658229eb57c619ec6fc4867bd2517610894 Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Thu, 27 Aug 2026 12:57:29 +0000 Subject: [PATCH 116/638] ROX-36389: ensure vulnerability state observed in console plugin (#22399) --- central/views/imagecve/view_test.go | 57 +++++++++++++++++++ central/views/images/view_test.go | 55 ++++++++++++++++++ .../hooks/useDefaultWorkloadCveViewContext.ts | 2 +- 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/central/views/imagecve/view_test.go b/central/views/imagecve/view_test.go index 763c76025f315..242d64f29703f 100644 --- a/central/views/imagecve/view_test.go +++ b/central/views/imagecve/view_test.go @@ -1263,6 +1263,63 @@ func standardizeImages(images ...*storage.Image) { } } +// TestImageCVECountFiltering verifies that "Image CVE Count > 0" doesn't drop any +// CVEs from ImageCVEView results when the underlying data is healthy (see ROX-36389 +// for background on this field, including a case where it isn't). +func (s *ImageCVEViewTestSuite) TestImageCVECountFiltering() { + if !features.FlattenImageData.Enabled() { + s.T().Skip("Image CVE Count only maps to images_v2.ScanStats.CveCount when FlattenImageData is enabled") + } + + ctx := s.suiteCtx + + imageV2Store := imageV2DS.GetTestPostgresDataStore(s.T(), s.testDB.DB) + zeroCveImage := &storage.ImageV2{ + Id: imageUtils.NewImageV2ID(&storage.ImageName{Registry: "reg-zero", FullName: "reg-zero"}, "sha-zero"), + Digest: "sha-zero", + Name: &storage.ImageName{Registry: "reg-zero", FullName: "reg-zero"}, + Scan: &storage.ImageScan{ + OperatingSystem: "zero-os", + Components: []*storage.EmbeddedImageScanComponent{}, + }, + } + s.Require().NoError(imageV2Store.UpsertImage(ctx, zeroCveImage)) + actualZeroCve, found, err := imageV2Store.GetImage(ctx, zeroCveImage.GetId()) + s.Require().NoError(err) + s.Require().True(found) + s.Require().Equal(int32(0), actualZeroCve.GetScanStats().GetCveCount()) + + noFilterQuery := search.EmptyQuery() + filteredQuery := search.NewQueryBuilder().AddStrings(search.ImageCVECount, ">0").ProtoQuery() + + noFilterCount, err := s.cveView.Count(ctx, noFilterQuery) + s.Require().NoError(err) + filteredCount, err := s.cveView.Count(ctx, filteredQuery) + s.Require().NoError(err) + + s.T().Logf("ImageCVEView.Count() with no filter: %d; with 'Image CVE Count > 0': %d", noFilterCount, filteredCount) + + s.Equal(noFilterCount, filteredCount, + "expected 'Image CVE Count > 0' to be a no-op on the CVE count") + + noFilterResults, err := s.cveView.Get(ctx, noFilterQuery, views.ReadOptions{}) + s.Require().NoError(err) + filteredResults, err := s.cveView.Get(ctx, filteredQuery, views.ReadOptions{}) + s.Require().NoError(err) + + getCVESet := func(results []CveCore) []string { + ids := make([]string, 0, len(results)) + for _, r := range results { + ids = append(ids, r.GetCVE()) + } + slices.Sort(ids) + return ids + } + + s.Equal(getCVESet(noFilterResults), getCVESet(filteredResults), + "expected the exact same set of CVEs to be returned with and without the 'Image CVE Count > 0' filter") +} + func TestImageCVEUnknownSeverity(t *testing.T) { ctx := sac.WithAllAccess(context.Background()) testDB := pgtest.ForT(t) diff --git a/central/views/images/view_test.go b/central/views/images/view_test.go index b4a8410acbdeb..741970fec5c1e 100644 --- a/central/views/images/view_test.go +++ b/central/views/images/view_test.go @@ -85,6 +85,8 @@ type ImageViewTestSuite struct { testImagesMap map[string]*storage.Image testImagesV2Map map[string]*storage.ImageV2 scopeToImageIDs map[string]set.StringSet + // zeroCveImageID is used by TestImageCVECountFiltering. + zeroCveImageID string } func (s *ImageViewTestSuite) SetupSuite() { @@ -114,6 +116,15 @@ func (s *ImageViewTestSuite) SetupSuite() { s.testImagesV2Map[actual.GetId()] = image } + // For TestImageCVECountFiltering. + zeroCveImageV2 := utils.ConvertToV2(zeroCveTestImage()) + s.Require().NoError(imageStore.UpsertImage(ctx, zeroCveImageV2)) + actualZeroCve, found, err := imageStore.GetImage(ctx, zeroCveImageV2.GetId()) + s.Require().NoError(err) + s.Require().True(found) + s.zeroCveImageID = actualZeroCve.GetId() + s.testImagesV2Map[s.zeroCveImageID] = zeroCveImageV2 + s.imagesView = NewImageView(s.testDB.DB) deployments := []*storage.Deployment{ @@ -167,6 +178,15 @@ func (s *ImageViewTestSuite) SetupSuite() { s.testImagesMap[actual.GetId()] = actual } + // For TestImageCVECountFiltering. + zeroCveImage := zeroCveTestImage() + s.Require().NoError(imageStore.UpsertImage(ctx, zeroCveImage)) + actualZeroCve, found, err := imageStore.GetImage(ctx, zeroCveImage.GetId()) + s.Require().NoError(err) + s.Require().True(found) + s.zeroCveImageID = actualZeroCve.GetId() + s.testImagesMap[s.zeroCveImageID] = actualZeroCve + s.imagesView = NewImageView(s.testDB.DB) deployments := []*storage.Deployment{ @@ -486,6 +506,24 @@ func (s *ImageViewTestSuite) TestGetImagesCore() { } } +// TestImageCVECountFiltering verifies that "Image CVE Count > 0" excludes zero-CVE +// images from ImageView results (see ROX-36389 for background on this field). +func (s *ImageViewTestSuite) TestImageCVECountFiltering() { + ctx := sac.WithAllAccess(context.Background()) + + query := search.NewQueryBuilder().AddStrings(search.ImageCVECount, ">0").ProtoQuery() + cores, err := s.imagesView.Get(ctx, query) + s.Require().NoError(err) + + ids := make([]string, 0, len(cores)) + for _, c := range cores { + ids = append(ids, c.GetImageID()) + } + + s.NotContains(ids, s.zeroCveImageID, "expected the 0-CVE image to be excluded by 'Image CVE Count > 0'") + s.NotEmpty(ids, "expected images with CVEs to still be returned") +} + func (s *ImageViewTestSuite) compileExpected(filter *filterImpl, less lessFunc, hasVulnFilters, hasSortBySeverityCounts bool) []ImageCore { expected := make([]*imageResponse, 0) if features.FlattenImageData.Enabled() { @@ -814,3 +852,20 @@ func testImages() []*storage.Image { } return []*storage.Image{img1, img2, img3, img4} } + +func zeroCveTestImage() *storage.Image { + return &storage.Image{ + Id: "sha5", + Name: &storage.ImageName{ + Registry: "reg5", + Remote: "img5", + Tag: "tag5", + FullName: "reg5/img5:tag5", + }, + RiskScore: 1, + Scan: &storage.ImageScan{ + OperatingSystem: "os5", + Components: []*storage.EmbeddedImageScanComponent{}, + }, + } +} diff --git a/ui/apps/platform/src/ConsolePlugin/hooks/useDefaultWorkloadCveViewContext.ts b/ui/apps/platform/src/ConsolePlugin/hooks/useDefaultWorkloadCveViewContext.ts index fdedeab2e5d30..10cb06cacf8ab 100644 --- a/ui/apps/platform/src/ConsolePlugin/hooks/useDefaultWorkloadCveViewContext.ts +++ b/ui/apps/platform/src/ConsolePlugin/hooks/useDefaultWorkloadCveViewContext.ts @@ -21,7 +21,7 @@ export function useDefaultWorkloadCveViewContext(): WorkloadCveView { const querySearchFilter = parseQuerySearchFilter(searchFilter); return { baseSearchFilter: { - 'Image CVE Count': ['>0'], + 'Vulnerability State': ['OBSERVED'], Namespace: activeNamespace === ALL_NAMESPACES_KEY ? querySearchFilter.Namespace From 437679491589ad27ccf6fec9b93d48f493eb6ed6 Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:36:01 +0200 Subject: [PATCH 117/638] ROX-33973: Add feature flag to gate access control delegation to OPP (#22280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stephan Heßelmann --- pkg/features/list.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/features/list.go b/pkg/features/list.go index 2fa41b0d09857..cee4a171d83f1 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -148,6 +148,9 @@ var ( // LegacyScanner enables the legacy scanner (Scanner V2) integration. LegacyScanner = registerFeature("Enable legacy scanner (Scanner V2) integration", "ROX_LEGACY_SCANNER", enabled) + + // ACMAccessControlDelegation enables AuthProviders with role lookup delegation to ACM access control information. + ACMAccessControlDelegation = registerFeature("Enable ACS access control integration with Red Hat Advanced Cluster Management", "ROX_ACM_ACCESS_CONTROL_DELEGATION") ) // The following feature flags are related to Scanner V4. From 7a6c3c4dccded8ddba9f84bf37f5cdc2f7d13629 Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Thu, 27 Aug 2026 13:36:48 +0000 Subject: [PATCH 118/638] ROX-36465: prevent kubectl NotFound errors from corrupting logs (#22475) --- scripts/ci/collect-service-logs.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/ci/collect-service-logs.sh b/scripts/ci/collect-service-logs.sh index e8ce1ad22aa0a..82f0753973a3c 100755 --- a/scripts/ci/collect-service-logs.sh +++ b/scripts/ci/collect-service-logs.sh @@ -83,7 +83,15 @@ main() { local item_count=0 for item in $(kubectl -n "${namespace}" get "${object}" -o jsonpath='{.items}' | jq -r '.[] | select(.metadata.deletionTimestamp | not) | .metadata.name'); do - kubectl get "${object}" "${item}" -n "${namespace}" -o json > "${log_dir}/${object}/${item}_object.json" 2>&1 + if ! kubectl get "${object}" "${item}" -n "${namespace}" -o json > "${log_dir}/${object}/${item}_object.json" 2>/dev/null; then + # The object may have been deleted/replaced between listing it above and + # fetching it here. Don't let the stderr text (e.g. "Error from server + # (NotFound)") get merged into what is meant to be a JSON file, and don't + # leave a partial/empty file behind for downstream jq consumers to choke on. + info "Cannot get ${object}/${item} in ${namespace} (likely deleted mid-collection): skipping" + rm -f "${log_dir}/${object}/${item}_object.json" + continue + fi { kubectl describe "${object}" "${item}" -n "${namespace}" 2>&1 echo From 1ebcfa5620ed87b674827ce7d11526c2a9273e67 Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Thu, 27 Aug 2026 13:37:57 +0000 Subject: [PATCH 119/638] ROX-36302: increase TestPing timeout to 30s (#22474) --- tests/api_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api_test.go b/tests/api_test.go index e5312dbb20e8c..21db9b41c6d94 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -14,7 +14,7 @@ import ( func TestPing(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() conn := centralgrpc.GRPCConnectionToCentral(t) From aef8ba0847f6b2363f603af35d8af3be02c3fb28 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:55:14 +0200 Subject: [PATCH 120/638] ROX-33929: drop SortMany and unshare PLOP insert timeouts (#22502) --- .../datastore/datastore_impl_test.go | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/central/processlisteningonport/datastore/datastore_impl_test.go b/central/processlisteningonport/datastore/datastore_impl_test.go index 1b4b67b8d1bf4..6acfb31cf64af 100644 --- a/central/processlisteningonport/datastore/datastore_impl_test.go +++ b/central/processlisteningonport/datastore/datastore_impl_test.go @@ -2735,19 +2735,20 @@ func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUID() { } } -func (suite *PLOPDataStoreTestSuite) addTooMany(plops []*storage.ProcessListeningOnPortFromSensor) { +func (suite *PLOPDataStoreTestSuite) addTooMany(plops []*storage.ProcessListeningOnPortFromSensor) error { batchSize := 30000 - // Use an explicit context timeout so that ContextWithTimeoutIfNotExists - // in the Postgres pool layer sees an existing deadline and skips the - // 60-second default per-query timeout, which is too short in some cases. - ctx, cancel := context.WithTimeout(suite.hasWriteCtx, 5*time.Minute) - defer cancel() - for plopBatch := range slices.Chunk(plops, batchSize) { + // Per-batch deadline so ContextWithTimeoutIfNotExists skips the + // 60-second default without sharing one budget across all batches. + ctx, cancel := context.WithTimeout(suite.hasWriteCtx, 5*time.Minute) err := suite.datastore.AddProcessListeningOnPort(ctx, fixtureconsts.Cluster1, plopBatch...) - suite.NoError(err) + cancel() + if err != nil { + return err + } } + return nil } func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutPodUIDScale(nport int, nprocess int, npod int) { @@ -2763,7 +2764,7 @@ func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutPodUIDScale(nport int, np } // Add the PLOPs - suite.addTooMany(plopObjects) + suite.Require().NoError(suite.addTooMany(plopObjects)) plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) suite.Equal(plopCount, 2*nport*nprocess*npod) @@ -2822,7 +2823,7 @@ func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScaleRaceCondit } // Add the open PLOPs - suite.addTooMany(plopObjects) + suite.NoError(suite.addTooMany(plopObjects)) // Close the PLOPs // This is so that UpsertMany will trigger deletes @@ -2831,7 +2832,7 @@ func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScaleRaceCondit } // Add the closed PLOPs - suite.addTooMany(plopObjects) + suite.NoError(suite.addTooMany(plopObjects)) } }() @@ -2874,23 +2875,3 @@ func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScaleRaceCondit suite.GreaterOrEqual(plopsWithoutPodUids, totalPrunedCount/2) suite.LessOrEqual(plopsWithoutPodUids, totalPrunedCount) } - -func (suite *PLOPDataStoreTestSuite) TestSortMany() { - nport := 50 - nprocess := 50 - npod := 50 - - plops := makeRandomPlops(nport, nprocess, npod, fixtureconsts.Deployment1) - suite.addTooMany(plops) - - suite.addDeployments() - - startTime := time.Now() - newPlops, err := suite.datastore.GetProcessListeningOnPort( - suite.hasWriteCtx, fixtureconsts.Deployment1) - suite.NoError(err) - duration := time.Since(startTime) - - fmt.Printf("Fetching %d plops took %s\n", len(newPlops), duration) - -} From 76702afcaf098ffcebd28ea3ac3af5c942f6c82d Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 27 Aug 2026 12:10:05 -0400 Subject: [PATCH 121/638] ROX-36586: Move validation schema to same folder as wizard step 2 (#22479) --- .../View/ImageVulnerabilityFiltersView.tsx | 4 +- ...eVulnerabilityFiltersViewForCollection.tsx | 4 +- .../View/ImageVulnerabilityResourcesView.tsx | 4 +- .../ImageVulnerabilityReportWizardPage.tsx | 3 +- .../Step2/ImageVulnerabilityResourcesStep.tsx | 20 +- .../Wizard/Step2/validationSchemaResources.ts | 45 ++++ .../Step3/ImageVulnerabilityFiltersSince.tsx | 8 +- .../Step3/ImageVulnerabilityFiltersStep.tsx | 11 +- ...eVulnerabilityFiltersStepForCollection.tsx | 10 +- .../Wizard/Step3/validationSchemaFilters.ts | 89 ++++++++ ...ageVulnerabilityReportValidationSchemas.ts | 192 +----------------- .../imageVulnerabilityReports.defaults.ts | 4 +- .../imageVulnerabilityReports.types.ts | 57 ------ .../imageVulnerabilityReports.utils.ts | 21 +- .../VulnReports/ConfigReportsTab.tsx | 8 +- .../src/services/ReportsService.types.ts | 42 ++++ 16 files changed, 229 insertions(+), 293 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/validationSchemaResources.ts create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/validationSchemaFilters.ts rename ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/{ => Wizard}/imageVulnerabilityReports.defaults.ts (90%) delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx index 1066de8e59cdf..939711e6eec31 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersView.tsx @@ -15,9 +15,9 @@ import type { CompoundSearchFilterAttribute, CompoundSearchFilterConfig, } from 'Components/CompoundSearchFilter/types'; +import type { ImageVulnerabilityReportFiltersConfigurationForEntity } from 'services/ReportsService.types'; import { getSearchFilterFromSearchString } from 'utils/searchUtils'; -import type { ImageVulnerabilityFiltersConfigurationForEntity } from '../imageVulnerabilityReports.types'; import { getTextForCvesSince } from '../imageVulnerabilityReports.utils'; export type ImageVulnerabilityFiltersViewProps = { @@ -25,7 +25,7 @@ export type ImageVulnerabilityFiltersViewProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; searchFilterConfig: CompoundSearchFilterConfig; - values: ImageVulnerabilityFiltersConfigurationForEntity; + values: ImageVulnerabilityReportFiltersConfigurationForEntity; }; function ImageVulnerabilityFiltersView({ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx index f2ff0ee193db7..a56026f4da25f 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityFiltersViewForCollection.tsx @@ -12,14 +12,14 @@ import type { BreakpointModifiers } from '@patternfly/react-core'; import { fixabilityLabels } from 'constants/reportConstants'; import { vulnerabilitySeverityLabels } from 'messages/common'; +import type { ImageVulnerabilityReportFiltersConfigurationForCollection } from 'services/ReportsService.types'; -import type { ImageVulnerabilityFiltersConfigurationForCollection } from '../imageVulnerabilityReports.types'; import { getTextForCvesSince } from '../imageVulnerabilityReports.utils'; export type ImageVulnerabilityFiltersViewForCollectionProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; - values: ImageVulnerabilityFiltersConfigurationForCollection; + values: ImageVulnerabilityReportFiltersConfigurationForCollection; }; function ImageVulnerabilityFiltersViewForCollection({ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx index 7281a9af4647d..477a4f572047f 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx @@ -11,14 +11,14 @@ import { import type { BreakpointModifiers } from '@patternfly/react-core'; import EntityScopeDescriptionListGroups from 'Components/EntityScope/EntityScopeDescriptionListGroups'; +import type { ImageVulnerabilityReportResourcesConfiguration } from 'services/ReportsService.types'; -import type { ImageVulnerabilityResourcesConfiguration } from '../imageVulnerabilityReports.types'; import { imageTypeLabelMap } from '../imageVulnerabilityReports.utils'; export type ImageVulnerabilityResourcesViewProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; - values: ImageVulnerabilityResourcesConfiguration; + values: ImageVulnerabilityReportResourcesConfiguration; }; function ImageVulnerabilityResourcesView({ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx index 4b56c365f3e35..ae260a2e9e118 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx @@ -44,8 +44,7 @@ import { searchFilterConfigForImageVulnerabilityReport, } from '../../../searchFilterConfig'; -import { defaultImageVulnerabilityReportConfiguration } from '../imageVulnerabilityReports.defaults'; - +import { defaultImageVulnerabilityReportConfiguration } from './imageVulnerabilityReports.defaults'; import ImageVulnerabilityReportWizard from './ImageVulnerabilityReportWizard'; const searchFilterLabelsForImages: SearchFieldLabel[] = [ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx index 20680096a2c0a..de2d2c4568776 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/ImageVulnerabilityResourcesStep.tsx @@ -15,12 +15,10 @@ import type { EntityScope, EntityScopeRule, ImageType, + ImageVulnerabilityReportFiltersConfiguration, + ImageVulnerabilityReportResourcesConfiguration, } from 'services/ReportsService.types'; -import type { - ImageVulnerabilityFiltersConfiguration, - ImageVulnerabilityResourcesConfiguration, -} from '../../imageVulnerabilityReports.types'; import { replaceFiltersForCollectionFromQueryInConfiguration, replaceFiltersForEntityFromFixabilityAndSeverityInConfiguration, @@ -34,13 +32,15 @@ function hasWatchedImagesOnly(imageTypes: ImageType[]) { } export type ImageVulnerabilityResourcesStepProps< - T extends ImageVulnerabilityResourcesConfiguration = ImageVulnerabilityResourcesConfiguration, + T extends ImageVulnerabilityReportResourcesConfiguration = + ImageVulnerabilityReportResourcesConfiguration, > = { formik: FormikProps; }; function ImageVulnerabilityResourcesStep< - T extends ImageVulnerabilityResourcesConfiguration = ImageVulnerabilityResourcesConfiguration, + T extends ImageVulnerabilityReportResourcesConfiguration = + ImageVulnerabilityReportResourcesConfiguration, >({ formik }: ImageVulnerabilityResourcesStepProps): ReactElement { const [collectionScopeSelected, setCollectionScopeSelected] = useState( 'collectionScope' in formik.values.resourceScope @@ -57,11 +57,11 @@ function ImageVulnerabilityResourcesStep< // If user selects entityScope again, then restore it instead of having forgotten it. // Forgive double type assertion until we delete deprecated collections. const [vulnReportFiltersForEntityScope, setVulnReportFiltersForEntityScope] = useState< - ImageVulnerabilityResourcesConfiguration['vulnReportFilters'] + ImageVulnerabilityReportResourcesConfiguration['vulnReportFilters'] >( replaceFiltersForEntityFromFixabilityAndSeverityInConfiguration( - formik.values as unknown as ImageVulnerabilityFiltersConfiguration - ) as unknown as ImageVulnerabilityResourcesConfiguration['vulnReportFilters'] + formik.values as unknown as ImageVulnerabilityReportFiltersConfiguration + ) as unknown as ImageVulnerabilityReportResourcesConfiguration['vulnReportFilters'] ); // Remember rules property of entityScope if user selects WATCHED only. @@ -103,7 +103,7 @@ function ImageVulnerabilityResourcesStep< formik.setFieldValue( 'vulnReportFilters', replaceFiltersForCollectionFromQueryInConfiguration( - formik.values as unknown as ImageVulnerabilityFiltersConfiguration + formik.values as unknown as ImageVulnerabilityReportFiltersConfiguration ) ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/validationSchemaResources.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/validationSchemaResources.ts new file mode 100644 index 0000000000000..e46f3ab07a246 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step2/validationSchemaResources.ts @@ -0,0 +1,45 @@ +import * as yup from 'yup'; + +import { imageTypes } from 'services/ReportsService.types'; +import type { + ImageType, + ImageVulnerabilityReportResourceScope, + ImageVulnerabilityReportResourcesConfiguration, +} from 'services/ReportsService.types'; + +export const validationSchemaResources: yup.ObjectSchema = + yup.object().shape({ + vulnReportFilters: yup.object().shape({ + imageTypes: yup + .array() + .of(yup.string().oneOf(imageTypes).required()) + .min(1, 'Select at least one image type') + .required(), + }), + resourceScope: yup + .object() + .test( + 'resource-scope-required', + 'Resource scope is required for deployed images', + function test(value) { + // resourceScope can be either CollectionScope or EntityScope, which .shape() + // can't express. Cast so property access is type checked. + const resourceScope = value as ImageVulnerabilityReportResourceScope; + const imageTypesValue: ImageType[] = + this.parent?.vulnReportFilters?.imageTypes ?? []; + const hasDeployed = imageTypesValue.includes('DEPLOYED'); + + if (!hasDeployed) { + return true; + } + + if ('collectionScope' in resourceScope) { + return Boolean(resourceScope.collectionScope.collectionId); + } + if ('entityScope' in resourceScope) { + return resourceScope.entityScope.rules.length > 0; + } + return false; + } + ) as unknown as yup.Schema, + }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx index 6878dbf03d007..7f4bca0a3ba89 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersSince.tsx @@ -5,21 +5,23 @@ import type { FormikProps } from 'formik'; import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; import SelectSingle from 'Components/SelectSingle/SelectSingle'; +import type { ImageVulnerabilityReportFiltersConfiguration } from 'services/ReportsService.types'; -import type { ImageVulnerabilityFiltersConfiguration } from '../../imageVulnerabilityReports.types'; import { getReportFiltersWithoutCvesSince, getValueForCvesSince, } from '../../imageVulnerabilityReports.utils'; export type ImageVulnerabilityFiltersSinceProps< - T extends ImageVulnerabilityFiltersConfiguration = ImageVulnerabilityFiltersConfiguration, + T extends ImageVulnerabilityReportFiltersConfiguration = + ImageVulnerabilityReportFiltersConfiguration, > = { formik: FormikProps; }; function ImageVulnerabilityFiltersSince< - T extends ImageVulnerabilityFiltersConfiguration = ImageVulnerabilityFiltersConfiguration, + T extends ImageVulnerabilityReportFiltersConfiguration = + ImageVulnerabilityReportFiltersConfiguration, >({ formik }: ImageVulnerabilityFiltersSinceProps): ReactElement { // Initialize with either selected start date or unspecified value. // Objective of local state is remember date in scenario: diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx index e1124ae780ef0..50d71fb149696 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStep.tsx @@ -9,14 +9,13 @@ import type { } from 'Components/CompoundSearchFilter/types'; import FiltersQuery from 'Components/Reports/Step/FiltersQuery'; - -import type { ImageVulnerabilityFiltersConfigurationForEntity } from '../../imageVulnerabilityReports.types'; +import type { ImageVulnerabilityReportFiltersConfigurationForEntity } from 'services/ReportsService.types'; import ImageVulnerabilityFiltersSince from './ImageVulnerabilityFiltersSince'; export type ImageVulnerabilityFiltersStepProps< - T extends ImageVulnerabilityFiltersConfigurationForEntity = - ImageVulnerabilityFiltersConfigurationForEntity, + T extends ImageVulnerabilityReportFiltersConfigurationForEntity = + ImageVulnerabilityReportFiltersConfigurationForEntity, > = { attributesSeparateFromConfig: SelectSearchFilterAttribute[]; formik: FormikProps; @@ -24,8 +23,8 @@ export type ImageVulnerabilityFiltersStepProps< }; function ImageVulnerabilityFiltersStep< - T extends ImageVulnerabilityFiltersConfigurationForEntity = - ImageVulnerabilityFiltersConfigurationForEntity, + T extends ImageVulnerabilityReportFiltersConfigurationForEntity = + ImageVulnerabilityReportFiltersConfigurationForEntity, >({ attributesSeparateFromConfig, formik, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx index ccb8107eda1c8..91b7780f67ac5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/ImageVulnerabilityFiltersStepForCollection.tsx @@ -7,9 +7,9 @@ import SearchFilterSelectInclusive from 'Components/CompoundSearchFilter/compone import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; import type { OnSearchPayload } from 'Components/CompoundSearchFilter/types'; import { updateSearchFilter } from 'Components/CompoundSearchFilter/utils/utils'; +import type { ImageVulnerabilityReportFiltersConfigurationForCollection } from 'services/ReportsService.types'; import { attributeForSeverityInBackendAndViewBasedReport } from '../../../../searchFilterConfig'; -import type { ImageVulnerabilityFiltersConfigurationForCollection } from '../../imageVulnerabilityReports.types'; import { attributeForFixabilityInFiltersStepForCollection, getFixabilityFromSearchFilter, @@ -20,15 +20,15 @@ import { import ImageVulnerabilityFiltersSince from './ImageVulnerabilityFiltersSince'; export type ImageVulnerabilityFiltersStepForCollectionProps< - T extends ImageVulnerabilityFiltersConfigurationForCollection = - ImageVulnerabilityFiltersConfigurationForCollection, + T extends ImageVulnerabilityReportFiltersConfigurationForCollection = + ImageVulnerabilityReportFiltersConfigurationForCollection, > = { formik: FormikProps; }; function ImageVulnerabilityFiltersStepForCollection< - T extends ImageVulnerabilityFiltersConfigurationForCollection = - ImageVulnerabilityFiltersConfigurationForCollection, + T extends ImageVulnerabilityReportFiltersConfigurationForCollection = + ImageVulnerabilityReportFiltersConfigurationForCollection, >({ formik }: ImageVulnerabilityFiltersStepForCollectionProps): ReactElement { const searchFilter = getSearchFilterFromReportConfiguration(formik.values.vulnReportFilters); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/validationSchemaFilters.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/validationSchemaFilters.ts new file mode 100644 index 0000000000000..86264538e7d29 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/Step3/validationSchemaFilters.ts @@ -0,0 +1,89 @@ +import * as yup from 'yup'; +import { isValid, parse } from 'date-fns'; + +import type { + CvesSince, + ImageVulnerabilityReportConfiguration, + ImageVulnerabilityReportConfigurationForCollection, + ImageVulnerabilityReportFiltersForEntity, +} from 'services/ReportsService.types'; +import { vulnerabilitySeverities } from 'types/cve.proto'; + +function isCvesSinceValid(filters: CvesSince): boolean { + if ('allVuln' in filters) { + return true; + } + if ('sinceLastSentScheduledReport' in filters) { + return true; + } + if ('sinceStartDate' in filters) { + if (!filters.sinceStartDate) { + return false; + } + // parse() is lenient. partial strings like "2026-10" pass isValid. + // Checking for "T" ensures the value is likely a full ISO timestamp. + return filters.sinceStartDate.includes('T') && isValid(parse(filters.sinceStartDate)); + } + return false; +} + +export const validationSchemaFilters = yup.lazy((values: ImageVulnerabilityReportConfiguration) => { + if ('entityScope' in values.resourceScope) { + return yup.object().shape({ + vulnReportFilters: yup + .object() + .shape({ + query: yup.string().required('At least one filter is required'), + }) + .test( + 'cves-since-entity', + 'CVE date range is required', + function test(reportFilters) { + // CvesSince is a union type that .shape() can't express. + // cast so property access is type checked. + const filters = reportFilters as ImageVulnerabilityReportFiltersForEntity; + if ('sinceStartDate' in filters && !isCvesSinceValid(filters)) { + return this.createError({ + path: 'vulnReportFilters.sinceStartDate', + message: 'A valid start date is required', + }); + } + return isCvesSinceValid(filters); + } + ), + }); + } + + return yup.object().shape({ + vulnReportFilters: yup + .object() + .shape({ + severities: yup + .array() + .of(yup.string().oneOf(vulnerabilitySeverities).required()) + .min(1, 'Select at least one CVE severity') + .required('CVE severity is required'), + fixability: yup + .string() + .oneOf(['FIXABLE', 'NOT_FIXABLE', 'BOTH']) + .required('CVE status is required'), + }) + .test( + 'cves-since-collection', + 'CVE date range is required', + function test(reportFilters) { + // CvesSince is a union type that .shape() can't express. + // cast so property access is type checked. + const filters = + reportFilters as ImageVulnerabilityReportConfigurationForCollection['vulnReportFilters']; + if ('sinceStartDate' in filters && !isCvesSinceValid(filters)) { + return this.createError({ + path: 'vulnReportFilters.sinceStartDate', + message: 'A valid start date is required', + }); + } + return isCvesSinceValid(filters); + } + ), + }); +}); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts index 09285d0710036..0ecf679dfc98c 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReportValidationSchemas.ts @@ -1,195 +1,11 @@ -import { isValid, parse } from 'date-fns'; import * as yup from 'yup'; -import type { DeliveryType, DetailsType } from 'Components/Reports/reports.types'; -import type { Schedule } from 'types/schedule.proto'; -import { imageTypes } from 'services/ReportsService.types'; -import type { - CvesSince, - ImageType, - ImageVulnerabilityReportConfiguration, - ImageVulnerabilityReportConfigurationForCollection, - ImageVulnerabilityReportFiltersForEntity, -} from 'services/ReportsService.types'; -import { vulnerabilitySeverities } from 'types/cve.proto'; - +import { validationSchemaDelivery } from 'Components/Reports/Step/validationSchemaDelivery'; +import { validationSchemaDetails } from 'Components/Reports/Step/validationSchemaDetails'; import { ensureExhaustive } from 'utils/type.utils'; -import type { - ImageVulnerabilityResourceScope, - ImageVulnerabilityResourcesConfiguration, -} from '../imageVulnerabilityReports.types'; - -const validationSchemaDetails: yup.ObjectSchema = yup.object().shape({ - name: yup.string().trim().required('Report name is required'), - description: yup.string().ensure(), -}); - -const validationSchemaResources: yup.ObjectSchema = yup - .object() - .shape({ - vulnReportFilters: yup.object().shape({ - imageTypes: yup - .array() - .of(yup.string().oneOf(imageTypes).required()) - .min(1, 'Select at least one image type') - .required(), - }), - resourceScope: yup - .object() - .test( - 'resource-scope-required', - 'Resource scope is required for deployed images', - function test(value) { - // resourceScope can be either CollectionScope or EntityScope, which .shape() - // can't express. Cast so property access is type checked. - const resourceScope = value as ImageVulnerabilityResourceScope; - const imageTypesValue: ImageType[] = - this.parent?.vulnReportFilters?.imageTypes ?? []; - const hasDeployed = imageTypesValue.includes('DEPLOYED'); - - if (!hasDeployed) { - return true; - } - - if ('collectionScope' in resourceScope) { - return Boolean(resourceScope.collectionScope.collectionId); - } - if ('entityScope' in resourceScope) { - return resourceScope.entityScope.rules.length > 0; - } - return false; - } - ) as unknown as yup.Schema, - }); - -function isCvesSinceValid(filters: CvesSince): boolean { - if ('allVuln' in filters) { - return true; - } - if ('sinceLastSentScheduledReport' in filters) { - return true; - } - if ('sinceStartDate' in filters) { - if (!filters.sinceStartDate) { - return false; - } - // parse() is lenient. partial strings like "2026-10" pass isValid. - // Checking for "T" ensures the value is likely a full ISO timestamp. - return filters.sinceStartDate.includes('T') && isValid(parse(filters.sinceStartDate)); - } - return false; -} - -const validationSchemaFilters = yup.lazy((values: ImageVulnerabilityReportConfiguration) => { - if ('entityScope' in values.resourceScope) { - return yup.object().shape({ - vulnReportFilters: yup - .object() - .shape({ - query: yup.string().required('At least one filter is required'), - }) - .test( - 'cves-since-entity', - 'CVE date range is required', - function test(reportFilters) { - // CvesSince is a union type that .shape() can't express. - // cast so property access is type checked. - const filters = reportFilters as ImageVulnerabilityReportFiltersForEntity; - if ('sinceStartDate' in filters && !isCvesSinceValid(filters)) { - return this.createError({ - path: 'vulnReportFilters.sinceStartDate', - message: 'A valid start date is required', - }); - } - return isCvesSinceValid(filters); - } - ), - }); - } - - return yup.object().shape({ - vulnReportFilters: yup - .object() - .shape({ - severities: yup - .array() - .of(yup.string().oneOf(vulnerabilitySeverities).required()) - .min(1, 'Select at least one CVE severity') - .required('CVE severity is required'), - fixability: yup - .string() - .oneOf(['FIXABLE', 'NOT_FIXABLE', 'BOTH']) - .required('CVE status is required'), - }) - .test( - 'cves-since-collection', - 'CVE date range is required', - function test(reportFilters) { - // CvesSince is a union type that .shape() can't express. - // cast so property access is type checked. - const filters = - reportFilters as ImageVulnerabilityReportConfigurationForCollection['vulnReportFilters']; - if ('sinceStartDate' in filters && !isCvesSinceValid(filters)) { - return this.createError({ - path: 'vulnReportFilters.sinceStartDate', - message: 'A valid start date is required', - }); - } - return isCvesSinceValid(filters); - } - ), - }); -}); - -const validationSchemaDelivery: yup.ObjectSchema = yup.object().shape({ - notifiers: yup - .array() - .of( - yup.object().shape({ - emailConfig: yup.object().shape({ - notifierId: yup.string().required('Email notifier is required'), - mailingLists: yup - .array() - .of(yup.string().required()) - .min(1, 'At least one distribution list email is required') - .required(), - customSubject: yup.string().defined(), - customBody: yup.string().defined(), - }), - notifierName: yup.string().defined(), - }) - ) - .defined(), - schedule: yup - .object() - .nullable() - .defined() - .test('schedule-days-required', 'At least one day must be selected', function test(value) { - if (value == null) { - return true; - } - // Schedule is a union type that .shape() can't express. - // Cast so property access is type-checked. - const schedule = value as Schedule; - if (schedule.intervalType === 'UNSET' || schedule.intervalType === 'DAILY') { - return true; - } - if ('daysOfWeek' in schedule && schedule.daysOfWeek.days.length === 0) { - return this.createError({ - path: 'schedule.daysOfWeek.days', - message: 'At least one day must be selected', - }); - } - if ('daysOfMonth' in schedule && schedule.daysOfMonth.days.length === 0) { - return this.createError({ - path: 'schedule.daysOfMonth.days', - message: 'At least one day must be selected', - }); - } - return true; - }) as unknown as yup.Schema, -}); +import { validationSchemaResources } from './Step2/validationSchemaResources'; +import { validationSchemaFilters } from './Step3/validationSchemaFilters'; export const wizardStepIds = ['details', 'resources', 'filters', 'delivery', 'review'] as const; export type WizardStepId = (typeof wizardStepIds)[number]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReports.defaults.ts similarity index 90% rename from ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts rename to ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReports.defaults.ts index d291e9182035c..7a880d4a22a11 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.defaults.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/imageVulnerabilityReports.defaults.ts @@ -4,8 +4,8 @@ import type { } from 'services/ReportsService.types'; import { getRequestQueryStringForSearchFilter } from 'utils/searchUtils'; -import { defaultStorage } from '../../WorkloadCves/Overview/WorkloadCvesOverviewPage'; -import { fixableStatusToFixability, severityLabelToSeverity } from '../../utils/searchUtils'; +import { defaultStorage } from '../../../WorkloadCves/Overview/WorkloadCvesOverviewPage'; +import { fixableStatusToFixability, severityLabelToSeverity } from '../../../utils/searchUtils'; const { preferences: { defaultFilters }, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts deleted file mode 100644 index e2f51f4ae3e9e..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { - CollectionScope, - CvesSince, - EntityScope, - Fixability, - ImageType, - NotifierConfiguration, -} from 'services/ReportsService.types'; -import type { VulnerabilitySeverity } from 'types/cve.proto'; -import type { Schedule } from 'types/schedule.proto'; - -// Types because Resources and Filters for view and wizard combine data properties. - -export type ImageVulnerabilityResourceScope = - | { - collectionScope: CollectionScope; - } - | { - entityScope: EntityScope; - }; - -export type ImageVulnerabilityResourcesConfiguration = { - resourceScope: ImageVulnerabilityResourceScope; - vulnReportFilters: { - imageTypes: ImageType[]; // more closely related to resources although in vulnReportFilters - }; -}; - -export type ImageVulnerabilityFiltersConfiguration = { - vulnReportFilters: ImageVulnerabilityFiltersWithoutCvesSince & CvesSince; -}; - -export type ImageVulnerabilityFiltersWithoutCvesSince = - | ImageVulnerabilityFiltersForEntityWithoutCvesSince - | ImageVulnerabilityFiltersForCollectionWithoutCvesSince; - -export type ImageVulnerabilityFiltersConfigurationForEntity = { - vulnReportFilters: ImageVulnerabilityFiltersForEntityWithoutCvesSince & CvesSince; -}; - -export type ImageVulnerabilityFiltersForEntityWithoutCvesSince = { - query: string; -}; - -export type ImageVulnerabilityFiltersConfigurationForCollection = { - vulnReportFilters: ImageVulnerabilityFiltersForCollectionWithoutCvesSince & CvesSince; -}; - -export type ImageVulnerabilityFiltersForCollectionWithoutCvesSince = { - fixability: Fixability; - severities: VulnerabilitySeverity[]; -}; - -export type DestinationsConfiguration = { - notifiers: NotifierConfiguration[]; - schedule: Schedule | null; -}; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts index fb3879a2f9537..efaaa8866396e 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts @@ -6,6 +6,9 @@ import type { ImageVulnerabilityReportConfiguration, ImageVulnerabilityReportConfigurationForCollection, ImageVulnerabilityReportConfigurationForEntity, + ImageVulnerabilityReportFiltersConfiguration, + ImageVulnerabilityReportFiltersForCollectionWithoutCvesSince, + ImageVulnerabilityReportFiltersWithoutCvesSince, } from 'services/ReportsService.types'; import type { VulnerabilitySeverity } from 'types/cve.proto'; import type { SearchFilter } from 'types/search'; @@ -18,12 +21,6 @@ import { import { ensureExhaustive } from 'utils/type.utils'; import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; -import type { - ImageVulnerabilityFiltersConfiguration, - ImageVulnerabilityFiltersForCollectionWithoutCvesSince, - ImageVulnerabilityFiltersWithoutCvesSince, -} from './imageVulnerabilityReports.types'; - // TypeScript needs help because entityScope and vulnReportFilters have mutual dependencies. // entityScope implies query but not fixability nore severities // collectionScope implies fixability and severities but not query @@ -69,8 +66,8 @@ export function getValueForCvesSince(vulnReportFilters: CvesSince) { // For future entity scope only, replace helper function call with the following: // const { query } = formik.values.vulnReportFilters; export function getReportFiltersWithoutCvesSince( - values: ImageVulnerabilityFiltersConfiguration -): ImageVulnerabilityFiltersWithoutCvesSince { + values: ImageVulnerabilityReportFiltersConfiguration +): ImageVulnerabilityReportFiltersWithoutCvesSince { // Idiom to ignore properties is awkward because of disjunction. /* eslint-disable @typescript-eslint/no-unused-vars */ // eslint-diable-next-line @typescript-eslint/ban-ts-comment @@ -114,7 +111,8 @@ function getFixableArrayFromFixability(fixability: Fixability): string[] { // TypeScript needs if statement and default return statement, but code does not call in that scenario. export function replaceFiltersForCollectionFromQueryInConfiguration({ vulnReportFilters, -}: ImageVulnerabilityFiltersConfiguration): ImageVulnerabilityFiltersWithoutCvesSince & CvesSince { +}: ImageVulnerabilityReportFiltersConfiguration): ImageVulnerabilityReportFiltersWithoutCvesSince & + CvesSince { if ('query' in vulnReportFilters) { const { query, ...restOfFilters } = vulnReportFilters; const searchFilter = getSearchFilterFromSearchString(query); @@ -133,7 +131,8 @@ export function replaceFiltersForCollectionFromQueryInConfiguration({ // otherwise return the original vulnReportFilters object for entityScope. export function replaceFiltersForEntityFromFixabilityAndSeverityInConfiguration({ vulnReportFilters, -}: ImageVulnerabilityFiltersConfiguration): ImageVulnerabilityFiltersWithoutCvesSince & CvesSince { +}: ImageVulnerabilityReportFiltersConfiguration): ImageVulnerabilityReportFiltersWithoutCvesSince & + CvesSince { if ('fixability' in vulnReportFilters && 'severities' in vulnReportFilters) { const { fixability, severities, ...restOfFilters } = vulnReportFilters; @@ -167,7 +166,7 @@ export const attributeForFixabilityInFiltersStepForCollection: SelectExclusiveSi // Pseudo search filter (for select elements) corresponds to property names and preceding pseudo attribute. export function getSearchFilterFromReportConfiguration( - vulnReportFilters: ImageVulnerabilityFiltersForCollectionWithoutCvesSince + vulnReportFilters: ImageVulnerabilityReportFiltersForCollectionWithoutCvesSince ): SearchFilter { return { Fixable: vulnReportFilters.fixability, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx index 8e7a9c0e2897c..0714b35338a88 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx @@ -43,11 +43,13 @@ import JobStatusPopoverContent from 'Components/ReportJob/JobStatusPopoverConten import MyLastJobStatus from 'Components/ReportJob/MyLastJobStatus'; import useAuthStatus from 'hooks/useAuthStatus'; import { deleteReportConfiguration, reportDownloadURL } from 'services/ReportsService'; -import type { ImageVulnerabilityReportConfiguration } from 'services/ReportsService.types'; +import type { + ImageVulnerabilityReportConfiguration, + ImageVulnerabilityReportResourceScope, +} from 'services/ReportsService.types'; import useDeleteModal from '../hooks/useDeleteModal'; import DeleteReportsModal from '../../Reports/components/DeleteReportsModal'; -import type { ImageVulnerabilityResourceScope } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.types'; import useFetchReports from '../api/useFetchReports'; import useRunReport from '../api/useRunReport'; import { useWatchLastSnapshotForReports } from '../api/useWatchLastSnapshotForReports'; @@ -156,7 +158,7 @@ function ConfigReportsTab() { // Because callback function exists independent of conditional rendering, // TypeScript does not know for sure that it is has a collectionScope property. - function onClickCollectionLink(resourceScope: ImageVulnerabilityResourceScope) { + function onClickCollectionLink(resourceScope: ImageVulnerabilityReportResourceScope) { if ('collectionScope' in resourceScope) { setCollectionModalId(resourceScope.collectionScope.collectionId); } diff --git a/ui/apps/platform/src/services/ReportsService.types.ts b/ui/apps/platform/src/services/ReportsService.types.ts index a825f0d148954..6e7168e1b5d74 100644 --- a/ui/apps/platform/src/services/ReportsService.types.ts +++ b/ui/apps/platform/src/services/ReportsService.types.ts @@ -91,6 +91,48 @@ export type ImageVulnerabilityReportConfigurationForCollection = { }; } & ReportConfigurationBase; +// Types of views and steps with only properties that they need to know: +export type ImageVulnerabilityReportResourcesConfiguration = { + resourceScope: ImageVulnerabilityReportResourceScope; + vulnReportFilters: { + imageTypes: ImageType[]; // more closely related to resources although in vulnReportFilters + }; +}; + +export type ImageVulnerabilityReportResourceScope = + | { + collectionScope: CollectionScope; + } + | { + entityScope: EntityScope; + }; + +// Types because Resources and Filters for view and wizard combine data properties. +export type ImageVulnerabilityReportFiltersConfiguration = { + vulnReportFilters: ImageVulnerabilityReportFiltersWithoutCvesSince & CvesSince; +}; + +export type ImageVulnerabilityReportFiltersWithoutCvesSince = + | ImageVulnerabilityReportFiltersForEntityWithoutCvesSince + | ImageVulnerabilityReportFiltersForCollectionWithoutCvesSince; + +export type ImageVulnerabilityReportFiltersConfigurationForEntity = { + vulnReportFilters: ImageVulnerabilityReportFiltersForEntityWithoutCvesSince & CvesSince; +}; + +export type ImageVulnerabilityReportFiltersForEntityWithoutCvesSince = { + query: string; +}; + +export type ImageVulnerabilityReportFiltersConfigurationForCollection = { + vulnReportFilters: ImageVulnerabilityReportFiltersForCollectionWithoutCvesSince & CvesSince; +}; + +export type ImageVulnerabilityReportFiltersForCollectionWithoutCvesSince = { + fixability: Fixability; + severities: VulnerabilitySeverity[]; +}; + // Vulnerability report filters export type Fixability = 'BOTH' | 'FIXABLE' | 'NOT_FIXABLE'; From f713410b1fac94a06d4c28d82c7255b9396ba5a9 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 27 Aug 2026 12:26:33 -0400 Subject: [PATCH 122/638] ROX-36587: Make ReportsService consistent with NodeReportsService (#22482) --- .../src/services/NodeReportsService.ts | 4 +- .../platform/src/services/ReportsService.ts | 182 +++++++++--------- 2 files changed, 94 insertions(+), 92 deletions(-) diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts index 7bf802a67c000..6c8f01a3342bd 100644 --- a/ui/apps/platform/src/services/NodeReportsService.ts +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -68,9 +68,7 @@ export function fetchNodeReportConfigurationsCount( return makeCancellableAxiosRequest((signal) => axios .get<{ count: number }>(`/v2/reports/node/configuration-count?${params}`, { signal }) - .then((response) => { - return response.data; - }) + .then((response) => response.data) ); } diff --git a/ui/apps/platform/src/services/ReportsService.ts b/ui/apps/platform/src/services/ReportsService.ts index d09b10a9e3dce..bb2180893c78c 100644 --- a/ui/apps/platform/src/services/ReportsService.ts +++ b/ui/apps/platform/src/services/ReportsService.ts @@ -6,7 +6,7 @@ import { } from 'services/ReportsService.types'; import type { ConfiguredReportSnapshot, - ReportConfiguration, + ImageVulnerabilityReportConfiguration, ReportHistoryResponse, ReportRequestViewBased, RunReportResponse, @@ -15,17 +15,45 @@ import type { } from 'services/ReportsService.types'; import type { ApiSortOption, SearchFilter } from 'types/search'; import { buildNestedRawQueryParams, getPaginationParams } from 'utils/searchUtils'; -import type { ReportNotificationMethod, ReportStatus } from 'types/reportJob'; +import type { ReportNotificationMethod } from 'types/reportJob'; import { sanitizeFilename } from 'utils/fileUtils'; import axios from './instance'; +import { makeCancellableAxiosRequest } from './cancellationUtils'; +import type { CancellableRequest } from './cancellationUtils'; import type { Empty } from './types'; import { saveFile } from './DownloadService'; // The following functions are built around the new VM Reporting Enhancements export const reportDownloadURL = '/api/reports/jobs/download'; -// @TODO: Same logic is used in fetchReportConfigurations. Maybe consider something more DRY +// https://github.com/stackrox/stackrox/blob/master/proto/api/v2/report_service.proto + +// Configuration of scheduled reports + +// Promise because of TS2339 in ImageVulnerabilityReportWizard component +// PostReportConfiguration +export function createReportConfiguration( + configuration: ImageVulnerabilityReportConfiguration +): Promise { + return axios + .post('/v2/reports/configurations', configuration) + .then((response) => response.data); +} + +// Promise because of TS2339 in ImageVulnerabilityReportWizard component +// UpdateReportConfiguration +export function updateReportConfiguration( + reportId: string, + configuration: ImageVulnerabilityReportConfiguration +): Promise { + return axios + .put(`/v2/reports/configurations/${reportId}`, configuration) + .then((response) => response.data); +} + +// Promise because of @typescript-eslint/await-thenable in useFetchReports hook +// CountReportConfigurations export function fetchReportConfigurationsCount({ query, }: { @@ -39,11 +67,11 @@ export function fetchReportConfigurationsCount({ ); return axios .get<{ count: number }>(`/v2/reports/configuration-count?${params}`) - .then((response) => { - return response.data; - }); + .then((response) => response.data); } +// Promise because of @typescript-eslint/await-thenable in useFetchReports hook +// ListReportConfigurations export function fetchReportConfigurations({ query, page, @@ -54,7 +82,7 @@ export function fetchReportConfigurations({ page: number; perPage: number; sortOption: ApiSortOption; -}): Promise { +}): Promise { const params = queryString.stringify( { query, @@ -63,35 +91,31 @@ export function fetchReportConfigurations({ { arrayFormat: 'repeat', allowDots: true } ); return axios - .get<{ reportConfigs: ReportConfiguration[] }>(`/v2/reports/configurations?${params}`) - .then((response) => { - return response?.data?.reportConfigs ?? []; - }); + .get<{ + reportConfigs: ImageVulnerabilityReportConfiguration[]; + }>(`/v2/reports/configurations?${params}`) + .then((response) => response?.data?.reportConfigs ?? []); } -export function fetchReportConfiguration(reportId: string): Promise { +// Promise because of @typescript-eslint/await-thenable in useFetchReport hook +// GetReportConfiguration +export function fetchReportConfiguration( + reportId: string +): Promise { return axios - .get(`/v2/reports/configurations/${reportId}`) - .then((response) => { - return response.data; - }); + .get(`/v2/reports/configurations/${reportId}`) + .then((response) => response.data); } -export function fetchReportStatus(id: string): Promise { +// Promise because of @typescript-eslint/await-thenable in useDeleteModal hook +// DeleteReportConfiguration +export function deleteReportConfiguration(reportId: string): Promise { return axios - .get<{ status: ReportStatus | null }>(`/v2/reports/jobs/${id}/status`) - .then((response) => { - return response.data?.status; - }); + .delete(`/v2/reports/configurations/${reportId}`) + .then((response) => response.data); } -export function fetchReportLastRunStatus(id: string): Promise { - return axios - .get<{ status: ReportStatus | null }>(`/v2/reports/last-status/${id}`) - .then((response) => { - return response.data?.status; - }); -} +// Configuration-based jobs export type FetchReportHistoryServiceParams = { id: string; @@ -102,6 +126,8 @@ export type FetchReportHistoryServiceParams = { showMyHistory: boolean; }; +// Promise because of @typescript-eslint/await-thenable in useWatchLastSnapshotForReports hook +// GetReportHistory and GetMyReportHistory export function fetchReportHistory({ id, query, @@ -119,16 +145,42 @@ export function fetchReportHistory({ }, { arrayFormat: 'repeat', allowDots: true } ); + const history = showMyHistory ? 'my-history' : 'history'; return axios - .get( - `/v2/reports/configurations/${id}/${showMyHistory ? 'my-history' : 'history'}?${params}` - ) + .get(`/v2/reports/configurations/${id}/${history}?${params}`) .then((response) => { const snapshots = response.data?.reportSnapshots ?? []; return snapshots.filter(isConfiguredReportSnapshot); }); } +// Promise because of TS2339 in useRunReport hook +// RunReport +// @TODO: Rename this to runReport when we remove the old report code +export function runReportRequest( + reportConfigId: string, + reportNotificationMethod: ReportNotificationMethod +): Promise { + return axios + .post('/v2/reports/run', { + reportConfigId, + reportNotificationMethod, + }) + .then((response) => response.data); +} + +// Job management + +// Promise because of TS2322 in ReportJobs component +// DeleteReport +export function deleteDownloadableReport(reportId: string): Promise { + return axios + .delete(`/v2/reports/jobs/${reportId}/delete`) + .then((response) => response.data); +} + +// View-based jobs + export type FetchViewBasedReportHistoryServiceParams = { searchFilter: SearchFilter; page: number; @@ -137,13 +189,14 @@ export type FetchViewBasedReportHistoryServiceParams = { showMyHistory: boolean; }; +// GetViewBasedMyReportHistory and GetViewBasedReportHistory export function fetchViewBasedReportHistory({ searchFilter, page, perPage, sortOption, showMyHistory, -}: FetchViewBasedReportHistoryServiceParams): Promise { +}: FetchViewBasedReportHistoryServiceParams): CancellableRequest { const params = buildNestedRawQueryParams( { searchFilter, page, perPage, sortOption }, 'reportParamQuery' @@ -153,66 +206,15 @@ export function fetchViewBasedReportHistory({ ? '/v2/reports/view-based/my-history' : '/v2/reports/view-based/history'; - return axios.get(`${endpoint}?${params}`).then((response) => { - const snapshots = response.data?.reportSnapshots ?? []; - return snapshots.filter(isViewBasedReportSnapshot); - }); -} - -export function createReportConfiguration( - report: ReportConfiguration -): Promise { - return axios - .post('/v2/reports/configurations', report) - .then((response) => { - return response.data; - }); -} - -export function updateReportConfiguration( - reportId: string, - report: ReportConfiguration -): Promise { - return axios - .put(`/v2/reports/configurations/${reportId}`, report) - .then((response) => { - return response.data; - }); -} - -export function deleteReportConfiguration(reportId: string): Promise { - return axios.delete(`/v2/reports/configurations/${reportId}`).then((response) => { - return response.data; - }); -} - -// @TODO: Rename this to runReport when we remove the old report code -export function runReportRequest( - reportConfigId: string, - reportNotificationMethod: ReportNotificationMethod -): Promise { - return axios - .post('/v2/reports/run', { - reportConfigId, - reportNotificationMethod, + return makeCancellableAxiosRequest((signal) => + axios.get(`${endpoint}?${params}`, { signal }).then((response) => { + const snapshots = response.data?.reportSnapshots ?? []; + return snapshots.filter(isViewBasedReportSnapshot); }) - .then((response) => { - return response.data; - }); -} - -export function downloadReport(reportId: string) { - return axios.get(`/v2/reports/jobs/${reportId}/download`).then((response) => { - return response.data; - }); -} - -export function deleteDownloadableReport(reportId: string) { - return axios.delete(`/v2/reports/jobs/${reportId}/delete`).then((response) => { - return response.data; - }); + ); } +// PostViewBasedReport export function runViewBasedReport({ query, areaOfConcern, @@ -233,6 +235,8 @@ export function runViewBasedReport({ .then((response) => response.data); } +// Job download + /** * Downloads a report file by job ID and saves it to the user's device with a sanitized filename * Returns file size in bytes for analytics tracking From 936ad82f2e02573563ebb458d42ea0ac7c1afacd Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 27 Aug 2026 14:44:28 -0400 Subject: [PATCH 123/638] ROX-36595: Use jobs an plural noun for view-based reports (#22515) --- .../NodeVulnerabilityReportsLayout.tsx | 2 +- .../VulnReports/ViewBasedReportsTab.tsx | 6 +++--- .../VulnReports/ViewBasedReportsTable.tsx | 6 +++--- .../VulnReports/VulnReportingLayout.tsx | 4 ++-- .../WorkloadCves/components/CreateViewBasedReportModal.tsx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx index e3390e19183f0..59dcd6b00c77b 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx @@ -46,7 +46,7 @@ function NodeVulnerabilityReportsLayout() { )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx index df9a7a17ad624..895e3ae8d89d2 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx @@ -146,11 +146,11 @@ function ViewBasedReportsTab() { return ( <> - + - Check job status and download view-based reports in CSV format. Requests are - purged according to retention settings. + Check job status and download view-based reports in CSV format. Jobs are purged + according to retention settings. diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx index 59b635ddc13a7..11fd7e2999abd 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx @@ -93,10 +93,10 @@ function ViewBasedReportsTable({ return ( <> - +
    - + @@ -119,7 +119,7 @@ function ViewBasedReportsTable({ return ( - - + + - - + + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx index 92f2e71a222f4..cfa9254105da8 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx @@ -17,32 +17,67 @@ import PageTitle from 'Components/PageTitle'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import useRestQuery from 'hooks/useRestQuery'; import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; +import useURLSort from 'hooks/useURLSort'; import { getVMCVEDetail, listVMCVEAffectedVMs } from 'services/VirtualMachineService'; import { getTableUIState } from 'utils/getTableUIState'; +import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; import BySeveritySummaryCard from '../../components/BySeveritySummaryCard'; import { SummaryCard, SummaryCardLayout } from '../../components/SummaryCardLayout'; import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; -import { getOverviewPagePath } from '../../utils/searchUtils'; +import { + virtualMachineComponentSearchFilterConfig, + virtualMachinesClusterSearchFilterConfig, + virtualMachinesNamespaceSearchFilterConfig, + virtualMachinesSearchFilterConfig, +} from '../../searchFilterConfig'; +import { + getHiddenSeverities, + getOverviewPagePath, + parseQuerySearchFilter, +} from '../../utils/searchUtils'; import AffectedVirtualMachinesSummaryCard from './AffectedVirtualMachinesSummaryCard'; -import AffectedVirtualMachinesTable from './AffectedVirtualMachinesTable'; +import AffectedVirtualMachinesTable, { + defaultSortOption, + sortFields, +} from './AffectedVirtualMachinesTable'; import VirtualMachineCvePageHeader from './VirtualMachineCvePageHeader'; +const searchFilterConfig = [ + virtualMachinesClusterSearchFilterConfig, + virtualMachinesNamespaceSearchFilterConfig, + virtualMachinesSearchFilterConfig, + virtualMachineComponentSearchFilterConfig, +]; + const virtualMachineCveOverviewCvePath = getOverviewPagePath('VirtualMachine', { entityTab: 'CVE', }); function VirtualMachineCvePage() { const { cveId } = useParams<{ cveId: string }>(); - - const fetchCveDetail = useCallback(() => getVMCVEDetail(cveId ?? ''), [cveId]); - const { data: cveDetail, isLoading, error } = useRestQuery(fetchCveDetail); + const { searchFilter, setSearchFilter } = useURLSearch(); + const querySearchFilter = parseQuerySearchFilter(searchFilter); const { page, perPage, setPage, setPerPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); + const { sortOption, getSortParams } = useURLSort({ sortFields, defaultSortOption }); + + const fetchCveDetail = useCallback( + () => getVMCVEDetail(cveId ?? '', parseQuerySearchFilter(searchFilter)), + [cveId, searchFilter] + ); + const { data: cveDetail, isLoading, error } = useRestQuery(fetchCveDetail); const fetchAffectedVMs = useCallback( - () => listVMCVEAffectedVMs(cveId ?? '', { page, perPage }), - [cveId, page, perPage] + () => + listVMCVEAffectedVMs(cveId ?? '', { + searchFilter: parseQuerySearchFilter(searchFilter), + sortOption, + page, + perPage, + }), + [cveId, searchFilter, sortOption, page, perPage] ); const { data: affectedVMsData, @@ -54,7 +89,7 @@ function VirtualMachineCvePage() { isLoading: isLoadingAffectedVMs, data: affectedVMsData?.vms ?? [], error: affectedVMsError, - searchFilter: {}, + searchFilter, }); const affectedVMCount = affectedVMsData?.totalCount ?? 0; @@ -76,6 +111,16 @@ function VirtualMachineCvePage() { + { + setSearchFilter(newFilter); + setPage(1); + }} + /> )} /> @@ -123,7 +168,9 @@ function VirtualMachineCvePage() { { + setSearchFilter({}); setPage(1); }} /> diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts b/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts index d79fe2c8b0c28..e119225f16106 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts @@ -23,3 +23,4 @@ export const NODE_SCAN_TIME_SORT_FIELD = 'Node Scan Time'; // Virtual Machine sort fields export const VIRTUAL_MACHINE_SORT_FIELD = 'Virtual Machine Name'; +export const GUEST_OS_SORT_FIELD = 'Guest OS'; diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index 44ec663773958..f74848fdb36c9 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -1,8 +1,14 @@ +import qs from 'qs'; + import axios from 'services/instance'; import type { VulnerabilitySeverity } from 'types/cve.proto'; import type { ScanComponent, SourceType } from 'types/scanComponent.proto'; -import type { SearchQueryOptions } from 'types/search'; -import { applyRegexSearchModifiers, buildNestedRawQueryParams } from 'utils/searchUtils'; +import type { SearchFilter, SearchQueryOptions } from 'types/search'; +import { + applyRegexSearchModifiers, + buildNestedRawQueryParams, + getRequestQueryStringForSearchFilter, +} from 'utils/searchUtils'; // Legacy API (v2/virtualmachines) @@ -252,17 +258,33 @@ export function listVMCVEs({ .then((response) => response.data); } -export function getVMCVEDetail(cveId: string): Promise { +export function getVMCVEDetail(cveId: string, searchFilter: SearchFilter): Promise { + // Might consider updating buildNestedRawQueryParams to handle this case (no pagination). + const params = qs.stringify( + { + query: { + query: getRequestQueryStringForSearchFilter( + applyRegexSearchModifiers(searchFilter) + ), + }, + }, + { arrayFormat: 'repeat', allowDots: true } + ); return axios - .get(`/v2/virtualmachines/cves/${cveId}`) + .get(`/v2/virtualmachines/cves/${cveId}?${params}`) .then((response) => response.data); } export function listVMCVEAffectedVMs( cveId: string, - { sortOption, page, perPage }: SearchQueryOptions + { searchFilter, sortOption, page, perPage }: SearchQueryOptions ): Promise { - const params = buildNestedRawQueryParams({ page, perPage, sortOption }); + const params = buildNestedRawQueryParams({ + page, + perPage, + sortOption, + searchFilter: applyRegexSearchModifiers(searchFilter ?? {}), + }); return axios .get(`/v2/virtualmachines/cves/${cveId}/vms?${params}`) .then((response) => response.data); @@ -299,5 +321,5 @@ export function listVMComponents( } export function getVM(vmId: string): Promise { - return axios.get(`/v2/virtualmachines/vms/${vmId}`).then((response) => response.data); + return axios.get(`/v2/virtualmachines/${vmId}`).then((response) => response.data); } From b4fb2577be5e1209b0b4d338252b62a39cf8972a Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Mon, 31 Aug 2026 13:58:11 +0200 Subject: [PATCH 132/638] ROX-35606: chore: remove writeExampleBundle (#21550) Co-authored-by: Guzman Alvarez --- .../datastore/key_bundle.go | 21 ------------- .../datastore/singleton.go | 2 -- .../datastore/singleton_test.go | 31 ------------------- 3 files changed, 54 deletions(-) diff --git a/central/signatureintegration/datastore/key_bundle.go b/central/signatureintegration/datastore/key_bundle.go index 1f896f4d2f4dc..1a1d22e7417e8 100644 --- a/central/signatureintegration/datastore/key_bundle.go +++ b/central/signatureintegration/datastore/key_bundle.go @@ -2,8 +2,6 @@ package datastore import ( "context" - "os" - "path/filepath" "strings" "github.com/stackrox/rox/central/signatureintegration/store" @@ -15,25 +13,6 @@ import ( var redHatKeyBundlePath = signatures.RedHatKeyBundlePath() -// ensureKeyBundleDirectory creates the directory where the watcher and -// downloader expect to find bundle.json. -func ensureKeyBundleDirectory() { - dir := filepath.Dir(redHatKeyBundlePath) - if err := os.MkdirAll(dir, 0700); err != nil { - log.Warnf("Failed to create key bundle directory %q: %v", dir, err) - } -} - -// writeExampleBundle writes bundle.example.json so offline-mode customers can -// see the expected format without consulting docs. -func writeExampleBundle() { - dir := filepath.Dir(redHatKeyBundlePath) - examplePath := filepath.Join(dir, "bundle.example.json") - if err := os.WriteFile(examplePath, signatures.DefaultBundleJSON(), 0600); err != nil { - log.Warnf("Failed to write example bundle to %q: %v", examplePath, err) - } -} - func keyBundleHandler(siStore store.SignatureIntegrationStore) filewatcher.Handler { return func(data []byte) error { bundle, err := signatures.ParseKeyBundle(data) diff --git a/central/signatureintegration/datastore/singleton.go b/central/signatureintegration/datastore/singleton.go index cfe48dced7f69..df754ee696b46 100644 --- a/central/signatureintegration/datastore/singleton.go +++ b/central/signatureintegration/datastore/singleton.go @@ -104,8 +104,6 @@ func Singleton() DataStore { once.Do(func() { storage := pgStore.New(globaldb.GetPostgres()) seedRedHatDefaultSignatureIntegration(storage) // must run before watcher; bundle file takes precedence on first tick - ensureKeyBundleDirectory() - writeExampleBundle() instance = New(storage, policyDataStore.Singleton()) startKeyBundleWatcher(storage) startKeyBundleUpdater() diff --git a/central/signatureintegration/datastore/singleton_test.go b/central/signatureintegration/datastore/singleton_test.go index f2e30835c06cb..9cde281672c82 100644 --- a/central/signatureintegration/datastore/singleton_test.go +++ b/central/signatureintegration/datastore/singleton_test.go @@ -1,15 +1,12 @@ package datastore import ( - "os" - "path/filepath" "testing" storeMocks "github.com/stackrox/rox/central/signatureintegration/store/mocks" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/signatures" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) @@ -34,34 +31,6 @@ func TestSeedSubsequentStartup(t *testing.T) { seedRedHatDefaultSignatureIntegration(mockStore) } -func TestEnsureKeyBundleDirectory(t *testing.T) { - dir := t.TempDir() - old := redHatKeyBundlePath - redHatKeyBundlePath = filepath.Join(dir, "subdir", "bundle.json") - t.Cleanup(func() { redHatKeyBundlePath = old }) - - ensureKeyBundleDirectory() - - _, err := os.Stat(filepath.Join(dir, "subdir")) - require.NoError(t, err, "directory should be created") -} - -func TestWriteExampleBundle(t *testing.T) { - dir := t.TempDir() - old := redHatKeyBundlePath - redHatKeyBundlePath = filepath.Join(dir, "bundle.json") - t.Cleanup(func() { redHatKeyBundlePath = old }) - - writeExampleBundle() - - data, err := os.ReadFile(filepath.Join(dir, "bundle.example.json")) - require.NoError(t, err, "example bundle should be written") - - bundle, err := signatures.ParseKeyBundle(data) - require.NoError(t, err, "example bundle must be valid") - assert.NotEmpty(t, bundle.CosignKeys, "example bundle must contain cosign keys") -} - func TestStartKeyBundleUpdaterOfflineMode(t *testing.T) { t.Setenv(env.OfflineModeEnv.EnvVar(), "true") t.Setenv(env.RedHatSigningKeyBundleURL.EnvVar(), "https://example.com/keys.json") From 5d33a546794ce000b55670591ff8127b9cfdb6e0 Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Mon, 31 Aug 2026 08:46:49 -0500 Subject: [PATCH 133/638] ROX-36498: Add CVE Origin filter to UI (#22488) --- .../attributes/imageCVE.ts | 23 +++++++++++++++++++ .../VulnReports/ViewBasedReportJobDetails.tsx | 5 ++++ .../DeploymentPageVulnerabilities.tsx | 12 ++++++---- .../Image/ImagePageVulnerabilities.tsx | 12 ++++++---- .../Vulnerabilities/searchFilterConfig.ts | 11 +++++---- ui/apps/platform/src/types/searchOptions.ts | 1 + 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts index e3c9a1a967df8..21fd252759695 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts @@ -1,5 +1,7 @@ // If you're adding a new attribute, make sure to add it to "imageCVEAttributes" as well +import { originDisplayNames } from 'Containers/Vulnerabilities/utils/vulnerabilityUtils'; + import type { CompoundSearchFilterAttribute } from '../types'; import { EPSSProbability } from './epssProbability'; @@ -52,4 +54,25 @@ export const KnownExploit: CompoundSearchFilterAttribute = { featureFlagDependency: ['ROX_SCANNER_V4', 'ROX_CISA_KEV'], }; +// The filter value is the VulnOrigin enum name (e.g. VULN_ORIGIN_RED_HAT), which the +// backend enum search matches by case-insensitive prefix. The human-readable label +// would not match, so value must be the enum key, not the display name. +// +// Intentionally NOT part of imageCVEAttributes: the CVE origin filter is only shown on +// the single image page, single deployment page, and image vulnerability reports, which +// append it to their own configs. +export const Origin: CompoundSearchFilterAttribute = { + displayName: 'Origin', + filterChipLabel: 'CVE origin', + searchTerm: 'CVE Origin', + inputType: 'select', + inputProps: { + options: Object.entries(originDisplayNames).map(([value, label]) => ({ + value, + label: label ?? value, + })), + }, + featureFlagDependency: ['ROX_SCANNER_V4'], +}; + export const imageCVEAttributes = [CVSS, DiscoveredTime, EPSSProbability, KnownExploit, Name]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportJobDetails.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportJobDetails.tsx index 9074b60bb3627..d9003406cfd98 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportJobDetails.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportJobDetails.tsx @@ -9,6 +9,7 @@ import { import type { ViewBasedReportSnapshot } from 'services/ReportsService.types'; import CompoundSearchFilterDescriptionListGroups from 'Components/CompoundSearchFilter/components/CompoundSearchFilterDescriptionListGroups'; +import { Origin } from 'Components/CompoundSearchFilter/attributes/imageCVE'; import { getSearchFilterFromSearchString } from 'utils/searchUtils'; import { attributesSeparateFromConfigForImageVulnerabilityReport, @@ -33,6 +34,10 @@ function ViewBasedReportJobDetails({ reportSnapshot }: ViewBasedReportJobDetails const attributes = [ ...attributesSeparateFromConfigForImageVulnerabilityReport, ...attributesFromConfig, + // Origin is not part of the shared workload/view-based config (that config also drives + // the overview page filter input, which is out of scope). Append it here so a view-based + // report created from the image/deployment single pages displays the CVE origin filter. + Origin, ]; return ( diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx index 2fbb9fb8a4e42..a3e15ee2e8be6 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPageVulnerabilities.tsx @@ -40,6 +40,7 @@ import { getVulnStateScopedQueryString, parseQuerySearchFilter, } from '../../utils/searchUtils'; +import { Origin } from 'Components/CompoundSearchFilter/attributes/imageCVE'; import { imageCVESearchFilterConfig, imageComponentSearchFilterConfig, @@ -195,10 +196,13 @@ function DeploymentPageVulnerabilities({ // imageCVESearchFilterConfig, { ...imageCVESearchFilterConfig, - attributes: imageCVESearchFilterConfig.attributes.filter( - ({ searchTerm }) => - searchTerm !== 'EPSS Probability' || isEpssProbabilityColumnEnabled - ), + attributes: [ + ...imageCVESearchFilterConfig.attributes.filter( + ({ searchTerm }) => + searchTerm !== 'EPSS Probability' || isEpssProbabilityColumnEnabled + ), + Origin, // CVE origin filter is scoped to the single deployment page + ], }, imageSearchFilterConfig, imageComponentSearchFilterConfig, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx index 2b771dfcd6b0e..8fd7bf3743048 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx @@ -64,6 +64,7 @@ import type { ExceptionRequestModalProps } from '../../components/ExceptionReque import CompletedExceptionRequestModal from '../../components/ExceptionRequestModal/CompletedExceptionRequestModal'; import useExceptionRequestModal from '../../hooks/useExceptionRequestModal'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; +import { Origin } from 'Components/CompoundSearchFilter/attributes/imageCVE'; import { imageCVESearchFilterConfig, imageComponentSearchFilterConfig, @@ -256,10 +257,13 @@ function ImagePageVulnerabilities({ // imageCVESearchFilterConfig, { ...imageCVESearchFilterConfig, - attributes: imageCVESearchFilterConfig.attributes.filter( - ({ searchTerm }) => - searchTerm !== 'EPSS Probability' || isEpssProbabilityColumnEnabled - ), + attributes: [ + ...imageCVESearchFilterConfig.attributes.filter( + ({ searchTerm }) => + searchTerm !== 'EPSS Probability' || isEpssProbabilityColumnEnabled + ), + Origin, // CVE origin filter is scoped to the single image page + ], }, imageComponentSearchFilterConfig, ]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts index 9abd4f0fe1f7a..ed9b44571ceab 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts @@ -29,7 +29,7 @@ import { Name, } from 'Components/CompoundSearchFilter/attributes/deployment'; import { imageAttributes } from 'Components/CompoundSearchFilter/attributes/image'; -import { imageCVEAttributes } from 'Components/CompoundSearchFilter/attributes/imageCVE'; +import { Origin, imageCVEAttributes } from 'Components/CompoundSearchFilter/attributes/imageCVE'; import { imageComponentAttributes } from 'Components/CompoundSearchFilter/attributes/imageComponent'; import { Annotation as namespaceAnnotation, @@ -261,9 +261,12 @@ export const attributeForSeverityInBackendAndViewBasedReport: SelectSearchFilter export const searchFilterConfigForImageVulnerabilityReport = [ { ...imageCVESearchFilterConfig, - attributes: imageCVESearchFilterConfig.attributes.filter( - ({ searchTerm }) => searchTerm !== 'CVE Created Time' - ), + attributes: [ + ...imageCVESearchFilterConfig.attributes.filter( + ({ searchTerm }) => searchTerm !== 'CVE Created Time' + ), + Origin, // CVE origin filter is scoped to reports (plus single image/deployment pages) + ], }, imageSearchFilterConfig, imageComponentSearchFilterConfig, diff --git a/ui/apps/platform/src/types/searchOptions.ts b/ui/apps/platform/src/types/searchOptions.ts index 83665db4f75e4..f313890b07bb8 100644 --- a/ui/apps/platform/src/types/searchOptions.ts +++ b/ui/apps/platform/src/types/searchOptions.ts @@ -37,6 +37,7 @@ export const searchFieldLabels = [ 'CVE Published On', 'CVE Fix Available Timestamp', 'CVE Created Time', + 'CVE Origin', 'CVE Snoozed', 'CVE Snooze Expiry', 'CVSS', From 6c93ac50b8f232e93c96d69ddedcc99db020c1f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:28:53 +0200 Subject: [PATCH 134/638] chore(deps): bump dompurify from 3.4.8 to 3.4.13 in /ui/apps/platform (#22183) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/apps/platform/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/apps/platform/package-lock.json b/ui/apps/platform/package-lock.json index caf91135eb628..d04b665316236 100644 --- a/ui/apps/platform/package-lock.json +++ b/ui/apps/platform/package-lock.json @@ -8121,9 +8121,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz", - "integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" From 8909a081f26f3b34f9a1dc5aec46ca0aeb67dff6 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Mon, 31 Aug 2026 17:13:48 +0200 Subject: [PATCH 135/638] ROX-35685: wire mapping updaters into roxagent serve (#22142) --- compliance/virtualmachines/roxagent/README.md | 21 +- .../virtualmachines/roxagent/cmd/cmd.go | 2 - .../roxagent/cmd/mapping_downloader.go | 32 -- .../virtualmachines/roxagent/cmd/rescanner.go | 122 ++++-- .../roxagent/cmd/rescanner_test.go | 296 ++++++++++++--- .../virtualmachines/roxagent/cmd/serve.go | 103 +++-- .../roxagent/cmd/serve_test.go | 354 ++++++++++++++++++ .../roxagent/vsockserver/mapping.go | 3 +- .../roxagent/vsockserver/protocol.go | 80 +++- .../roxagent/vsockserver/protocol_test.go | 251 ++++++++++++- .../roxagent/vsockserver/sensor_updater.go | 17 +- .../roxagent/vsockserver/server_test.go | 6 +- .../roxagent/vsockserver/url_updater.go | 6 +- .../common/virtualmachine/metrics/metrics.go | 1 + .../virtualmachine/vmscraper/scraper.go | 4 + .../virtualmachine/vmscraper/scraper_test.go | 13 + .../virtualmachine/vsockclient/client.go | 4 + .../vsockclient/client_integration_test.go | 19 +- .../virtualmachine/vsockclient/client_test.go | 5 + 19 files changed, 1103 insertions(+), 236 deletions(-) delete mode 100644 compliance/virtualmachines/roxagent/cmd/mapping_downloader.go diff --git a/compliance/virtualmachines/roxagent/README.md b/compliance/virtualmachines/roxagent/README.md index 4574c8b91ad9d..4dafcb651f3dd 100644 --- a/compliance/virtualmachines/roxagent/README.md +++ b/compliance/virtualmachines/roxagent/README.md @@ -8,10 +8,10 @@ The sources live under `compliance/` because the agent reuses the Scanner V4 nod ## What it does -1. Fetches the repository-to-CPE mapping file (required before the first scan). -2. Scans the VM for installed packages and caches the index report with a content-hash token. -3. Listens on a VSOCK port with mandatory mTLS. -4. On each Sensor connection, handles a framed `VMServiceRequest` / `VMServiceResponse` (for example `get_report`). +1. Listens on a VSOCK port with mandatory mTLS. Startup does not wait for a mapping or a first scan. +2. Obtains a repository-to-CPE mapping (Sensor push over VSOCK by default, or a download URL). Until one is ready, GetReport returns `MAPPING_REQUIRED` and no scan runs. +3. Scans the VM for installed packages and caches the index report with a content-hash token. +4. On each Sensor connection, handles a framed `VMServiceRequest` / `VMServiceResponse` (for example `get_report` and `sync_repo_cpe_mapping`). 5. Periodically rescans and atomically swaps the cached report. ## Usage @@ -29,19 +29,20 @@ sudo ./roxagent serve --port 818 --host-path / --rescan-interval 4h |------|---------|-------| | `--port` | `818` | VSOCK listen port | | `--host-path` | `/` | Root filesystem path for package indexing | -| `--repo-cpe-url` | Red Hat security data URL | Repository-to-CPE mapping download URL | +| `--repo-cpe-url` | empty (Sensor-managed) | Mapping download URL. Empty means Sensor pushes the mapping over VSOCK. | +| `--repo-cpe-bundled-path` | empty | Optional seed mapping file for Sensor-managed agents. Ignored when `--repo-cpe-url` is set. | | `--rescan-interval` | `4h` | Must be between `5m` and `168h` (7d) | | `--ca-fetch-timeout` | `10s` | Timeout for one KubeVirt CA fetch over VSOCK | | `--conn-deadline` | `30s` | Max time for one connection's TLS handshake plus request/response (`5s`-`5m`) | ## How it works -1. **Mapping file:** On startup the agent downloads the repository-to-CPE mapping into a local cache and blocks until that succeeds. Every scan reads from that file. If a later refresh fails, the agent keeps the last good cache and continues scanning. +1. **Mapping file:** Every scan reads a local cache file kept fresh by a mapping provider. With `--repo-cpe-url` empty (the default), Sensor pushes the mapping over VSOCK via `sync_repo_cpe_mapping`. `--repo-cpe-bundled-path` is an optional seed for that Sensor-managed path when there is no cache yet. With a URL set, the agent downloads in the background and never accepts a Sensor push or a bundled seed: a URL that never succeeds stays not-ready rather than looking configured. A failed later URL refresh keeps the last-good cache from a previous successful fetch of that URL. - The default `--repo-cpe-url` is `https://security.access.redhat.com/data/metrics/repository-to-cpe.json`, so the VM needs outbound HTTPS to that host (or a proxy that can reach it). On isolated networks, host a copy of `repository-to-cpe.json` somewhere the VM can reach and point `--repo-cpe-url` at that URL. -2. **Initial scan:** Indexes `--host-path`, stores the report and discovered VM facts in an in-memory cache, then starts the VSOCK server and the rescan loop. -3. **Serving reports:** Sensor connects and the agent serves the cached report. If Sensor already has the current content-hash token, the agent omits the payload. Sensor can still force a full report by sending an empty token (first request, NACK retry, or the 4h refresh). -4. **Rescan:** On `--rescan-interval`, re-indexes the rpm/dnf database and swaps the cache. The token stays the same when the report and facts are unchanged. + A URL-backed agent needs outbound HTTPS to that host (or a proxy). Isolated networks either rely on Sensor (optionally with a bundled seed), or host a copy of `repository-to-cpe.json` the VM can reach and pass `--repo-cpe-url`. +2. **Listen first:** VSOCK comes up immediately. The first scan runs when a mapping is ready (cached from a previous run, a bundled seed, a successful URL fetch, or a Sensor push), not at process start. +3. **Serving reports:** Sensor connects and the agent serves the cached report. If Sensor already has the current content-hash token, the agent omits the payload. Sensor can still force a full report by sending an empty token (first request, NACK retry, or the 4h refresh). With no mapping yet, the response is `MAPPING_REQUIRED`. +4. **Rescan:** On `--rescan-interval`, or sooner when the mapping changes, re-indexes the rpm/dnf database and swaps the cache. The token stays the same when the report and facts are unchanged. ### TLS (mandatory) diff --git a/compliance/virtualmachines/roxagent/cmd/cmd.go b/compliance/virtualmachines/roxagent/cmd/cmd.go index 63b598d014e80..ded5aa003db5e 100644 --- a/compliance/virtualmachines/roxagent/cmd/cmd.go +++ b/compliance/virtualmachines/roxagent/cmd/cmd.go @@ -9,8 +9,6 @@ import ( var log = logging.LoggerForModule() -const repoToCPEMappingURL = "https://security.access.redhat.com/data/metrics/repository-to-cpe.json" - func RootCmd(ctx context.Context) *cobra.Command { cmd := cobra.Command{ Use: "agent", diff --git a/compliance/virtualmachines/roxagent/cmd/mapping_downloader.go b/compliance/virtualmachines/roxagent/cmd/mapping_downloader.go deleted file mode 100644 index 08c978327464c..0000000000000 --- a/compliance/virtualmachines/roxagent/cmd/mapping_downloader.go +++ /dev/null @@ -1,32 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/stackrox/rox/pkg/filedownloader" -) - -const ( - mappingFetchRetryMax = 3 // RetryMax: 1 initial + 3 retries = 4 attempts. - mappingFetchRetryWaitMin = 5 * time.Second - // Must cover retryablehttp backoff plus request time. With WaitMin=5s and - // retryablehttp's default WaitMax=30s, waits are 5+10+20=35s; leave - // headroom for four HTTP attempts on a multi-MB mapping file. - mappingClientTimeout = 2 * time.Minute - mappingRefreshInterval = time.Hour -) - -// newMappingDownloader builds the filedownloader.Downloader that keeps the -// repository-to-CPE mapping file at cachePath fresh. scan() only reads the -// local file, so a flaky mapping endpoint never blocks a scan directly. -// onComplete is invoked after every fetch attempt (the mandatory initial -// one and every later periodic refresh); the caller decides what that -// means for logging. -func newMappingDownloader(url, cachePath string, onComplete func(err error, duration time.Duration)) *filedownloader.Downloader { - return filedownloader.New(url, cachePath, mappingRefreshInterval, - filedownloader.WithRequestTimeout(mappingClientTimeout), - filedownloader.WithRetryMax(mappingFetchRetryMax), - filedownloader.WithRetryWaitMin(mappingFetchRetryWaitMin), - filedownloader.WithOnComplete(onComplete), - ) -} diff --git a/compliance/virtualmachines/roxagent/cmd/rescanner.go b/compliance/virtualmachines/roxagent/cmd/rescanner.go index e4597a312f4aa..98af9a7281b77 100644 --- a/compliance/virtualmachines/roxagent/cmd/rescanner.go +++ b/compliance/virtualmachines/roxagent/cmd/rescanner.go @@ -2,6 +2,8 @@ package cmd import ( "context" + "fmt" + "sync/atomic" "time" "github.com/stackrox/rox/compliance/virtualmachines/roxagent/vsockserver" @@ -20,39 +22,54 @@ const ( // the VM filesystem and publishing results to cache, independent of how the // cached report is served over VSOCK. Kept separate from Server/CARefresher // wiring in runServe so its retry/backoff policy is easier to reason about -// and test on its own. Scanning does not make a network call; the downloader -// built by newMappingDownloader keeps the mapping file fresh independently, -// on its own schedule, so only the schedule for the next rescan attempt is -// retried here, not the scan itself. +// and test on its own. Scanning does not make a network call; provider +// keeps the mapping file fresh independently, on its own schedule, so only +// the schedule for the next rescan attempt is retried here, not the scan +// itself. type rescanner struct { - cache *vsockserver.ReportCache - hostPath string - mappingFilePath string - interval time.Duration + cache *vsockserver.ReportCache + hostPath string + provider vsockserver.MappingProvider + interval time.Duration // scanFn defaults to the package scan function; tests override it to // inject failures. factsFn defaults to the package discoverFacts // function; tests override it to avoid exercising the real // filesystem, since discoverFacts otherwise reads real host paths // (e.g. hostPath="" resolves to "/etc/pki/entitlement" et al., not a - // no-op). newDelay defaults to time.After (a one-shot timer); tests - // substitute a function returning a manually driven channel for - // precise control over Run's loop. - scanFn func(ctx context.Context, hostPath, mappingFilePath string) (*v4.IndexReport, error) - factsFn func(hostPath string) map[string]string - newDelay func(d time.Duration) <-chan time.Time + // no-op). + scanFn func(ctx context.Context, hostPath, mappingFilePath string) (*v4.IndexReport, error) + factsFn func(hostPath string) map[string]string + + // timer is the next scan deadline. OnMappingChanged Reset(0) fires it + // immediately; a Reset(0) during scanOnce is received on the next + // select because t.C is unbuffered (Go 1.23+). Nil after Run returns + // so a later Reset is a no-op. + timer atomic.Pointer[time.Timer] +} + +func newRescanner(cache *vsockserver.ReportCache, hostPath string, provider vsockserver.MappingProvider, interval time.Duration) *rescanner { + r := &rescanner{ + cache: cache, + hostPath: hostPath, + provider: provider, + interval: interval, + scanFn: scan, + factsFn: discoverFacts, + } + r.timer.Store(time.NewTimer(interval)) + return r } -func newRescanner(cache *vsockserver.ReportCache, hostPath, mappingFilePath string, interval time.Duration) *rescanner { - return &rescanner{ - cache: cache, - hostPath: hostPath, - mappingFilePath: mappingFilePath, - interval: interval, - scanFn: scan, - factsFn: discoverFacts, - newDelay: time.After, +// OnMappingChanged is the updater's onChange callback: it schedules an +// immediate scan attempt. +func (r *rescanner) OnMappingChanged() { + t := r.timer.Load() + if t == nil { + return } + t.Reset(0) + log.Info("Mapping changed, scheduling immediate rescan") } // Run rescans every r.interval, publishing each successful result to cache. @@ -61,33 +78,70 @@ func newRescanner(cache *vsockserver.ReportCache, hostPath, mappingFilePath stri // and r.interval itself, so a retry is never slower than just waiting for // the next scheduled rescan would be. Blocks until ctx is cancelled. func (r *rescanner) Run(ctx context.Context) { + t := r.timer.Load() + defer func() { + r.timer.Store(nil) + t.Stop() + }() + backoff := rescanRetryBaseBackoff - delay := r.newDelay(r.interval) for { select { case <-ctx.Done(): return - case <-delay: - log.Info("Starting rescan") - report, err := r.scanFn(ctx, r.hostPath, r.mappingFilePath) - if err != nil { + case <-t.C: + // Schedule the default next scan before scanOnce so a + // mapping change during the scan (Reset(0)) is not + // overwritten by the post-scan Reset. + t.Reset(r.interval) + if err := r.scanOnce(ctx); err != nil { retryIn := min(backoff, r.interval) log.Errorf("Rescan failed: %v; trying again in %v", err, retryIn) backoff = min(backoff*2, rescanRetryMaxBackoff) - delay = r.newDelay(retryIn) + t.Reset(retryIn) continue } - r.cache.SetReport(report, r.factsFn(r.hostPath)) - log.Infof("Rescan complete, report updated. Num packages: %d", len(report.GetContents().GetPackages())) backoff = rescanRetryBaseBackoff - delay = r.newDelay(r.interval) } } } +// scanOnce keeps the mapping file unchanged while this scan reads it, then +// applies any pending Sync so a follow-up scan can use the new mapping. +func (r *rescanner) scanOnce(ctx context.Context) error { + if !r.provider.Ready() { + log.Info("Skipping rescan: repository-to-CPE mapping not yet available") + return nil + } + + gate, hasGate := r.provider.(vsockserver.ScanBusyGate) + if hasGate { + gate.MarkScanBusy() + defer gate.MarkScanIdleAndApplyPending() + } + + path, err := r.provider.Path() + if err != nil { + return fmt.Errorf("resolving mapping file path: %w", err) + } + + // Snapshot the hash before scanFn: a URL-backed provider has no busy + // gate, so a background fetch can replace the live mapping mid-scan. + mappingHash := r.provider.Hash() + log.Infof("Starting rescan (mapping hash=%s)", mappingHash) + report, err := r.scanFn(ctx, r.hostPath, path) + if err != nil { + return err + } + + r.cache.SetReport(report, r.factsFn(r.hostPath), mappingHash) + log.Infof("Rescan complete, report updated (token=%s, packages=%d)", r.cache.Token(), len(report.GetContents().GetPackages())) + return nil +} + // runAsync starts Run in a goroutine and returns a channel that is closed -// when Run returns. Callers that cancel ctx should wait on stopped before -// tearing down anything Run still observes (e.g. an injected tick channel). +// when Run returns. Callers that cancel ctx should wait on stopped so +// assertions do not race Run's last scanOnce. func (r *rescanner) runAsync(ctx context.Context) (stopped <-chan struct{}) { return startRun(ctx, r.Run) } diff --git a/compliance/virtualmachines/roxagent/cmd/rescanner_test.go b/compliance/virtualmachines/roxagent/cmd/rescanner_test.go index f65b28ead8f0e..4839d828e207f 100644 --- a/compliance/virtualmachines/roxagent/cmd/rescanner_test.go +++ b/compliance/virtualmachines/roxagent/cmd/rescanner_test.go @@ -3,73 +3,79 @@ package cmd import ( "context" "errors" + "net" "testing" "testing/synctest" "time" "github.com/stackrox/rox/compliance/virtualmachines/roxagent/vsockserver" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/vsockframing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) -// testRescanner returns a rescanner with a long default interval so tests -// that don't care about the periodic loop never trigger it by accident. -// factsFn is stubbed out so Run never exercises the real discoverFacts, -// which - unlike scanFn - has no test-friendly no-op input; hostPath="" -// resolves to real absolute host paths (e.g. "/etc/pki/entitlement"), not -// a safe default. -func testRescanner() *rescanner { - r := newRescanner(&vsockserver.ReportCache{}, "", "", time.Hour) - r.factsFn = func(string) map[string]string { return nil } - return r -} - -// fakeTicker is a newDelay func driven manually by a test: fire triggers a -// delay expiry, and lastReset reports the duration most recently requested via -// newDelay, letting tests assert on scheduling decisions directly instead of -// on elapsed time. Pair with synctest.Wait after fire to block until the -// loop under test has processed the tick and settled back into waiting for -// the next one. -type fakeTicker struct { - tick chan time.Time - - mu sync.Mutex - resets []time.Duration +// fakeProvider is a vsockserver.MappingProvider test double for rescanner +// tests; UpdatePath/Bytes are unused by rescanner and always zero. +type fakeProvider struct { + ready bool + path string + pathErr error + hash string } -func newFakeTicker() *fakeTicker { - return &fakeTicker{tick: make(chan time.Time, 1)} +func (f *fakeProvider) Ready() bool { return f.ready } +func (f *fakeProvider) Hash() string { return f.hash } +func (f *fakeProvider) UpdatePath() pb.RepoCPEMappingUpdatePath { + return pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED } +func (f *fakeProvider) Bytes() ([]byte, error) { return nil, errors.New("not implemented") } +func (f *fakeProvider) Path() (string, error) { return f.path, f.pathErr } -func (f *fakeTicker) close() { close(f.tick) } +var _ vsockserver.MappingProvider = (*fakeProvider)(nil) -// newDelay has the same signature as time.After, so it's directly assignable -// to a rescanner's newDelay field. -func (f *fakeTicker) newDelay(d time.Duration) <-chan time.Time { - concurrency.WithLock(&f.mu, func() { f.resets = append(f.resets, d) }) - return f.tick +// fakeGatedProvider adds ScanBusyGate call tracking on top of fakeProvider, +// mirroring SensorUpdater (gated) as opposed to URLUpdater (ungated) so +// tests can verify scanOnce only invokes the gate for providers that +// implement it. +type fakeGatedProvider struct { + fakeProvider + busyCalls int + idleCalls int } -func (f *fakeTicker) fire() { f.tick <- time.Time{} } +func (f *fakeGatedProvider) MarkScanBusy() { f.busyCalls++ } +func (f *fakeGatedProvider) MarkScanIdleAndApplyPending() { f.idleCalls++ } -func (f *fakeTicker) lastReset() time.Duration { - return concurrency.WithLock1(&f.mu, func() time.Duration { - if len(f.resets) == 0 { - return 0 - } - return f.resets[len(f.resets)-1] - }) +var ( + _ vsockserver.MappingProvider = (*fakeGatedProvider)(nil) + _ vsockserver.ScanBusyGate = (*fakeGatedProvider)(nil) +) + +// testRescanner returns a rescanner with a long default interval so tests +// that don't care about the periodic loop never trigger it by accident. +// factsFn is stubbed out so Run never exercises the real discoverFacts, +// which - unlike scanFn - has no test-friendly no-op input; hostPath="" +// resolves to real absolute host paths (e.g. "/etc/pki/entitlement"), not +// a safe default. provider defaults to always-ready when nil, so existing +// tests that don't care about readiness are unaffected. +func testRescanner(provider vsockserver.MappingProvider) *rescanner { + if provider == nil { + provider = &fakeProvider{ready: true} + } + r := newRescanner(&vsockserver.ReportCache{}, "", provider, time.Hour) + r.factsFn = func(string) map[string]string { return nil } + return r } func TestRescanner_Run(t *testing.T) { t.Run("should publish to cache on each tick", func(t *testing.T) { synctest.Test(t, func(t *testing.T) { - r := testRescanner() - ticker := newFakeTicker() - defer ticker.close() - r.newDelay = ticker.newDelay + r := testRescanner(nil) var mu sync.Mutex var calls int r.scanFn = func(_ context.Context, _, _ string) (*v4.IndexReport, error) { @@ -81,19 +87,17 @@ func TestRescanner_Run(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) stopped := r.runAsync(ctx) - // Stop Run before close(tick): a closed tick chan would make - // select fire continuously with zero values. defer func() { cancel() <-stopped }() - synctest.Wait() // Run is blocked waiting for the first tick + synctest.Wait() - ticker.fire() + time.Sleep(r.interval) synctest.Wait() assert.Equal(t, 1, concurrency.WithLock1(&mu, func() int { return calls })) - ticker.fire() + time.Sleep(r.interval) synctest.Wait() assert.Equal(t, 2, concurrency.WithLock1(&mu, func() int { return calls }), "should rescan again on the next tick") }) @@ -101,10 +105,7 @@ func TestRescanner_Run(t *testing.T) { t.Run("should retry sooner than the full interval after a failed rescan", func(t *testing.T) { synctest.Test(t, func(t *testing.T) { - r := testRescanner() - ticker := newFakeTicker() - defer ticker.close() - r.newDelay = ticker.newDelay + r := testRescanner(nil) var mu sync.Mutex var calls int r.scanFn = func(_ context.Context, _, _ string) (*v4.IndexReport, error) { @@ -123,26 +124,25 @@ func TestRescanner_Run(t *testing.T) { cancel() <-stopped }() - synctest.Wait() // Run is blocked waiting for the first tick - - ticker.fire() synctest.Wait() - assert.Equal(t, rescanRetryBaseBackoff, ticker.lastReset(), - "a failed rescan should be rescheduled after rescanRetryBaseBackoff, not r.interval") + time.Sleep(r.interval) + synctest.Wait() assert.Equal(t, 1, concurrency.WithLock1(&mu, func() int { return calls })) - ticker.fire() // the rescheduled retry firing + time.Sleep(rescanRetryBaseBackoff) synctest.Wait() - assert.Equal(t, 2, concurrency.WithLock1(&mu, func() int { return calls }), "the failed rescan was never retried") - assert.Equal(t, r.interval, ticker.lastReset(), "a successful rescan should reschedule after the full interval, resetting backoff") + + time.Sleep(rescanRetryBaseBackoff) + synctest.Wait() + assert.Equal(t, 2, concurrency.WithLock1(&mu, func() int { return calls }), "a successful rescan should wait the full interval, not keep the retry backoff") }) }) t.Run("should stop promptly when the context is cancelled", func(t *testing.T) { synctest.Test(t, func(t *testing.T) { - r := testRescanner() + r := testRescanner(nil) ctx, cancel := context.WithCancel(t.Context()) stopped := r.runAsync(ctx) @@ -153,6 +153,180 @@ func TestRescanner_Run(t *testing.T) { // fails the test on deadlock automatically. cancel() <-stopped + // Updaters can still fire onChange after Run has returned. + r.OnMappingChanged() }) }) + + t.Run("a periodic tick is a no-op when the mapping is not yet ready", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + r := testRescanner(&fakeProvider{ready: false}) + var calls int + r.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + calls++ + return &v4.IndexReport{}, nil + } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := r.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + synctest.Wait() + + time.Sleep(r.interval) + synctest.Wait() + + assert.Zero(t, calls, "scanFn must not be called while the mapping provider isn't ready") + }) + }) + + t.Run("OnMappingChanged triggers an immediate scan attempt", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + r := testRescanner(&fakeProvider{ready: true}) + var calls int + r.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + calls++ + return &v4.IndexReport{}, nil + } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := r.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + synctest.Wait() + + r.OnMappingChanged() + synctest.Wait() + + assert.Equal(t, 1, calls, "OnMappingChanged should trigger a scan without waiting for the next tick") + }) + }) + + t.Run("OnMappingChanged failure retries at backoff not the full interval", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + r := testRescanner(nil) + var mu sync.Mutex + var calls int + r.scanFn = func(_ context.Context, _, _ string) (*v4.IndexReport, error) { + return concurrency.WithLock2(&mu, func() (*v4.IndexReport, error) { + calls++ + if calls == 1 { + return nil, errors.New("transient scan error") + } + return &v4.IndexReport{HashId: "ok"}, nil + }) + } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := r.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + synctest.Wait() + + r.OnMappingChanged() + synctest.Wait() + assert.Equal(t, 1, concurrency.WithLock1(&mu, func() int { return calls }), "OnMappingChanged should trigger a scan immediately") + + time.Sleep(rescanRetryBaseBackoff) + synctest.Wait() + assert.Equal(t, 2, concurrency.WithLock1(&mu, func() int { return calls }), "a failed mapping-triggered scan should retry after the backoff") + + time.Sleep(rescanRetryBaseBackoff) + synctest.Wait() + assert.Equal(t, 2, concurrency.WithLock1(&mu, func() int { return calls }), "a successful retry should wait the full interval") + }) + }) + + t.Run("a failed scan clears busy via idle-and-apply-pending", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + provider := &fakeGatedProvider{fakeProvider: fakeProvider{ready: true}} + r := testRescanner(provider) + r.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + return nil, errors.New("scan failed") + } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := r.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + synctest.Wait() + + time.Sleep(r.interval) + synctest.Wait() + + assert.Equal(t, 1, provider.busyCalls) + assert.Equal(t, 1, provider.idleCalls, "a failed scan must not leave a Sync stuck behind busy forever") + }) + }) + + t.Run("a successful scan clears busy without waiting for GetReport", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + provider := &fakeGatedProvider{fakeProvider: fakeProvider{ready: true}} + r := testRescanner(provider) + r.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + return &v4.IndexReport{HashId: "ok"}, nil + } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := r.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + synctest.Wait() + + time.Sleep(r.interval) + synctest.Wait() + + assert.Equal(t, 1, provider.busyCalls) + assert.Equal(t, 1, provider.idleCalls, "a successful scan must apply a deferred Sync itself; GetReport can be hours away") + }) + }) +} + +// TestScanOnce_StoresHashFromBeforeTheScan covers a URL refresh that +// replaces the live mapping while scanFn runs: SetReport must keep the +// hash from before the scan. +func TestScanOnce_StoresHashFromBeforeTheScan(t *testing.T) { + const hashAtScan = "hash-at-scan-start" + provider := &fakeProvider{ready: true, hash: hashAtScan} + r := testRescanner(provider) + r.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + provider.hash = "hash-after-url-refresh" + return &v4.IndexReport{HashId: "ok"}, nil + } + + require.NoError(t, r.scanOnce(t.Context())) + assert.Equal(t, "hash-after-url-refresh", provider.Hash(), "the live mapping may have moved on") + assert.Equal(t, hashAtScan, mappingHashFromCache(t, r.cache, provider)) +} + +func mappingHashFromCache(t *testing.T, cache *vsockserver.ReportCache, provider vsockserver.MappingProvider) string { + t.Helper() + handler := vsockserver.NewHandler(cache, "test", provider, nil) + clientConn, serverConn := net.Pipe() + go handler.HandleConn(serverConn) + + req, err := proto.Marshal(&pb.VMServiceRequest{ + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + }) + require.NoError(t, err) + require.NoError(t, vsockframing.WriteFrame(clientConn, req)) + + respData, err := vsockframing.ReadFrame(clientConn, 10<<20) + require.NoError(t, err) + _ = clientConn.Close() + + var resp pb.VMServiceResponse + require.NoError(t, proto.Unmarshal(respData, &resp)) + require.NotNil(t, resp.GetGetReport()) + return resp.GetMeta().GetRepoCpeMappingHash() } diff --git a/compliance/virtualmachines/roxagent/cmd/serve.go b/compliance/virtualmachines/roxagent/cmd/serve.go index 8fd852b87f07d..9921bd5221612 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve.go +++ b/compliance/virtualmachines/roxagent/cmd/serve.go @@ -66,7 +66,10 @@ func ServeCmd(ctx context.Context) *cobra.Command { } cmd.Flags().Uint32Var(&cfg.port, "port", 818, "VSOCK port to listen on") cmd.Flags().StringVar(&cfg.hostPath, "host-path", "/", "Root filesystem path for indexing") - cmd.Flags().StringVar(&cfg.repoCPEURL, "repo-cpe-url", repoToCPEMappingURL, "Repository to CPE mapping URL") + cmd.Flags().StringVar(&cfg.repoCPEURL, "repo-cpe-url", "", + "Repository to CPE mapping URL. Empty (default) means Sensor pushes the mapping over VSOCK instead.") + cmd.Flags().StringVar(&cfg.repoCPEBundledPath, "repo-cpe-bundled-path", "", + "Optional seed mapping file for Sensor-managed agents. Ignored when --repo-cpe-url is set.") cmd.Flags().DurationVar(&cfg.rescanInterval, "rescan-interval", 4*time.Hour, fmt.Sprintf("Interval between rescans (range %v-%v)", minRescanInterval, maxRescanInterval)) cmd.Flags().DurationVar(&cfg.caFetchTimeout, "ca-fetch-timeout", 10*time.Second, @@ -82,12 +85,13 @@ func ServeCmd(ctx context.Context) *cobra.Command { // serveConfig holds runServe's inputs, validated together by validate. type serveConfig struct { - port uint32 - hostPath string - repoCPEURL string - rescanInterval time.Duration - caFetchTimeout time.Duration - connDeadline time.Duration + port uint32 + hostPath string + repoCPEURL string + repoCPEBundledPath string + rescanInterval time.Duration + caFetchTimeout time.Duration + connDeadline time.Duration } func (c serveConfig) validate() error { @@ -103,18 +107,38 @@ func (c serveConfig) validate() error { return nil } +// newRescannerAndProvider builds the rescanner and its mapping provider. +// OnMappingChanged does not read provider, so passing it to the +// provider's constructor before provider is assigned below is safe. +func newRescannerAndProvider(cache *vsockserver.ReportCache, cfg serveConfig) (vmRescanner *rescanner, provider vsockserver.MappingProvider, updater vsockserver.MappingUpdater, urlUpdater *vsockserver.URLUpdater) { + vmRescanner = newRescanner(cache, cfg.hostPath, nil, cfg.rescanInterval) + + if cfg.repoCPEURL != "" { + urlUpdater = vsockserver.NewURLUpdater(cfg.repoCPEURL, mappingCachePath, vmRescanner.OnMappingChanged) + provider = urlUpdater + } else { + sensorUpdater := vsockserver.NewSensorUpdater(mappingCachePath, cfg.repoCPEBundledPath, vmRescanner.OnMappingChanged) + provider, updater = sensorUpdater, sensorUpdater + } + vmRescanner.provider = provider + + return vmRescanner, provider, updater, urlUpdater +} + func runServe(ctx context.Context, cfg serveConfig) error { if err := cfg.validate(); err != nil { return err } - // serveCtx lets early-return paths (mapping/scan failure after listen) + // serveCtx lets early-return paths (systemd notify failure after listen) // stop Serve instead of leaking the accept loop until process exit. serveCtx, cancelServe := context.WithCancel(ctx) defer cancelServe() cache := &vsockserver.ReportCache{} - handler := vsockserver.NewHandler(cache, agentVersion) + vmRescanner, provider, updater, urlUpdater := newRescannerAndProvider(cache, cfg) + + handler := vsockserver.NewHandler(cache, agentVersion, provider, updater) // TLS is mandatory: sensor always dials with TLS, so a plaintext agent is // unreachable. The KubeVirt CA (served by virt-handler on CID 2, port 1) @@ -139,71 +163,42 @@ func runServe(ctx context.Context, cfg serveConfig) error { } log.Infof("Listening on VSOCK port %d (pull mode)", cfg.port) + if urlUpdater != nil { + if err := urlUpdater.Start(serveCtx); err != nil { + return fmt.Errorf("starting repository-to-CPE mapping downloader: %w", err) + } + } + var wg sync.WaitGroup wg.Go(func() { srv.Serve(serveCtx, ln) }) + wg.Go(func() { vmRescanner.Run(serveCtx) }) // Report systemd readiness once VSOCK is up. if err := notifySystemdReady(); err != nil { // Exiting, as systemd would kill this service anyway after TimeoutStartSec. cancelServe() wg.Wait() + if urlUpdater != nil { + urlUpdater.Stop() + } return err } - // The mapping file must exist locally before the first scan can run: - // scan() never fetches it itself, so this initial fetch is mandatory. - // If it fails, startup fails rather than running a scan against no data. - // Start(serveCtx, true) blocks until that fetch completes, then keeps - // refreshing the file in the background. - mappingDownloader := newMappingDownloader(cfg.repoCPEURL, mappingCachePath, - logMappingDownloadResult(cfg.repoCPEURL, mappingCachePath)) - if err := mappingDownloader.Start(serveCtx, true); err != nil { - cancelServe() - wg.Wait() - return fmt.Errorf("initial repository-to-CPE mapping fetch: %w", err) - } - - report, err := scan(serveCtx, cfg.hostPath, mappingCachePath) - if err != nil { - cancelServe() - wg.Wait() - mappingDownloader.Stop() - return fmt.Errorf("initial scan: %w", err) - } - cache.SetReport(report, discoverFacts(cfg.hostPath)) - log.Infof("Initial scan complete, report cached. Num packages: %d", len(report.GetContents().GetPackages())) - - vmRescanner := newRescanner(cache, cfg.hostPath, mappingCachePath, cfg.rescanInterval) - wg.Go(func() { vmRescanner.Run(serveCtx) }) - <-serveCtx.Done() // Wait for Serve's graceful drain (in-flight connections) and the // rescan loop to finish before returning, so the process doesn't exit - // mid-drain or mid-scan. mappingDownloader.Stop waits for its own - // background refresh loop the same way, once serveCtx is already done. + // mid-drain or mid-scan. urlUpdater.Stop waits for its own background + // refresh loop the same way, once serveCtx is already done. wg.Wait() - mappingDownloader.Stop() - return nil -} - -// logMappingDownloadResult logs a repository-to-CPE mapping download outcome and status of the local mapping file. -func logMappingDownloadResult(url, cachePath string) func(err error, duration time.Duration) { - return func(err error, duration time.Duration) { - if err == nil { - log.Infof("Repository-to-CPE mapping file downloaded from %s in %s", url, duration) - return - } - if _, statErr := os.Stat(cachePath); statErr == nil { - log.Warnf("Repository-to-CPE mapping download failed after %s, scans keep using the previously cached file: %v", duration, err) - return - } - log.Errorf("Repository-to-CPE mapping download failed after %s, no cached file available: %v", duration, err) + if urlUpdater != nil { + urlUpdater.Stop() } + return nil } // scan indexes the VM filesystem at hostPath, consulting the // repository-to-CPE mapping data cached at mappingFilePath. -// The mapping downloader keeps mappingFilePath fresh independently. +// The mapping provider keeps mappingFilePath fresh independently. func scan(ctx context.Context, hostPath, mappingFilePath string) (*v4.IndexReport, error) { cfg := index.NodeIndexerConfig{ HostPath: hostPath, diff --git a/compliance/virtualmachines/roxagent/cmd/serve_test.go b/compliance/virtualmachines/roxagent/cmd/serve_test.go index 3a56e35a4bbdf..12f8e4c720de2 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve_test.go +++ b/compliance/virtualmachines/roxagent/cmd/serve_test.go @@ -1,14 +1,368 @@ package cmd import ( + "context" "crypto/x509" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sync/atomic" "testing" "time" + "github.com/stackrox/rox/compliance/virtualmachines/roxagent/vsockserver" + v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// validMappingJSON is the minimal content cpemapping.ValidateMapping accepts. +// otherValidMappingJSON is a distinct valid payload for tests that need two +// different hashes (e.g. deferred-apply across a simulated scan). +const ( + validMappingJSON = `{"data":{}}` + otherValidMappingJSON = `{"data":{"rhel-9-server-rpms":{"cpes":["cpe:/o:redhat:enterprise_linux:9"]}}}` +) + +// waitTimeout/waitTick bound assert.Eventually polling for background +// effects of a real (non-fake-clock) mapping update, e.g. onChange firing. +const ( + waitTimeout = 2 * time.Second + waitTick = 10 * time.Millisecond +) + +// withMappingCachePath points the package-level mappingCachePath at a fresh +// file under a per-test temp dir, so tests never share state through it or +// touch the real hardcoded path, and restores it afterward. +func withMappingCachePath(t *testing.T) string { + t.Helper() + original := mappingCachePath + path := filepath.Join(t.TempDir(), "cache.json") + mappingCachePath = path + t.Cleanup(func() { mappingCachePath = original }) + return path +} + +func TestNewRescannerAndProvider_URLSelection(t *testing.T) { + withMappingCachePath(t) + + tests := map[string]struct { + repoCPEURL string + wantUpdatePath pb.RepoCPEMappingUpdatePath + wantUpdaterNil bool + wantURLUpdater bool + }{ + "empty repo-cpe-url selects the Sensor-managed path": { + repoCPEURL: "", + wantUpdatePath: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, + wantUpdaterNil: false, + wantURLUpdater: false, + }, + "non-empty repo-cpe-url selects the URL-managed path": { + repoCPEURL: "https://example.invalid/repo-to-cpe.json", + wantUpdatePath: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, + wantUpdaterNil: true, + wantURLUpdater: true, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + cfg := serveConfig{repoCPEURL: tt.repoCPEURL} + _, provider, updater, urlUpdater := newRescannerAndProvider(&vsockserver.ReportCache{}, cfg) + + assert.Equal(t, tt.wantUpdatePath, provider.UpdatePath()) + assert.Equal(t, tt.wantUpdaterNil, updater == nil) + assert.Equal(t, tt.wantURLUpdater, urlUpdater != nil) + }) + } +} + +// TestNewRescannerAndProvider_NoMappingStaysIdle covers agent startup with +// no mapping file present yet: the provider must report not-Ready instead +// of treating a missing file as a fetch error, so runServe never attempts +// a scan before a mapping actually arrives. +func TestNewRescannerAndProvider_NoMappingStaysIdle(t *testing.T) { + for name, repoCPEURL := range map[string]string{"Sensor-managed": "", "URL-managed": "https://example.invalid/repo-to-cpe.json"} { + t.Run(name, func(t *testing.T) { + withMappingCachePath(t) + vmRescanner, provider, _, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{repoCPEURL: repoCPEURL}) + + assert.False(t, provider.Ready(), "no cached mapping file must leave the provider not-Ready, not erroring") + + var scanCalls int + vmRescanner.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + scanCalls++ + return &v4.IndexReport{}, nil + } + require.NoError(t, vmRescanner.scanOnce(t.Context())) + assert.Zero(t, scanCalls, "a not-Ready provider must never trigger a disk scan") + }) + } +} + +// TestNewRescannerAndProvider_CallbackWiredAfterConstruction pins down the +// construction order: the rescanner's provider field is only assigned +// after the provider itself is built, but OnMappingChanged - already bound +// to the rescanner - must be safe to call throughout, and the assignment +// must have actually taken effect by the time construction returns. +func TestNewRescannerAndProvider_CallbackWiredAfterConstruction(t *testing.T) { + withMappingCachePath(t) + vmRescanner, provider, _, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{}) + + assert.Same(t, provider, vmRescanner.provider, "the rescanner must observe the same provider instance constructed alongside it") + assert.NotPanics(t, vmRescanner.OnMappingChanged) +} + +// TestNewRescannerAndProvider_SyncKicksFirstScan simulates a Sensor-pushed +// mapping arriving (without any real VSOCK connection) and checks it +// triggers an immediate scan attempt via the onChange wiring. +// Runs on the real clock (not synctest): SensorUpdater.Update's fire-and- +// forget cache-persistence goroutine is real background I/O outside the +// rescanner's own fake-clock-driven loop. +func TestNewRescannerAndProvider_SyncKicksFirstScan(t *testing.T) { + cachePath := withMappingCachePath(t) + vmRescanner, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{rescanInterval: time.Hour}) + require.False(t, provider.Ready()) + require.NotNil(t, updater, "Sensor path must produce a non-nil updater") + + var mu sync.Mutex + var calls int + vmRescanner.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + concurrency.WithLock(&mu, func() { calls++ }) + return &v4.IndexReport{}, nil + } + vmRescanner.factsFn = func(string) map[string]string { return nil } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := vmRescanner.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + + updated, err := updater.Update([]byte(validMappingJSON)) + require.NoError(t, err) + require.True(t, updated) + + assert.Eventually(t, func() bool { + return concurrency.WithLock1(&mu, func() int { return calls }) == 1 + }, waitTimeout, waitTick, "a Sync-style mapping update should kick an immediate scan attempt") + + // SensorUpdater.Update persists to disk via a fire-and-forget goroutine + // (see vsockserver.SensorUpdater.persistAndNotify), so the on-disk + // content lands independently of, and possibly after, the scan kick + // asserted above. + assert.Eventually(t, func() bool { + content, err := os.ReadFile(cachePath) + return err == nil && string(content) == validMappingJSON + }, waitTimeout, waitTick, "a synced mapping should also land in the on-disk cache") +} + +// TestNewRescannerAndProvider_BundledPathSeedsSensor covers cmd passing +// --repo-cpe-bundled-path through to NewSensorUpdater: a seed file is +// enough to become Ready and trigger the first scan, without a Sensor push. +func TestNewRescannerAndProvider_BundledPathSeedsSensor(t *testing.T) { + withMappingCachePath(t) + bundledPath := filepath.Join(t.TempDir(), "bundled.json") + require.NoError(t, os.WriteFile(bundledPath, []byte(validMappingJSON), 0o600)) + + vmRescanner, provider, updater, urlUpdater := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{ + repoCPEBundledPath: bundledPath, + rescanInterval: time.Hour, + }) + require.True(t, provider.Ready(), "a Sensor-managed seed file must bootstrap the provider Ready") + require.NotNil(t, updater) + require.Nil(t, urlUpdater) + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), provider.Hash()) + + var mu sync.Mutex + var calls int + vmRescanner.scanFn = func(context.Context, string, string) (*v4.IndexReport, error) { + concurrency.WithLock(&mu, func() { calls++ }) + return &v4.IndexReport{}, nil + } + vmRescanner.factsFn = func(string) map[string]string { return nil } + + ctx, cancel := context.WithCancel(t.Context()) + stopped := vmRescanner.runAsync(ctx) + defer func() { + cancel() + <-stopped + }() + + assert.Eventually(t, func() bool { + return concurrency.WithLock1(&mu, func() int { return calls }) == 1 + }, waitTimeout, waitTick, "bootstrapping from the bundled seed should kick an immediate scan attempt") +} + +// TestNewRescannerAndProvider_URLIgnoresBundledPath covers URL-managed +// plus --repo-cpe-bundled-path: the seed is Sensor-only, so a URL that +// has never succeeded stays not-ready instead of looking configured. +func TestNewRescannerAndProvider_URLIgnoresBundledPath(t *testing.T) { + withMappingCachePath(t) + bundledPath := filepath.Join(t.TempDir(), "bundled.json") + require.NoError(t, os.WriteFile(bundledPath, []byte(validMappingJSON), 0o600)) + + _, provider, updater, urlUpdater := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{ + repoCPEURL: "https://example.invalid/repo-to-cpe.json", + repoCPEBundledPath: bundledPath, + }) + require.NotNil(t, urlUpdater) + require.Nil(t, updater) + assert.False(t, provider.Ready(), "URL-managed must not seed from --repo-cpe-bundled-path") + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, provider.UpdatePath()) +} + +// TestNewRescannerAndProvider_URLNeverSucceeds_StaysNotSensorManaged covers +// a configured --repo-cpe-url that never produces a usable mapping: the +// update path must stay URL, with no fallback to Sensor, even once the +// downloader has actually attempted and failed a fetch. Log-level (ERROR +// vs WARN) is not asserted here: the codebase has no lightweight log- +// capture convention (see report), and URLUpdater.onDownloadComplete only +// ever logs WARN today regardless of whether a mapping was ever +// successfully fetched — see the task report for this gap. +func TestNewRescannerAndProvider_URLNeverSucceeds_StaysNotSensorManaged(t *testing.T) { + withMappingCachePath(t) + + var requests atomic.Int64 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + requests.Add(1) + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + + _, provider, updater, urlUpdater := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{repoCPEURL: srv.URL}) + require.Nil(t, updater) + require.NotNil(t, urlUpdater) + + ctx, cancel := context.WithCancel(t.Context()) + require.NoError(t, urlUpdater.Start(ctx)) + defer func() { + cancel() + urlUpdater.Stop() + }() + + assert.Eventually(t, func() bool { return requests.Load() > 0 }, waitTimeout, waitTick, + "the downloader should have attempted at least one fetch against the configured URL") + + assert.False(t, provider.Ready(), "a URL that never succeeds must never make the provider Ready") + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, provider.UpdatePath(), + "a failing URL must not fall back to Sensor management") +} + +// TestNewRescannerAndProvider_RestartSwitchesUpdatePath simulates an +// operator adding or removing --repo-cpe-url between two agent restarts +// that share the same on-disk mappingCachePath. Per the design, the +// update path is re-derived purely from cfg.repoCPEURL on every restart, +// never persisted; this table only pins that re-derivation, not whether +// the previous updater's cached bytes get re-read (see report: cmd wires +// both updaters to the same mappingCachePath file, so unlike the design's +// "own cache file" wording, content bootstrapped by one updater is in +// fact visible to the other after a switch). +func TestNewRescannerAndProvider_RestartSwitchesUpdatePath(t *testing.T) { + tests := map[string]struct { + firstURL string + secondURL string + wantFirst pb.RepoCPEMappingUpdatePath + wantAfter pb.RepoCPEMappingUpdatePath + }{ + "adding --repo-cpe-url switches Sensor-managed to URL-managed": { + firstURL: "", + secondURL: "https://example.invalid/repo-to-cpe.json", + wantFirst: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, + wantAfter: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, + }, + "removing --repo-cpe-url switches URL-managed to Sensor-managed": { + firstURL: "https://example.invalid/repo-to-cpe.json", + secondURL: "", + wantFirst: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL, + wantAfter: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + withMappingCachePath(t) + + _, firstProvider, _, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{repoCPEURL: tt.firstURL}) + require.Equal(t, tt.wantFirst, firstProvider.UpdatePath()) + + _, secondProvider, _, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{repoCPEURL: tt.secondURL}) + assert.Equal(t, tt.wantAfter, secondProvider.UpdatePath(), + "a fresh restart must re-derive the update path from the current --repo-cpe-url, not remember the prior one") + }) + } +} + +// TestNewRescannerAndProvider_SensorSync_DefersUnderScanThenApplies covers +// the cmd-level wiring for deferred apply: SensorUpdater's own deferred- +// apply logic is unit-tested in vsockserver/sensor_updater_test.go, so this +// only checks that newRescannerAndProvider's returned updater/provider +// pair exposes that behavior end-to-end through the ScanBusyGate interface. +func TestNewRescannerAndProvider_SensorSync_DefersUnderScanThenApplies(t *testing.T) { + cachePath := withMappingCachePath(t) + require.NoError(t, os.WriteFile(cachePath, []byte(validMappingJSON), 0o600)) + + _, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{}) + require.True(t, provider.Ready(), "a pre-seeded Sensor cache must bootstrap the provider Ready") + require.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), provider.Hash()) + + gate, ok := updater.(vsockserver.ScanBusyGate) + require.True(t, ok, "the Sensor updater must implement ScanBusyGate") + gate.MarkScanBusy() + + updated, err := updater.Update([]byte(otherValidMappingJSON)) + require.NoError(t, err) + require.True(t, updated) + + assert.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), provider.Hash(), + "the active mapping must stay the bootstrapped content while a scan is in flight") + + gate.MarkScanIdleAndApplyPending() + + assert.Equal(t, cpemapping.HashMapping([]byte(otherValidMappingJSON)), provider.Hash(), + "the pending mapping should apply once the scan is marked idle") + + // MarkScanIdleAndApplyPending's persist is also fire-and-forget; wait + // for it so the background write can't still be touching cachePath's + // directory when t.TempDir() cleans it up. + assert.Eventually(t, func() bool { + content, err := os.ReadFile(cachePath) + return err == nil && string(content) == otherValidMappingJSON + }, waitTimeout, waitTick, "the applied mapping should also land in the on-disk cache") +} + +// TestNewRescannerAndProvider_SensorUpdate_InvalidContentKeepsLastGood +// covers the cmd-level wiring only; SensorUpdater's own validation +// behavior is unit-tested in vsockserver/sensor_updater_test.go. +func TestNewRescannerAndProvider_SensorUpdate_InvalidContentKeepsLastGood(t *testing.T) { + cachePath := withMappingCachePath(t) + _, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{}) + + updated, err := updater.Update([]byte(validMappingJSON)) + require.NoError(t, err) + require.True(t, updated) + wantHash := provider.Hash() + // Drain the first Update's fire-and-forget persist before the test ends, + // so its background write can't still be touching cachePath's directory + // when t.TempDir() cleans it up. + assert.Eventually(t, func() bool { + content, err := os.ReadFile(cachePath) + return err == nil && string(content) == validMappingJSON + }, waitTimeout, waitTick, "the initial valid mapping should land in the on-disk cache") + + updated, err = updater.Update([]byte(`{not valid json`)) + assert.Error(t, err) + assert.False(t, updated) + assert.Equal(t, wantHash, provider.Hash(), "invalid content must not replace the last-known-good mapping") +} + // validServeConfig returns a serveConfig that passes validate(), so each // test case in TestRunServe_ValidatesFlags only needs to override the one // field it's exercising. diff --git a/compliance/virtualmachines/roxagent/vsockserver/mapping.go b/compliance/virtualmachines/roxagent/vsockserver/mapping.go index 24226356d349d..eb59d5573fd6e 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/mapping.go +++ b/compliance/virtualmachines/roxagent/vsockserver/mapping.go @@ -23,8 +23,7 @@ type MappingUpdater interface { Update(content []byte) (updated bool, err error) } -// ScanBusyGate is implemented by the Sensor updater so rescanner/Handler -// can defer active applies across an in-flight scan + GetReport send. +// ScanBusyGate keeps a mapping Sync from replacing the file a scan is reading. type ScanBusyGate interface { MarkScanBusy() MarkScanIdleAndApplyPending() diff --git a/compliance/virtualmachines/roxagent/vsockserver/protocol.go b/compliance/virtualmachines/roxagent/vsockserver/protocol.go index f37bd92841eb9..8ef0d7919e6f1 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/protocol.go +++ b/compliance/virtualmachines/roxagent/vsockserver/protocol.go @@ -13,12 +13,19 @@ import ( "github.com/cespare/xxhash/v2" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" "github.com/stackrox/rox/pkg/vsockframing" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) -const maxRequestSize = 1 << 20 // 1 MiB +const ( + // maxRequestSize must fit the largest message - a synced repo-to-CPE mapping (up to MaxMappingBytes) - plus envelope headroom. + maxRequestSize = cpemapping.MaxMappingBytes + 1<<20 + + methodGetReport = "get_report" + methodSyncRepoCPEMapping = "sync_repo_cpe_mapping" +) // reportSnapshot is an immutable point-in-time view of the cached report state. type reportSnapshot struct { @@ -26,6 +33,7 @@ type reportSnapshot struct { token string generatedAt time.Time facts map[string]string + mappingHash string } // ReportCache holds the cached scan report with its content-hash token. @@ -36,14 +44,29 @@ type ReportCache struct { snap atomic.Pointer[reportSnapshot] } +// Token returns the published content-hash, or empty before the first scan. +func (c *ReportCache) Token() string { + if c == nil { + return "" + } + snap := c.snap.Load() + if snap == nil { + return "" + } + return snap.token +} + // SetReport atomically publishes a new report and facts. The token is an // XXH64 of that content, so identical rescans stay unchanged for Sensor. // +// mappingHash is the repo-to-CPE mapping the scan used, so GetReport can send +// it after a later Sync has already replaced the live mapping. +// // r and facts are defensively copied so that a caller mutating its own copy // after this call (or reusing the same facts map across scans) can never // mutate the published, supposedly-immutable snapshot out from under // concurrent readers. -func (c *ReportCache) SetReport(r *v4.IndexReport, facts map[string]string) { +func (c *ReportCache) SetReport(r *v4.IndexReport, facts map[string]string, mappingHash string) { cloned := cloneIndexReport(r) clonedFacts := cloneFacts(facts) c.snap.Store(&reportSnapshot{ @@ -51,6 +74,7 @@ func (c *ReportCache) SetReport(r *v4.IndexReport, facts map[string]string) { token: reportToken(cloned, clonedFacts), generatedAt: time.Now(), facts: clonedFacts, + mappingHash: mappingHash, }) } @@ -96,11 +120,14 @@ func cloneFacts(in map[string]string) map[string]string { type Handler struct { cache *ReportCache agentVersion string + provider MappingProvider + updater MappingUpdater } -// NewHandler creates a protocol handler. -func NewHandler(cache *ReportCache, agentVersion string) *Handler { - return &Handler{cache: cache, agentVersion: agentVersion} +// NewHandler creates a protocol handler. updater is nil for URL-managed +// agents, so SyncRepoCPEMapping is rejected with MAPPING_NOT_SENSOR_MANAGED. +func NewHandler(cache *ReportCache, agentVersion string, provider MappingProvider, updater MappingUpdater) *Handler { + return &Handler{cache: cache, agentVersion: agentVersion, provider: provider, updater: updater} } // HandleConn reads a framed request from conn, processes it, writes a framed response, and closes conn. @@ -138,6 +165,7 @@ func (h *Handler) HandleConn(conn net.Conn) { } if err := vsockframing.WriteFrame(conn, respData); err != nil { log.Errorf("Writing response frame: %v", err) + return } } @@ -145,12 +173,19 @@ func (h *Handler) dispatch(req *pb.VMServiceRequest) *pb.VMServiceResponse { switch req.GetMethod().(type) { case *pb.VMServiceRequest_GetReport: return h.handleGetReport(req.GetGetReport()) + case *pb.VMServiceRequest_SyncRepoCpeMapping: + return h.handleSyncRepoCPEMapping(req.GetSyncRepoCpeMapping()) default: return h.errorResponse(pb.ErrorCode_ERROR_CODE_UNKNOWN_METHOD, "unknown or unset method") } } func (h *Handler) handleGetReport(req *pb.GetReportRequest) *pb.VMServiceResponse { + if !h.provider.Ready() { + log.Info("GetReport: no repo-to-CPE mapping available yet") + return h.errorResponse(pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED, "repository-to-CPE mapping not yet available") + } + snap := h.cache.snap.Load() if snap == nil || snap.report == nil { log.Info("GetReport: not ready (initial scan in progress)") @@ -180,15 +215,24 @@ func (h *Handler) handleGetReport(req *pb.GetReportRequest) *pb.VMServiceRespons func (h *Handler) newResponseFromSnap(snap *reportSnapshot) *pb.VMServiceResponse { var facts map[string]string var token string + mappingHash := h.provider.Hash() if snap != nil { facts = cloneFacts(snap.facts) token = snap.token + mappingHash = snap.mappingHash + } + updatePath := h.provider.UpdatePath() + methods := []string{methodGetReport} + if h.updater != nil { + methods = append(methods, methodSyncRepoCPEMapping) } meta := &pb.ResponseMeta{ - AgentVersion: h.agentVersion, - ReportToken: token, - SupportedMethods: []string{"get_report"}, - Facts: facts, + AgentVersion: h.agentVersion, + ReportToken: token, + SupportedMethods: methods, + Facts: facts, + RepoCpeMappingHash: new(mappingHash), + RepoCpeMappingUpdatePath: updatePath.Enum(), } if snap != nil && !snap.generatedAt.IsZero() { meta.ReportGeneratedAt = timestamppb.New(snap.generatedAt) @@ -196,6 +240,24 @@ func (h *Handler) newResponseFromSnap(snap *reportSnapshot) *pb.VMServiceRespons return &pb.VMServiceResponse{Meta: meta} } +// handleSyncRepoCPEMapping applies a Sensor-pushed mapping. URL-managed +// agents (updater == nil) reject Sync outright; Update keeps the last-good +// mapping on a validation failure. +func (h *Handler) handleSyncRepoCPEMapping(req *pb.SyncRepoCPEMappingRequest) *pb.VMServiceResponse { + if h.updater == nil { + return h.errorResponse(pb.ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED, "agent is URL-managed and does not accept pushed mappings") + } + updated, err := h.updater.Update(req.GetMapping()) + if err != nil { + return h.errorResponse(pb.ErrorCode_ERROR_CODE_MALFORMED_REQUEST, fmt.Sprintf("invalid repository-to-CPE mapping: %v", err)) + } + resp := h.newResponseFromSnap(h.cache.snap.Load()) + resp.Result = &pb.VMServiceResponse_SyncRepoCpeMapping{ + SyncRepoCpeMapping: &pb.SyncRepoCPEMappingResponse{Updated: updated}, + } + return resp +} + func (h *Handler) errorResponse(code pb.ErrorCode, msg string) *pb.VMServiceResponse { return h.errorResponseFromSnap(h.cache.snap.Load(), code, msg) } diff --git a/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go b/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go index e8372f4e85710..1150d1dcf1894 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go +++ b/compliance/virtualmachines/roxagent/vsockserver/protocol_test.go @@ -1,17 +1,56 @@ package vsockserver import ( + "errors" "net" + "path/filepath" "testing" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" "github.com/stackrox/rox/pkg/vsockframing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) +// fakeMappingProvider is a MappingProvider test double whose Ready/Hash/ +// UpdatePath are directly settable; Bytes/Path are unused by the Handler +// and always error. +type fakeMappingProvider struct { + ready bool + hash string + updatePath pb.RepoCPEMappingUpdatePath +} + +func (f *fakeMappingProvider) Ready() bool { return f.ready } +func (f *fakeMappingProvider) Hash() string { return f.hash } +func (f *fakeMappingProvider) UpdatePath() pb.RepoCPEMappingUpdatePath { return f.updatePath } +func (f *fakeMappingProvider) Bytes() ([]byte, error) { return nil, errors.New("not implemented") } +func (f *fakeMappingProvider) Path() (string, error) { return "", errors.New("not implemented") } + +var _ MappingProvider = (*fakeMappingProvider)(nil) + +// fakeMappingUpdater is a MappingUpdater test double that returns a fixed +// (updated, err) pair and records the content it was called with. +type fakeMappingUpdater struct { + updated bool + err error + calls [][]byte +} + +func (f *fakeMappingUpdater) Update(content []byte) (bool, error) { + f.calls = append(f.calls, content) + return f.updated, f.err +} + +var _ MappingUpdater = (*fakeMappingUpdater)(nil) + +// readyProvider is the default fakeMappingProvider for GetReport test +// cases that aren't exercising mapping-readiness behavior itself. +func readyProvider() *fakeMappingProvider { return &fakeMappingProvider{ready: true} } + func sendAndReceive(t *testing.T, handler *Handler, req *pb.VMServiceRequest) *pb.VMServiceResponse { t.Helper() clientConn, serverConn := net.Pipe() @@ -39,9 +78,10 @@ func getReportRequest(requestID, lastKnownToken string) *pb.VMServiceRequest { func TestHandleRequest_GetReport(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil, "mapping-hash") + provider := &fakeMappingProvider{ready: true, hash: "mapping-hash", updatePath: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR} - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", provider, nil) req := getReportRequest("req-1", "") req.Meta.Capabilities = []string{"report_v1"} @@ -56,14 +96,34 @@ func TestHandleRequest_GetReport(t *testing.T) { assert.Equal(t, "test-1.0.0", meta.GetAgentVersion()) assert.NotEmpty(t, meta.GetReportToken()) assert.NotNil(t, meta.GetReportGeneratedAt()) - assert.Contains(t, meta.GetSupportedMethods(), "get_report") + assert.Contains(t, meta.GetSupportedMethods(), methodGetReport) + assert.NotContains(t, meta.GetSupportedMethods(), methodSyncRepoCPEMapping) + assert.Equal(t, "mapping-hash", meta.GetRepoCpeMappingHash()) + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, meta.GetRepoCpeMappingUpdatePath()) +} + +func TestHandleRequest_GetReport_MappingHashStaysWithTheReport(t *testing.T) { + cache := &ReportCache{} + cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil, "hash-at-scan") + provider := &fakeMappingProvider{ready: true, hash: "hash-after-later-sync"} + + handler := NewHandler(cache, "test-1.0.0", provider, nil) + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-snap-hash"}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + } + + resp := sendAndReceive(t, handler, req) + + assert.Equal(t, "hash-at-scan", resp.GetMeta().GetRepoCpeMappingHash(), + "GetReport must send the hash the cached report was built with, not the mapping that landed after the scan") } func TestHandleRequest_GetReport_Unchanged(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil, "") - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) first := sendAndReceive(t, handler, getReportRequest("req-learn-token", "")) token := first.GetMeta().GetReportToken() require.NotEmpty(t, token) @@ -80,9 +140,9 @@ func TestHandleRequest_GetReport_Unchanged(t *testing.T) { // that does not match the agent's current content. func TestHandleRequest_GetReport_TokenMismatch(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "post-restart-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "post-restart-hash"}, nil, "") - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) resp := sendAndReceive(t, handler, getReportRequest("req-mismatch", "stale-token")) assert.NotNil(t, resp.GetGetReport()) @@ -98,14 +158,14 @@ func TestHandleRequest_GetReport_TokenMismatch(t *testing.T) { // gets unchanged unless it sends an empty last_known_token. func TestHandleRequest_GetReport_IdenticalRescanUnchanged(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil, "") - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) first := sendAndReceive(t, handler, getReportRequest("req-first-scan", "")) token := first.GetMeta().GetReportToken() require.NotEmpty(t, token) - cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "same-hash"}, nil, "") resp := sendAndReceive(t, handler, getReportRequest("req-after-rescan", token)) assert.NotNil(t, resp.GetGetReport()) @@ -118,14 +178,14 @@ func TestHandleRequest_GetReport_IdenticalRescanUnchanged(t *testing.T) { // whose content changed: the token changes and the full report is served. func TestHandleRequest_GetReport_ContentChangeServesReport(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "before"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "before"}, nil, "") - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) first := sendAndReceive(t, handler, getReportRequest("req-before", "")) token := first.GetMeta().GetReportToken() require.NotEmpty(t, token) - cache.SetReport(&v4.IndexReport{HashId: "after"}, nil) + cache.SetReport(&v4.IndexReport{HashId: "after"}, nil, "") resp := sendAndReceive(t, handler, getReportRequest("req-after", token)) assert.NotNil(t, resp.GetGetReport()) @@ -146,7 +206,7 @@ func TestReportToken(t *testing.T) { func TestHandleRequest_NotReady(t *testing.T) { cache := &ReportCache{} - handler := NewHandler(cache, "test-1.0.0") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) req := &pb.VMServiceRequest{ Meta: &pb.RequestMeta{RequestId: "req-3"}, Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, @@ -161,8 +221,8 @@ func TestHandleRequest_NotReady(t *testing.T) { func TestHandleRequest_UnknownMethod(t *testing.T) { cache := &ReportCache{} - cache.SetReport(&v4.IndexReport{HashId: "x"}, nil) - handler := NewHandler(cache, "test-1.0.0") + cache.SetReport(&v4.IndexReport{HashId: "x"}, nil, "") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) req := &pb.VMServiceRequest{ Meta: &pb.RequestMeta{RequestId: "req-4"}, @@ -175,3 +235,162 @@ func TestHandleRequest_UnknownMethod(t *testing.T) { require.NotNil(t, resp.GetError()) assert.Equal(t, pb.ErrorCode_ERROR_CODE_UNKNOWN_METHOD, resp.GetError().GetCode()) } + +func TestHandleRequest_GetReport_MappingRequired(t *testing.T) { + cache := &ReportCache{} + cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil, "") + handler := NewHandler(cache, "test-1.0.0", &fakeMappingProvider{ready: false}, nil) + + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-mapping-required"}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + } + + resp := sendAndReceive(t, handler, req) + + assert.Nil(t, resp.GetGetReport()) + require.NotNil(t, resp.GetError()) + assert.Equal(t, pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED, resp.GetError().GetCode()) +} + +// TestHandleRequest_MetadataAlwaysPresentOnError pins down that an empty +// mapping hash is still an explicitly-present optional field, not a nil +// one Sensor would have to special-case. +func TestHandleRequest_MetadataAlwaysPresentOnError(t *testing.T) { + cache := &ReportCache{} + handler := NewHandler(cache, "test-1.0.0", &fakeMappingProvider{ready: false}, nil) + + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-meta-on-error"}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + } + + resp := sendAndReceive(t, handler, req) + + require.NotNil(t, resp.GetError()) + meta := resp.GetMeta() + require.NotNil(t, meta) + require.NotNil(t, meta.RepoCpeMappingHash, "hash must be present-but-empty, not nil") + assert.Empty(t, meta.GetRepoCpeMappingHash()) + require.NotNil(t, meta.RepoCpeMappingUpdatePath) + assert.NotContains(t, meta.GetSupportedMethods(), methodSyncRepoCPEMapping) +} + +func TestHandleRequest_SyncRepoCPEMapping(t *testing.T) { + tests := map[string]struct { + updater *fakeMappingUpdater + wantCode pb.ErrorCode + wantUpdated bool + }{ + "new mapping content is applied": { + updater: &fakeMappingUpdater{updated: true}, + wantUpdated: true, + }, + "unchanged mapping content is a no-op": { + updater: &fakeMappingUpdater{updated: false}, + wantUpdated: false, + }, + "oversize or otherwise invalid content is rejected as malformed": { + updater: &fakeMappingUpdater{err: errors.New("mapping size exceeds cap")}, + wantCode: pb.ErrorCode_ERROR_CODE_MALFORMED_REQUEST, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + cache := &ReportCache{} + handler := NewHandler(cache, "test-1.0.0", readyProvider(), tt.updater) + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-sync"}, + Method: &pb.VMServiceRequest_SyncRepoCpeMapping{SyncRepoCpeMapping: &pb.SyncRepoCPEMappingRequest{Mapping: []byte("content")}}, + } + + resp := sendAndReceive(t, handler, req) + + if tt.wantCode != pb.ErrorCode_ERROR_CODE_UNSPECIFIED { + require.NotNil(t, resp.GetError()) + assert.Equal(t, tt.wantCode, resp.GetError().GetCode()) + assert.Nil(t, resp.GetSyncRepoCpeMapping()) + return + } + require.NotNil(t, resp.GetSyncRepoCpeMapping()) + assert.Equal(t, tt.wantUpdated, resp.GetSyncRepoCpeMapping().GetUpdated()) + assert.Contains(t, resp.GetMeta().GetSupportedMethods(), methodSyncRepoCPEMapping) + require.Len(t, tt.updater.calls, 1) + assert.Equal(t, []byte("content"), tt.updater.calls[0]) + }) + } +} + +func TestHandleRequest_SyncRepoCPEMapping_NilUpdater_NotSensorManaged(t *testing.T) { + cache := &ReportCache{} + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-sync-url-managed"}, + Method: &pb.VMServiceRequest_SyncRepoCpeMapping{SyncRepoCpeMapping: &pb.SyncRepoCPEMappingRequest{Mapping: []byte("content")}}, + } + + resp := sendAndReceive(t, handler, req) + + assert.Nil(t, resp.GetSyncRepoCpeMapping()) + require.NotNil(t, resp.GetError()) + assert.Equal(t, pb.ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED, resp.GetError().GetCode()) +} + +// TestHandleConn_GetReport_DoesNotApplyPending covers a rescan in flight +// while Sensor still polls: GetReport must not apply a deferred Sync. The +// scan itself applies pending when it returns. +func TestHandleConn_GetReport_DoesNotApplyPending(t *testing.T) { + cache := &ReportCache{} + cachePath := filepath.Join(t.TempDir(), "cache.json") + u := NewSensorUpdater(cachePath, "", nil) + _, err := u.Update([]byte(validMappingJSON)) + require.NoError(t, err) + scanHash := u.Hash() + cache.SetReport(&v4.IndexReport{HashId: "gen-1"}, nil, scanHash) + + handler := NewHandler(cache, "test-1.0.0", u, u) + u.MarkScanBusy() + + getReportReq := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-during-scan"}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + } + resp := sendAndReceive(t, handler, getReportReq) + require.NotNil(t, resp.GetGetReport()) + + updated, err := u.Update([]byte(otherValidMappingJSON)) + require.NoError(t, err) + require.True(t, updated) + assert.Equal(t, scanHash, u.Hash(), "GetReport must not apply a deferred Sync") + + cache.SetReport(&v4.IndexReport{HashId: "gen-2"}, nil, scanHash) + resp = sendAndReceive(t, handler, getReportReq) + require.NotNil(t, resp.GetGetReport()) + assert.Equal(t, scanHash, u.Hash(), "GetReport of a newer cached report must still not apply pending") + assert.Equal(t, scanHash, resp.GetMeta().GetRepoCpeMappingHash()) + + u.MarkScanIdleAndApplyPending() + assert.Equal(t, cpemapping.HashMapping([]byte(otherValidMappingJSON)), u.Hash()) + waitForCacheContent(t, u, cachePath, otherValidMappingJSON) + + resp = sendAndReceive(t, handler, getReportReq) + assert.Equal(t, scanHash, resp.GetMeta().GetRepoCpeMappingHash(), + "the cached report must still send the hash it was built with") +} + +// TestHandleConn_UngatedProviderNeverPanics covers a URL-managed agent's +// provider, which does not implement ScanBusyGate. +func TestHandleConn_UngatedProviderNeverPanics(t *testing.T) { + cache := &ReportCache{} + cache.SetReport(&v4.IndexReport{HashId: "test-hash"}, nil, "") + handler := NewHandler(cache, "test-1.0.0", readyProvider(), nil) + + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{RequestId: "req-ungated"}, + Method: &pb.VMServiceRequest_GetReport{GetReport: &pb.GetReportRequest{}}, + } + + resp := sendAndReceive(t, handler, req) + assert.NotNil(t, resp.GetGetReport()) +} diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go index 6a9055779197f..ead8678b1eb03 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go @@ -20,9 +20,9 @@ var ( var errSensorMappingNotReady = errors.New("no repo-to-CPE mapping available yet") -// SensorUpdater applies repo-to-CPE mappings pushed in over VSOCK via -// SyncRepoCPEMapping, deferring the apply while busy is set so an -// in-flight scan's GetReport response never sees a mid-flight change. +// SensorUpdater applies repo-to-CPE mappings pushed over VSOCK. A Sync +// during a scan is deferred so it cannot replace the file that scan is +// reading. type SensorUpdater struct { active []byte activeHash string @@ -145,27 +145,27 @@ func (u *SensorUpdater) Update(content []byte) (updated bool, err error) { return true, false }) if !updated { + log.Debugf("SyncRepoCPEMapping: mapping unchanged (hash=%s)", hash) return false, nil } if deferred { log.Infof("SyncRepoCPEMapping: deferring apply of mapping (hash=%s) until the in-flight scan finishes", hash) return true, nil } + log.Infof("SyncRepoCPEMapping: applied mapping (hash=%s)", hash) u.persistAndNotify() return true, nil } -// MarkScanBusy marks a VM-disk scan in progress, so a concurrent Update -// stages new content as pending instead of mutating active underneath it. +// MarkScanBusy sets busy=true so a mapping-Sync during this scan is saved into pending instead of replacing the mapping. +// Replacing the mapping could cause race between the scan reading that mapping and the Sync writing it. func (u *SensorUpdater) MarkScanBusy() { u.mu.Lock() defer u.mu.Unlock() u.busy = true } -// MarkScanIdleAndApplyPending clears busy and, if Update staged content -// while busy, promotes it to active now that the scan (and its GetReport -// response) has finished. +// MarkScanIdleAndApplyPending applies a mapping that arrived during the scan, now that the scan is no longer reading the file. func (u *SensorUpdater) MarkScanIdleAndApplyPending() { pending := concurrency.WithLock1(&u.mu, func() []byte { u.busy = false @@ -178,6 +178,7 @@ func (u *SensorUpdater) MarkScanIdleAndApplyPending() { return pending }) if pending != nil { + log.Infof("Applying deferred mapping (hash=%s)", cpemapping.HashMapping(pending)) u.persistAndNotify() } } diff --git a/compliance/virtualmachines/roxagent/vsockserver/server_test.go b/compliance/virtualmachines/roxagent/vsockserver/server_test.go index 7cf5c2372eb9a..509a84362829a 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/server_test.go +++ b/compliance/virtualmachines/roxagent/vsockserver/server_test.go @@ -19,7 +19,7 @@ import ( // rejects a second connection with an ERROR_CODE_BUSY response while the first // is still being handled, and that cancelling the context drains gracefully. func TestServeAcceptLoop(t *testing.T) { - handler := NewHandler(&ReportCache{}, "test") + handler := NewHandler(&ReportCache{}, "test", readyProvider(), nil) srv := NewServer(handler, nil) ln, err := net.Listen("tcp", "127.0.0.1:0") @@ -77,7 +77,7 @@ func TestRejectConn(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { synctest.Test(t, func(t *testing.T) { - handler := NewHandler(&ReportCache{}, "test") + handler := NewHandler(&ReportCache{}, "test", readyProvider(), nil) srv := NewServer(handler, nil) serverConn, clientConn := net.Pipe() @@ -133,7 +133,7 @@ func TestRejectConn(t *testing.T) { // ERROR_CODE_BUSY. That is enough to prove Accept() kept running: a blocked // accept loop would never handshake the second peer or write BUSY. func TestServeAcceptLoop_StalledHandshakeDoesNotBlockOtherConnections(t *testing.T) { - handler := NewHandler(&ReportCache{}, "test") + handler := NewHandler(&ReportCache{}, "test", readyProvider(), nil) srv := NewServer(handler, &tls.Config{Certificates: []tls.Certificate{testServerCert(t)}}) ln, err := net.Listen("tcp", "127.0.0.1:0") diff --git a/compliance/virtualmachines/roxagent/vsockserver/url_updater.go b/compliance/virtualmachines/roxagent/vsockserver/url_updater.go index 23b19dc8a7628..3178bed180def 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/url_updater.go +++ b/compliance/virtualmachines/roxagent/vsockserver/url_updater.go @@ -13,9 +13,9 @@ import ( "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" ) -// Retry/refresh policy for the periodic mapping download, mirroring -// cmd.newMappingDownloader: WaitMin=5s with the default WaitMax=30s covers -// four HTTP attempts (5+10+20=35s) within urlMappingClientTimeout. +// Retry/refresh policy for the periodic mapping download. WaitMin=5s with +// retryablehttp's default WaitMax=30s covers four HTTP attempts (5+10+20=35s) +// within urlMappingClientTimeout. const ( urlMappingFetchRetryMax = 3 urlMappingFetchRetryWaitMin = 5 * time.Second diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index 57af412f01cd5..15e1fdf723a2c 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -123,6 +123,7 @@ const ( const ( PullGetReportUnchanged = "unchanged" PullGetReportNotReady = "not_ready" + PullGetReportMappingRequired = "mapping_required" PullGetReportUnknownMethod = "unknown_method" PullGetReportBusy = "busy" PullGetReportInternalError = "internal_error" diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 5e912fe03cd2e..bab9d23190070 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -621,6 +621,10 @@ func (s *VMScraper) handleGetReportError(ctx context.Context, key string, err er log.Debugf("VMScraper: roxagent on %q has not yet generated a report", key) metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportNotReady).Inc() return scrapeRetryable + case errors.Is(err, vsockclient.ErrMappingRequired): + log.Debugf("VMScraper: roxagent on %q has no repository-to-CPE mapping yet", key) + metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportMappingRequired).Inc() + return scrapeRetryable case errors.Is(err, vsockclient.ErrUnknownMethod): log.Warnf("VMScraper: roxagent on %q does not support the GetReport method", key) metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportUnknownMethod).Inc() diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 06b8c855f8766..a69c203d6ed0b 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -890,6 +890,7 @@ func allPullOutcomeSamples() []pullOutcomeSample { for _, label := range []string{ metrics.PullGetReportUnchanged, metrics.PullGetReportNotReady, + metrics.PullGetReportMappingRequired, metrics.PullGetReportUnknownMethod, metrics.PullGetReportBusy, metrics.PullGetReportInternalError, @@ -944,6 +945,12 @@ func TestHandleGetReportError_ClassifiesEveryErrorCode(t *testing.T) { wantLabel: metrics.PullGetReportNotReady, wantOutcome: scrapeRetryable, }, + "MAPPING_REQUIRED maps to get_report mapping_required": { + err: fmt.Errorf("%w: no mapping yet", vsockclient.ErrMappingRequired), + wantMetric: metrics.PullGetReportTotal, + wantLabel: metrics.PullGetReportMappingRequired, + wantOutcome: scrapeRetryable, + }, "UNKNOWN_METHOD maps to get_report unknown_method": { err: fmt.Errorf("%w: no get_report", vsockclient.ErrUnknownMethod), wantMetric: metrics.PullGetReportTotal, @@ -1244,6 +1251,12 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { wantBackoff: initialBackoff, wantGap: initialBackoff, }, + "ErrMappingRequired should retry using backoff": { + client: &mockProtocolClient{errQueue: []error{vsockclient.ErrMappingRequired}}, + sender: &mockSender{}, + wantBackoff: initialBackoff, + wantGap: initialBackoff, + }, "io.EOF should retry using backoff": { client: &mockProtocolClient{errQueue: []error{io.EOF}}, sender: &mockSender{}, diff --git a/sensor/common/virtualmachine/vsockclient/client.go b/sensor/common/virtualmachine/vsockclient/client.go index 06a27954e4199..5fe235c329aab 100644 --- a/sensor/common/virtualmachine/vsockclient/client.go +++ b/sensor/common/virtualmachine/vsockclient/client.go @@ -36,6 +36,8 @@ var ( // ErrUnknownAgentError indicates a well-formed ErrorResponse whose code // this client doesn't recognize (e.g. a future ErrorCode value). ErrUnknownAgentError = errors.New("unrecognized agent error code") + // ErrMappingRequired indicates the agent has no repository-to-CPE mapping yet. + ErrMappingRequired = errors.New("agent has no repository-to-CPE mapping yet") ) // GetReportResult holds the parsed response from a GetReport call. @@ -154,6 +156,8 @@ func errorFromResponse(e *pb.ErrorResponse) error { return fmt.Errorf("%w: %s", ErrRequestTooLarge, e.GetMessage()) case pb.ErrorCode_ERROR_CODE_BUSY: return fmt.Errorf("%w: %s", ErrBusy, e.GetMessage()) + case pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED: + return fmt.Errorf("%w: %s", ErrMappingRequired, e.GetMessage()) default: return fmt.Errorf("%w: agent error (%s): %s", ErrUnknownAgentError, e.GetCode(), e.GetMessage()) } diff --git a/sensor/common/virtualmachine/vsockclient/client_integration_test.go b/sensor/common/virtualmachine/vsockclient/client_integration_test.go index 605d99ff1a545..2d8233d7fc889 100644 --- a/sensor/common/virtualmachine/vsockclient/client_integration_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_integration_test.go @@ -2,6 +2,7 @@ package vsockclient import ( "context" + "errors" "fmt" "net" "testing" @@ -17,6 +18,20 @@ import ( "google.golang.org/protobuf/proto" ) +// readyMappingProvider is Ready so GetReport reaches the cache path these +// tests cover (empty cache → NOT_READY, seeded cache → report). +type readyMappingProvider struct{} + +func (readyMappingProvider) Ready() bool { return true } +func (readyMappingProvider) Hash() string { return "" } +func (readyMappingProvider) UpdatePath() pb.RepoCPEMappingUpdatePath { + return pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR +} +func (readyMappingProvider) Bytes() ([]byte, error) { return nil, errors.New("not implemented") } +func (readyMappingProvider) Path() (string, error) { return "", errors.New("not implemented") } + +var _ roxagentvsock.MappingProvider = readyMappingProvider{} + // exchangeTimeout bounds a single client/handler exchange. Under synctest the // fake clock advances this duration instantly once every goroutine is durably // blocked (e.g. a protocol deadlock on net.Pipe), so GetReport's context @@ -55,13 +70,13 @@ func newProtocolHarness(t *testing.T, opts protocolHarnessOptions) *protocolHarn cache := &roxagentvsock.ReportCache{} if opts.seedReport != nil { - cache.SetReport(opts.seedReport, opts.seedFacts) + cache.SetReport(opts.seedReport, opts.seedFacts, "") } return &protocolHarness{ client: NewClient(opts.capabilities, opts.maxResponseSize), cache: cache, - handler: roxagentvsock.NewHandler(cache, opts.agentVersion), + handler: roxagentvsock.NewHandler(cache, opts.agentVersion, readyMappingProvider{}, nil), } } diff --git a/sensor/common/virtualmachine/vsockclient/client_test.go b/sensor/common/virtualmachine/vsockclient/client_test.go index 2c1dc85ef3726..82bc94ba3f7ed 100644 --- a/sensor/common/virtualmachine/vsockclient/client_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_test.go @@ -143,6 +143,11 @@ func TestSendGetReport_ErrorCodes(t *testing.T) { message: "", wantErr: ErrUnknownAgentError, }, + "should wrap ErrMappingRequired for MAPPING_REQUIRED": { + code: pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED, + message: "repository-to-CPE mapping not yet available", + wantErr: ErrMappingRequired, + }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { From 5c26670e82496512067b8e9738e8a989f3cff170 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 31 Aug 2026 11:18:02 -0400 Subject: [PATCH 136/638] ROX-36559: Update frontend files for CISA KEV as search field (#22458) --- .../CompoundSearchFilter/attributes/imageCVE.ts | 10 +++++----- .../src/Components/CompoundSearchFilter/types.ts | 4 ++-- ui/apps/platform/src/types/cve.proto.ts | 2 +- ui/apps/platform/src/types/searchOptions.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts index 21fd252759695..c8bb26a4920c4 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts @@ -29,24 +29,24 @@ export const CVSS: CompoundSearchFilterAttribute = { export const KnownExploit: CompoundSearchFilterAttribute = { displayName: 'Known exploit', filterChipLabel: 'Known exploit', - searchTerm: 'Known Exploit', // and 'Known Ransonware Campaign' as category2 + searchTerm: 'CISA KEV', // and 'Known Ransomware Campaign' as category2 inputType: 'select-exclusive-double', inputProps: { - category2: 'Known Ransonware Campaign', + category2: 'Known Ransomware Campaign', options: [ { label: 'Has a known exploit', - category: 'Known Exploit', + category: 'CISA KEV', value: 'true', }, { label: 'Used in ransomware campaigns', - category: 'Known Ransonware Campaign', + category: 'Known Ransomware Campaign', value: 'true', }, { label: 'No known exploit', - category: 'Known Exploit', + category: 'CISA KEV', value: 'false', }, ], diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/types.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/types.ts index c7ab7ad243ca9..d426595c3d5f4 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/types.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/types.ts @@ -62,12 +62,12 @@ export type SelectExclusiveDoubleSearchFilterAttribute = { } & BaseSearchFilterAttribute; export type SelectExclusiveDoubleSearchFilterInputProps = { - category2: string; + category2: SearchFieldLabel; options: NonEmptyArray; }; export type SelectExclusiveDoubleSearchFilterOption = { - category: string; + category: SearchFieldLabel; } & SelectSearchFilterOption; export type CompoundSearchFilterAttribute = diff --git a/ui/apps/platform/src/types/cve.proto.ts b/ui/apps/platform/src/types/cve.proto.ts index e8778f7cd608e..2d808b8feaaba 100644 --- a/ui/apps/platform/src/types/cve.proto.ts +++ b/ui/apps/platform/src/types/cve.proto.ts @@ -35,7 +35,7 @@ export type EPSS = { export type Exploit = { // This must always be set to true. // This field solely exists for search purposes. - // exists: boolean; // search:"Known Exploit,store" + // exists: boolean; // search:"CISA KEV,store" knownRansomwareCampaignUse: boolean; // search:"Known Ransomware Campaign,store" }; diff --git a/ui/apps/platform/src/types/searchOptions.ts b/ui/apps/platform/src/types/searchOptions.ts index f313890b07bb8..e2db4acb81016 100644 --- a/ui/apps/platform/src/types/searchOptions.ts +++ b/ui/apps/platform/src/types/searchOptions.ts @@ -47,8 +47,8 @@ export const searchFieldLabels = [ 'CVE Orphaned', 'CVE Orphaned Time', 'EPSS Probability', - 'Known Exploit', // frontend only pending backend implementation see obsolete #16887 - 'Known Ransomware Campaign', // frontend only pending backend implementation see obsolete #16887 + 'CISA KEV', + 'Known Ransomware Campaign', 'Advisory Name', 'Advisory Link', // From 2d23e921736cf9852bee380b417d00ff2ab555c1 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:04:04 +0200 Subject: [PATCH 137/638] chore(collector): Update COLLECTOR_VERSION (#22052) --- COLLECTOR_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COLLECTOR_VERSION b/COLLECTOR_VERSION index 98822bb3fc4b5..f8fdde8c81c08 100644 --- a/COLLECTOR_VERSION +++ b/COLLECTOR_VERSION @@ -1 +1 @@ -3.25.0-71-gf833aaf49f +3.25.0-136-g9c3e5263c6 From e9ec136ac531be94a0eabd1ba4b7100203d682b8 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:13:57 +0200 Subject: [PATCH 138/638] chore(fact): Update FACT_VERSION (#22054) --- FACT_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FACT_VERSION b/FACT_VERSION index 5f054639b4ac2..3d828935ed6bc 100644 --- a/FACT_VERSION +++ b/FACT_VERSION @@ -1 +1 @@ -0.3.x-230-g922750cb27 +0.3.x-340-gcbfa5e1fc2 From 5fd9c1516fe5402a00a2bafc973ae3e603502af7 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Mon, 31 Aug 2026 11:54:14 -0700 Subject: [PATCH 139/638] ROX-36123: Add risk summary api,lightspeed client (#22373) --- central/deployment/service/ai_prompt.go | 61 +++ .../service/risk_ai_summary_test.go | 458 ++++++++++++++++++ .../deployment/service/risk_sanitization.go | 136 ++++++ central/deployment/service/service.go | 4 +- central/deployment/service/service_impl.go | 42 ++ .../service/service_impl_benchmark_test.go | 2 +- central/deployment/service/singleton.go | 3 +- central/lightspeed/client/client.go | 121 +++++ generated/api/v1/deployment_service.pb.go | 136 ++++-- generated/api/v1/deployment_service.pb.gw.go | 78 +++ .../api/v1/deployment_service.swagger.json | 39 ++ .../api/v1/deployment_service_grpc.pb.go | 40 ++ .../api/v1/deployment_service_vtproto.pb.go | 260 ++++++++++ proto/api/v1/deployment_service.proto | 9 + .../export/deployments/deployments_test.go | 4 + 15 files changed, 1347 insertions(+), 46 deletions(-) create mode 100644 central/deployment/service/ai_prompt.go create mode 100644 central/deployment/service/risk_ai_summary_test.go create mode 100644 central/deployment/service/risk_sanitization.go create mode 100644 central/lightspeed/client/client.go diff --git a/central/deployment/service/ai_prompt.go b/central/deployment/service/ai_prompt.go new file mode 100644 index 0000000000000..86e9c60d66d87 --- /dev/null +++ b/central/deployment/service/ai_prompt.go @@ -0,0 +1,61 @@ +package service + +// This is the prompt for AI risk summary +const aiSummaryPrompt = `You are a Kubernetes security advisor embedded in Red Hat Advanced +Cluster Security. A security operator is investigating a deployment +flagged for review. + +AUDIENCE: Kubernetes cluster admin managing thousands of deployments. +They have oc access and want to reduce risk fast. + +TONE: Brief incident report. Short declarative sentences. No filler. + +CONTEXT ALREADY VISIBLE TO THE USER: +The user already sees the deployment name, namespace, cluster, +risk score, and a stat summary bar (policy violations, CVE count, +image age, component count) in the UI. Do NOT restate any of that. +Start with the insight. + +If the deployment has no significant risk factors (normalized score +below 25), state that in one sentence. Do not generate a risk +breakdown or actions. + +Use these exact section labels with no additional text: + +SUMMARY +2-3 sentences. Why the risk is high and the single most dangerous +finding. Name specific images, permission levels, and CVE counts +where relevant. Start with the insight, not the deployment metadata. + +RISK BREAKDOWN +Max 4 bullets. Top risk factors ordered by score impact. One +sentence per bullet, max 20 words. Group related findings under +one bullet (e.g., image age + image CVEs). Skip factors scoring +below 1.5. + +IMMEDIATE ACTIONS +Max 4 numbered items. Concrete steps to reduce the risk score now. +Each action: one sentence describing what to do, followed by the +oc command on its own line. + +Rules for commands: +- Each command must be a single line, runnable as-is. +- Use oc (not kubectl). Scope to the correct namespace and + deployment name from the data. +- Only use real oc subcommands: patch, set resources, set image, + create, delete, annotate, label. +- If a value depends on information not in the data (e.g., a new + image tag), do NOT include a command. State the action in plain + text and note what the user must determine first. +- If an action could break the workload, prefix the description + with "⚠ May affect application behavior:" and state what to + verify before applying. + +CONSTRAINTS: +- Plain text only. No markdown: no **, no backticks, no #. +- Do NOT explain ACS, risk scoring, or how the system works. +- Do NOT hedge. Be direct. +- Do NOT echo these instructions or section descriptions in + your response. +- CLUSTER_ADMIN service accounts are always a top-priority finding + regardless of score.` diff --git a/central/deployment/service/risk_ai_summary_test.go b/central/deployment/service/risk_ai_summary_test.go new file mode 100644 index 0000000000000..790d85f5afac6 --- /dev/null +++ b/central/deployment/service/risk_ai_summary_test.go @@ -0,0 +1,458 @@ +package service + +import ( + "context" + "encoding/json" + "errors" + "testing" + + deploymentMocks "github.com/stackrox/rox/central/deployment/datastore/mocks" + olsClient "github.com/stackrox/rox/central/lightspeed/client" + riskMocks "github.com/stackrox/rox/central/risk/datastore/mocks" + v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type mockOLSClient struct { + response *olsClient.QueryResponse + err error + captured *olsClient.QueryRequest +} + +func (m *mockOLSClient) Query(_ context.Context, req *olsClient.QueryRequest) (*olsClient.QueryResponse, error) { + m.captured = req + return m.response, m.err +} + +func TestGetDeploymentRiskAISummary_Success(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + mockRisks := riskMocks.NewMockDataStore(ctrl) + + deployment := &storage.Deployment{ + Id: "dep-1", + Name: "nginx-frontend", + Namespace: "production", + ClusterName: "cluster-east", + Type: "Deployment", + Created: timestamppb.Now(), + ServiceAccount: "default", + ServiceAccountPermissionLevel: storage.PermissionLevel_CLUSTER_ADMIN, + AutomountServiceAccountToken: true, + HostNetwork: false, + Containers: []*storage.Container{ + { + Name: "nginx", + Image: &storage.ContainerImage{ + Name: &storage.ImageName{FullName: "docker.io/nginx:1.25"}, + }, + SecurityContext: &storage.SecurityContext{ + Privileged: true, + AllowPrivilegeEscalation: true, + }, + Resources: &storage.Resources{ + CpuCoresRequest: 0.5, + CpuCoresLimit: 1.0, + MemoryMbRequest: 256, + MemoryMbLimit: 512, + }, + Config: &storage.ContainerConfig{ + Uid: 0, + Env: []*storage.ContainerConfig_EnvironmentConfig{ + {Key: "SECRET_KEY", Value: "super-secret-123"}, + {Key: "DB_URL", Value: "postgres://db.internal:5432/mydb"}, // #nosec G101 + }, + }, + LivenessProbe: &storage.LivenessProbe{Defined: true}, + ReadinessProbe: &storage.ReadinessProbe{Defined: false}, + Secrets: []*storage.EmbeddedSecret{ + {Name: "db-creds", Path: "/var/run/secrets/db"}, + }, + }, + }, + } + + risk := &storage.Risk{ + Id: "risk-1", + Score: 8.5, + Subject: &storage.RiskSubject{ + Id: "dep-1", + Namespace: "production", + ClusterId: "cluster-1", + Type: storage.RiskSubjectType_DEPLOYMENT, + }, + Results: []*storage.Risk_Result{ + { + Name: "Service Configuration", + Score: 3.5, + Factors: []*storage.Risk_Result_Factor{ + {Message: "Service account is cluster-admin"}, + {Message: "Automount SA token enabled"}, + }, + }, + { + Name: "Image Vulnerabilities", + Score: 2.0, + Factors: []*storage.Risk_Result_Factor{ + {Message: "Image has 42 CVEs", Url: "https://example.com/cves"}, + }, + }, + }, + } + + mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-1").Return(deployment, true, nil) + mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) + + olsMock := &mockOLSClient{ + response: &olsClient.QueryResponse{ + Response: "SUMMARY\nThis deployment runs as cluster-admin with a privileged container.", + }, + } + + svc := &serviceImpl{ + datastore: mockDS, + risks: mockRisks, + lightspeedClient: olsMock, + } + + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-1"}) + require.NoError(t, err) + assert.Equal(t, "SUMMARY\nThis deployment runs as cluster-admin with a privileged container.", resp.GetSummary()) + + // Verify prompt was passed correctly. + assert.Equal(t, aiSummaryPrompt, olsMock.captured.Query) + assert.NotEmpty(t, olsMock.captured.Context) +} + +func TestGetDeploymentRiskAISummary_SensitiveFieldsStripped(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + mockRisks := riskMocks.NewMockDataStore(ctrl) + + deployment := &storage.Deployment{ + Id: "dep-2", + Name: "mid-server", + Namespace: "operations", + ClusterName: "cluster-west", + Type: "Deployment", + Containers: []*storage.Container{ + { + Name: "app", + Image: &storage.ContainerImage{ + Id: "sha256:abc123", + IdV2: "img-v2-id", + Name: &storage.ImageName{FullName: "registry.example.com/app:v2"}, + }, + Config: &storage.ContainerConfig{ + Uid: 1000, + Env: []*storage.ContainerConfig_EnvironmentConfig{ + {Key: "SERVICENOW_URL", Value: "https://company.service-now.com"}, + {Key: "SN_USER", Value: "admin"}, + {Key: "SN_SECRET_REF", Value: "vault:secret/data/sn-key"}, + }, + Command: []string{"/bin/app"}, + Args: []string{"--port=8080"}, + Directory: "/app", + }, + Secrets: []*storage.EmbeddedSecret{ + {Name: "sn-credentials", Path: "/var/run/secrets/servicenow"}, + }, + SecurityContext: &storage.SecurityContext{ + Privileged: false, + }, + }, + }, + } + + risk := &storage.Risk{ + Id: "risk-2", + Score: 4.0, + Subject: &storage.RiskSubject{ + Id: "dep-2", + Namespace: "operations", + }, + Results: []*storage.Risk_Result{ + { + Name: "Image Freshness", + Score: 2.0, + Factors: []*storage.Risk_Result_Factor{ + {Message: "Image is 180 days old", Url: ""}, + }, + }, + }, + } + + mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-2").Return(deployment, true, nil) + mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) + + var capturedContext string + olsMock := &mockOLSClient{ + response: &olsClient.QueryResponse{Response: "Low risk deployment."}, + } + + svc := &serviceImpl{ + datastore: mockDS, + risks: mockRisks, + lightspeedClient: olsMock, + } + + _, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-2"}) + require.NoError(t, err) + + capturedContext = olsMock.captured.Context + + // Sensitive env vars must NOT appear in context sent to LLM. + assert.NotContains(t, capturedContext, "SERVICENOW_URL") + assert.NotContains(t, capturedContext, "https://company.service-now.com") + assert.NotContains(t, capturedContext, "SN_USER") + assert.NotContains(t, capturedContext, "admin") + assert.NotContains(t, capturedContext, "SN_SECRET_REF") + assert.NotContains(t, capturedContext, "vault:secret/data/sn-key") + + // Secret mount paths must NOT appear. + assert.NotContains(t, capturedContext, "/var/run/secrets/servicenow") + assert.NotContains(t, capturedContext, "sn-credentials") + + // Internal IDs must NOT appear. + assert.NotContains(t, capturedContext, "sha256:abc123") + assert.NotContains(t, capturedContext, "img-v2-id") + assert.NotContains(t, capturedContext, "dep-2") // deployment ID + assert.NotContains(t, capturedContext, "risk-2") // risk ID + + // Command, args, and directory must NOT appear. + assert.NotContains(t, capturedContext, "/bin/app") + assert.NotContains(t, capturedContext, "--port=8080") + + // Relevant fields MUST appear. + assert.Contains(t, capturedContext, "mid-server") + assert.Contains(t, capturedContext, "operations") + assert.Contains(t, capturedContext, "cluster-west") + assert.Contains(t, capturedContext, "registry.example.com/app:v2") + assert.Contains(t, capturedContext, "Image is 180 days old") +} + +func TestGetDeploymentRiskAISummary_DeploymentNotFound(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + + mockDS.EXPECT().GetDeployment(gomock.Any(), "nonexistent").Return(nil, false, nil) + + svc := &serviceImpl{ + datastore: mockDS, + } + + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "nonexistent"}) + assert.Nil(t, resp) + assert.Error(t, err) + assert.Contains(t, err.Error(), "does not exist") +} + +func TestGetDeploymentRiskAISummary_OLSError(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + mockRisks := riskMocks.NewMockDataStore(ctrl) + + deployment := &storage.Deployment{ + Id: "dep-3", + Name: "test-app", + Namespace: "default", + ClusterName: "test-cluster", + Type: "Deployment", + } + risk := &storage.Risk{Score: 2.0} + + mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-3").Return(deployment, true, nil) + mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) + + olsMock := &mockOLSClient{ + err: errors.New("Lightspeed API returned HTTP 503"), + } + + svc := &serviceImpl{ + datastore: mockDS, + risks: mockRisks, + lightspeedClient: olsMock, + } + + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-3"}) + assert.Nil(t, resp) + require.Error(t, err) + + st, ok := status.FromError(err) + require.True(t, ok, "expected a gRPC status error") + assert.Equal(t, codes.Unavailable, st.Code()) + assert.Equal(t, "AI service unavailable", st.Message()) +} + +func TestGetDeploymentRiskAISummary_NilRisk(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + mockRisks := riskMocks.NewMockDataStore(ctrl) + + deployment := &storage.Deployment{ + Id: "dep-4", + Name: "new-app", + Namespace: "staging", + ClusterName: "staging-cluster", + Type: "Deployment", + } + + mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-4").Return(deployment, true, nil) + mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(nil, false, nil) + + olsMock := &mockOLSClient{ + response: &olsClient.QueryResponse{Response: "No significant risk factors identified."}, + } + + svc := &serviceImpl{ + datastore: mockDS, + risks: mockRisks, + lightspeedClient: olsMock, + } + + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-4"}) + require.NoError(t, err) + assert.Equal(t, "No significant risk factors identified.", resp.GetSummary()) +} + +func TestBuildSanitizedRiskContext_FieldSelection(t *testing.T) { + deployment := &storage.Deployment{ + Id: "id-should-be-stripped", + Hash: 12345, + Name: "my-deployment", + Namespace: "my-namespace", + NamespaceId: "ns-id-should-be-stripped", + ClusterId: "cluster-id-should-be-stripped", + ClusterName: "my-cluster", + Type: "Deployment", + Created: timestamppb.Now(), + ServiceAccount: "deployer-sa", + ServiceAccountPermissionLevel: storage.PermissionLevel_ELEVATED_IN_NAMESPACE, + AutomountServiceAccountToken: true, + HostNetwork: true, + HostPid: false, + HostIpc: false, + StateTimestamp: 999999, + ImagePullSecrets: []string{"registry-secret"}, + Tolerations: []*storage.Toleration{ + {Key: "node-role.kubernetes.io/master"}, + }, + Ports: []*storage.PortConfig{ + {Name: "http", ContainerPort: 8080}, + }, + Labels: map[string]string{"app": "web"}, + Annotations: map[string]string{"note": "test"}, + Containers: []*storage.Container{ + { + Id: "container-id-should-be-stripped", + Name: "web", + Image: &storage.ContainerImage{ + Id: "sha256:deadbeef", + IdV2: "idv2-stripped", + Name: &storage.ImageName{FullName: "quay.io/app:latest"}, + NotPullable: true, + IsClusterLocal: true, + }, + SecurityContext: &storage.SecurityContext{ + Privileged: true, + ReadOnlyRootFilesystem: true, + AllowPrivilegeEscalation: false, + DropCapabilities: []string{"ALL"}, + AddCapabilities: []string{"NET_BIND_SERVICE"}, + }, + Resources: &storage.Resources{ + CpuCoresRequest: 0.25, + CpuCoresLimit: 1.0, + MemoryMbRequest: 128, + MemoryMbLimit: 512, + }, + Config: &storage.ContainerConfig{ + Uid: 0, + Env: []*storage.ContainerConfig_EnvironmentConfig{ + {Key: "PASSWORD", Value: "secret123"}, + }, + Command: []string{"/entrypoint.sh"}, + Directory: "/app", + }, + LivenessProbe: &storage.LivenessProbe{Defined: true}, + ReadinessProbe: &storage.ReadinessProbe{Defined: true}, + Secrets: []*storage.EmbeddedSecret{ + {Name: "tls-cert", Path: "/etc/tls/certs"}, + }, + }, + }, + } + + risk := &storage.Risk{ + Id: "risk-id-stripped", + Score: 7.2, + Subject: &storage.RiskSubject{ + Id: "dep-subject", + Namespace: "my-namespace", + ClusterId: "cluster-1", + }, + Results: []*storage.Risk_Result{ + { + Name: "Policy Violations", + Score: 4.0, + Factors: []*storage.Risk_Result_Factor{ + {Message: "Privileged container detected", Url: "https://docs.example.com/priv"}, + {Message: "Running as root"}, + }, + }, + }, + } + + contextJSON, err := buildSanitizedRiskContext(deployment, risk) + require.NoError(t, err) + + // Parse to verify structure. + var parsed map[string]json.RawMessage + require.NoError(t, json.Unmarshal([]byte(contextJSON), &parsed)) + assert.Contains(t, string(parsed["deployment"]), "my-deployment") + assert.Contains(t, string(parsed["risk"]), "Policy Violations") + + // Fields that MUST be present. + assert.Contains(t, contextJSON, "my-deployment") + assert.Contains(t, contextJSON, "my-namespace") + assert.Contains(t, contextJSON, "my-cluster") + assert.Contains(t, contextJSON, "Deployment") + assert.Contains(t, contextJSON, "deployer-sa") + assert.Contains(t, contextJSON, "ELEVATED_IN_NAMESPACE") + assert.Contains(t, contextJSON, "quay.io/app:latest") + assert.Contains(t, contextJSON, "Privileged container detected") + assert.Contains(t, contextJSON, "Running as root") + + // Fields that MUST be stripped — IDs and metadata. + assert.NotContains(t, contextJSON, "id-should-be-stripped") + assert.NotContains(t, contextJSON, "ns-id-should-be-stripped") + assert.NotContains(t, contextJSON, "cluster-id-should-be-stripped") + assert.NotContains(t, contextJSON, "container-id-should-be-stripped") + assert.NotContains(t, contextJSON, "sha256:deadbeef") + assert.NotContains(t, contextJSON, "idv2-stripped") + assert.NotContains(t, contextJSON, "risk-id-stripped") + assert.NotContains(t, contextJSON, "dep-subject") + assert.NotContains(t, contextJSON, "999999") // stateTimestamp + + // Fields stripped for security. + assert.NotContains(t, contextJSON, "PASSWORD") + assert.NotContains(t, contextJSON, "secret123") + assert.NotContains(t, contextJSON, "/etc/tls/certs") + assert.NotContains(t, contextJSON, "tls-cert") + assert.NotContains(t, contextJSON, "/entrypoint.sh") + + // Wasteful fields stripped. + assert.NotContains(t, contextJSON, "registry-secret") // imagePullSecrets + assert.NotContains(t, contextJSON, "node-role.kubernetes.io/master") // tolerations + assert.NotContains(t, contextJSON, "https://docs.example.com/priv") // factor URLs + + // notPullable and isClusterLocal should not be included. + assert.NotContains(t, contextJSON, "notPullable") + assert.NotContains(t, contextJSON, "isClusterLocal") +} diff --git a/central/deployment/service/risk_sanitization.go b/central/deployment/service/risk_sanitization.go new file mode 100644 index 0000000000000..1c510d987c928 --- /dev/null +++ b/central/deployment/service/risk_sanitization.go @@ -0,0 +1,136 @@ +package service + +import ( + "encoding/json" + + "github.com/stackrox/rox/generated/storage" +) + +// buildSanitizedRiskContext produces a minimal JSON representation of the +// deployment and risk data suitable for sending to an external LLM. It keeps +// only fields relevant to risk analysis and strips sensitive data (env vars, +// secret paths) and wasteful metadata (IDs, hashes, empty fields). +func buildSanitizedRiskContext(deployment *storage.Deployment, risk *storage.Risk) (string, error) { + ctx := sanitizedContext{ + Deployment: sanitizeDeployment(deployment), + Risk: sanitizeRisk(risk), + } + data, err := json.Marshal(ctx) + if err != nil { + return "", err + } + return string(data), nil +} + +type sanitizedContext struct { + Deployment sanitizedDeployment `json:"deployment"` + Risk sanitizedRisk `json:"risk"` +} + +type sanitizedDeployment struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + ClusterName string `json:"clusterName"` + Type string `json:"type"` + Created string `json:"created,omitempty"` + ServiceAccount string `json:"serviceAccount,omitempty"` + ServiceAccountPermissionLevel string `json:"serviceAccountPermissionLevel,omitempty"` + AutomountServiceAccountToken bool `json:"automountServiceAccountToken"` + HostNetwork bool `json:"hostNetwork"` + HostPid bool `json:"hostPid"` + HostIpc bool `json:"hostIpc"` + Containers []sanitizedContainer `json:"containers"` +} + +type sanitizedContainer struct { + Name string `json:"name"` + ImageFullName string `json:"imageFullName,omitempty"` + SecurityContext *storage.SecurityContext `json:"securityContext,omitempty"` + Resources *storage.Resources `json:"resources,omitempty"` + UID int64 `json:"uid,omitempty"` + LivenessProbe *storage.LivenessProbe `json:"livenessProbe,omitempty"` + ReadinessProbe *storage.ReadinessProbe `json:"readinessProbe,omitempty"` +} + +type sanitizedRisk struct { + Score float32 `json:"score"` + Results []sanitizedRiskResult `json:"results,omitempty"` +} + +type sanitizedRiskResult struct { + Name string `json:"name"` + Score float32 `json:"score"` + Factors []sanitizedRiskFactor `json:"factors,omitempty"` +} + +type sanitizedRiskFactor struct { + Message string `json:"message"` +} + +func sanitizeDeployment(d *storage.Deployment) sanitizedDeployment { + sd := sanitizedDeployment{ + Name: d.GetName(), + Namespace: d.GetNamespace(), + ClusterName: d.GetClusterName(), + Type: d.GetType(), + ServiceAccount: d.GetServiceAccount(), + AutomountServiceAccountToken: d.GetAutomountServiceAccountToken(), + HostNetwork: d.GetHostNetwork(), + HostPid: d.GetHostPid(), + HostIpc: d.GetHostIpc(), + } + + if d.GetServiceAccountPermissionLevel() != storage.PermissionLevel_UNSET { + sd.ServiceAccountPermissionLevel = d.GetServiceAccountPermissionLevel().String() + } + + if d.GetCreated() != nil { + sd.Created = d.GetCreated().AsTime().Format("2006-01-02T15:04:05Z") + } + + for _, c := range d.GetContainers() { + sc := sanitizedContainer{ + Name: c.GetName(), + SecurityContext: c.GetSecurityContext(), + UID: c.GetConfig().GetUid(), + } + + if img := c.GetImage(); img != nil && img.GetName() != nil { + sc.ImageFullName = img.GetName().GetFullName() + } + + if res := c.GetResources(); res != nil { + sc.Resources = res + } + + sc.LivenessProbe = c.GetLivenessProbe() + sc.ReadinessProbe = c.GetReadinessProbe() + + sd.Containers = append(sd.Containers, sc) + } + return sd +} + +func sanitizeRisk(r *storage.Risk) sanitizedRisk { + if r == nil { + return sanitizedRisk{} + } + sr := sanitizedRisk{ + Score: r.GetScore(), + } + for _, result := range r.GetResults() { + srr := sanitizedRiskResult{ + Name: result.GetName(), + Score: result.GetScore(), + } + for _, factor := range result.GetFactors() { + if factor.GetMessage() != "" { + srr.Factors = append(srr.Factors, sanitizedRiskFactor{ + Message: factor.GetMessage(), + }) + } + } + sr.Results = append(sr.Results, srr) + } + return sr +} diff --git a/central/deployment/service/service.go b/central/deployment/service/service.go index 85a5236c9de5f..9918f55fdf9f5 100644 --- a/central/deployment/service/service.go +++ b/central/deployment/service/service.go @@ -4,6 +4,7 @@ import ( "context" "github.com/stackrox/rox/central/deployment/datastore" + olsClient "github.com/stackrox/rox/central/lightspeed/client" processBaselineDataStore "github.com/stackrox/rox/central/processbaseline/datastore" processBaselineResultsStore "github.com/stackrox/rox/central/processbaselineresults/datastore" processIndicatorDataStore "github.com/stackrox/rox/central/processindicator/datastore" @@ -24,7 +25,7 @@ type Service interface { // New returns a new Service instance using the given DataStore. func New(datastore datastore.DataStore, processIndicators processIndicatorDataStore.DataStore, processBaselines processBaselineDataStore.DataStore, - processBaselineResults processBaselineResultsStore.DataStore, risks riskDataStore.DataStore, manager riskManager.Manager) Service { + processBaselineResults processBaselineResultsStore.DataStore, risks riskDataStore.DataStore, manager riskManager.Manager, lightspeed olsClient.Client) Service { return &serviceImpl{ datastore: datastore, processIndicators: processIndicators, @@ -32,5 +33,6 @@ func New(datastore datastore.DataStore, processIndicators processIndicatorDataSt processBaselineResults: processBaselineResults, risks: risks, manager: manager, + lightspeedClient: lightspeed, } } diff --git a/central/deployment/service/service_impl.go b/central/deployment/service/service_impl.go index 6cefc43bc7aef..447031681e6c2 100644 --- a/central/deployment/service/service_impl.go +++ b/central/deployment/service/service_impl.go @@ -9,6 +9,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/pkg/errors" "github.com/stackrox/rox/central/deployment/datastore" + olsClient "github.com/stackrox/rox/central/lightspeed/client" processBaselineStore "github.com/stackrox/rox/central/processbaseline/datastore" processBaselineResultsStore "github.com/stackrox/rox/central/processbaselineresults/datastore" processIndicatorStore "github.com/stackrox/rox/central/processindicator/datastore" @@ -21,6 +22,7 @@ import ( "github.com/stackrox/rox/pkg/grpc/authz" "github.com/stackrox/rox/pkg/grpc/authz/perrpc" "github.com/stackrox/rox/pkg/grpc/authz/user" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/search/options/deployments" @@ -29,17 +31,22 @@ import ( "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/utils" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const ( maxDeploymentsReturned = 1000 ) +var log = logging.LoggerForModule() + var ( authorizer = perrpc.FromMap(map[authz.Authorizer][]string{ user.With(permissions.View(resources.Deployment)): { v1.DeploymentService_GetDeployment_FullMethodName, v1.DeploymentService_GetDeploymentWithRisk_FullMethodName, + v1.DeploymentService_GetDeploymentRiskAISummary_FullMethodName, v1.DeploymentService_CountDeployments_FullMethodName, v1.DeploymentService_ListDeployments_FullMethodName, v1.DeploymentService_GetLabels_FullMethodName, @@ -60,6 +67,7 @@ type serviceImpl struct { processBaselineResults processBaselineResultsStore.DataStore risks riskDataStore.DataStore manager manager.Manager + lightspeedClient olsClient.Client } func (s *serviceImpl) ExportDeployments(req *v1.ExportDeploymentRequest, srv v1.DeploymentService_ExportDeploymentsServer) error { @@ -279,3 +287,37 @@ func labelsMapFromSearchResults(results []search.Result) (map[string]*v1.Deploym return keyValuesMap, values } + +// GetDeploymentRiskAISummary returns an AI-generated risk summary for a deployment. +func (s *serviceImpl) GetDeploymentRiskAISummary(ctx context.Context, request *v1.ResourceByID) (*v1.DeploymentRiskAISummaryResponse, error) { + deployment, exists, err := s.datastore.GetDeployment(ctx, request.GetId()) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.Wrapf(errox.NotFound, "deployment with id '%s' does not exist", request.GetId()) + } + + risk, _, err := s.risks.GetRiskForDeployment(ctx, deployment) + if err != nil { + return nil, err + } + + contextJSON, err := buildSanitizedRiskContext(deployment, risk) + if err != nil { + return nil, errors.Wrap(err, "building risk context for AI summary") + } + + olsResp, err := s.lightspeedClient.Query(ctx, &olsClient.QueryRequest{ + Query: aiSummaryPrompt, + Context: contextJSON, + }) + if err != nil { + log.Errorf("Lightspeed query failed for deployment %s: %v", request.GetId(), err) + return nil, status.Error(codes.Unavailable, "AI service unavailable") + } + + return &v1.DeploymentRiskAISummaryResponse{ + Summary: olsResp.Response, + }, nil +} diff --git a/central/deployment/service/service_impl_benchmark_test.go b/central/deployment/service/service_impl_benchmark_test.go index 7d6af56a21247..a2daed471bb34 100644 --- a/central/deployment/service/service_impl_benchmark_test.go +++ b/central/deployment/service/service_impl_benchmark_test.go @@ -21,7 +21,7 @@ func BenchmarkService_Export(b *testing.B) { if err != nil { b.Error(err) } - svc := New(testHelper.Deployments, nil, nil, nil, nil, nil) + svc := New(testHelper.Deployments, nil, nil, nil, nil, nil, nil) benchmarkFunc := getExportServiceBenchmark(testHelper, svc) testHelper.InjectDataAndRunBenchmark(b, false, benchmarkFunc) } diff --git a/central/deployment/service/singleton.go b/central/deployment/service/singleton.go index 9453236863d01..e3d1042365d6d 100644 --- a/central/deployment/service/singleton.go +++ b/central/deployment/service/singleton.go @@ -2,6 +2,7 @@ package service import ( "github.com/stackrox/rox/central/deployment/datastore" + olsClient "github.com/stackrox/rox/central/lightspeed/client" processBaselineDataStore "github.com/stackrox/rox/central/processbaseline/datastore" processBaselineResultsStore "github.com/stackrox/rox/central/processbaselineresults/datastore" processIndicatorDataStore "github.com/stackrox/rox/central/processindicator/datastore" @@ -17,7 +18,7 @@ var ( ) func initialize() { - as = New(datastore.Singleton(), processIndicatorDataStore.Singleton(), processBaselineDataStore.Singleton(), processBaselineResultsStore.Singleton(), riskDataStore.Singleton(), manager.Singleton()) + as = New(datastore.Singleton(), processIndicatorDataStore.Singleton(), processBaselineDataStore.Singleton(), processBaselineResultsStore.Singleton(), riskDataStore.Singleton(), manager.Singleton(), olsClient.NewClient()) } // Singleton provides the instance of the Service interface to register. diff --git a/central/lightspeed/client/client.go b/central/lightspeed/client/client.go new file mode 100644 index 0000000000000..975d2679c0826 --- /dev/null +++ b/central/lightspeed/client/client.go @@ -0,0 +1,121 @@ +package client + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "os" + "time" + + "github.com/pkg/errors" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/httputil/proxy" + "github.com/stackrox/rox/pkg/satoken" + "github.com/stackrox/rox/pkg/utils" +) + +const ( + serviceOperatorCAPath = "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" + defaultRequestTimeout = 30 * time.Second +) + +var ( + // LightspeedEndpoint is the URL of the OpenShift Lightspeed API. URL will be populated from integration in a follow up PR + LightspeedEndpoint = env.RegisterSetting("ROX_LIGHTSPEED_ENDPOINT", + env.WithDefault("https://lightspeed-app-server.openshift-lightspeed.svc:8443")) +) + +// QueryRequest is the request payload for the OLS query API. +type QueryRequest struct { + Query string `json:"query"` + Context string `json:"context,omitempty"` +} + +// QueryResponse is the response payload from the OLS query API. +type QueryResponse struct { + Response string `json:"response"` +} + +// Client calls the OpenShift Lightspeed API. +type Client interface { + Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error) +} + +// NewClient creates a Client that authenticates with the OLS API using the +// pod's Kubernetes service account token and trusts the service CA. +func NewClient() Client { + return &clientImpl{ + loadToken: satoken.LoadTokenFromFile, + endpoint: LightspeedEndpoint.Setting(), + httpClient: &http.Client{ + Transport: transportWithServiceCA(), + Timeout: defaultRequestTimeout, + }, + } +} + +type clientImpl struct { + loadToken func() (string, error) + endpoint string + httpClient *http.Client +} + +func transportWithServiceCA() http.RoundTripper { + rootCAs, err := x509.SystemCertPool() + if err != nil { + rootCAs = x509.NewCertPool() + } + + if serviceCA, err := os.ReadFile(serviceOperatorCAPath); err == nil { + rootCAs.AppendCertsFromPEM(serviceCA) + } + + return &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAs, + }, + Proxy: proxy.FromConfig(), + } +} + +func (c *clientImpl) Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error) { + token, err := c.loadToken() + if err != nil { + return nil, errors.Wrap(err, "loading service account token") + } + + body, err := json.Marshal(req) + if err != nil { + return nil, errors.Wrap(err, "marshaling request") + } + + url := fmt.Sprintf("%s/v1/query", c.endpoint) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return nil, errors.Wrap(err, "building HTTP request") + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, errors.Wrap(err, "sending request to Lightspeed") + } + defer utils.IgnoreError(resp.Body.Close) + + if resp.StatusCode >= 300 { + return nil, fmt.Errorf("Lightspeed API returned HTTP %d", resp.StatusCode) + } + + var result QueryResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, errors.Wrap(err, "decoding Lightspeed response") + } + + return &result, nil +} diff --git a/generated/api/v1/deployment_service.pb.go b/generated/api/v1/deployment_service.pb.go index 1d50e671d94d0..2003993071140 100644 --- a/generated/api/v1/deployment_service.pb.go +++ b/generated/api/v1/deployment_service.pb.go @@ -355,6 +355,50 @@ func (x *ExportDeploymentResponse) GetDeployment() *storage.Deployment { return nil } +type DeploymentRiskAISummaryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Summary string `protobuf:"bytes,1,opt,name=summary,proto3" json:"summary,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeploymentRiskAISummaryResponse) Reset() { + *x = DeploymentRiskAISummaryResponse{} + mi := &file_api_v1_deployment_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeploymentRiskAISummaryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeploymentRiskAISummaryResponse) ProtoMessage() {} + +func (x *DeploymentRiskAISummaryResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_deployment_service_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeploymentRiskAISummaryResponse.ProtoReflect.Descriptor instead. +func (*DeploymentRiskAISummaryResponse) Descriptor() ([]byte, []int) { + return file_api_v1_deployment_service_proto_rawDescGZIP(), []int{7} +} + +func (x *DeploymentRiskAISummaryResponse) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + type DeploymentLabelsResponse_LabelValues struct { state protoimpl.MessageState `protogen:"open.v1"` Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` @@ -364,7 +408,7 @@ type DeploymentLabelsResponse_LabelValues struct { func (x *DeploymentLabelsResponse_LabelValues) Reset() { *x = DeploymentLabelsResponse_LabelValues{} - mi := &file_api_v1_deployment_service_proto_msgTypes[7] + mi := &file_api_v1_deployment_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -376,7 +420,7 @@ func (x *DeploymentLabelsResponse_LabelValues) String() string { func (*DeploymentLabelsResponse_LabelValues) ProtoMessage() {} func (x *DeploymentLabelsResponse_LabelValues) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_deployment_service_proto_msgTypes[7] + mi := &file_api_v1_deployment_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -409,7 +453,7 @@ type ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo struct { func (x *ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo) Reset() { *x = ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo{} - mi := &file_api_v1_deployment_service_proto_msgTypes[9] + mi := &file_api_v1_deployment_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -421,7 +465,7 @@ func (x *ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo) Strin func (*ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo) ProtoMessage() {} func (x *ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_deployment_service_proto_msgTypes[9] + mi := &file_api_v1_deployment_service_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -486,10 +530,13 @@ const file_api_v1_deployment_service_proto_rawDesc = "" + "\x18ExportDeploymentResponse\x123\n" + "\n" + "deployment\x18\x01 \x01(\v2\x13.storage.DeploymentR\n" + - "deployment2\xe8\x05\n" + + "deployment\";\n" + + "\x1fDeploymentRiskAISummaryResponse\x12\x18\n" + + "\asummary\x18\x01 \x01(\tR\asummary2\xef\x06\n" + "\x11DeploymentService\x12T\n" + "\rGetDeployment\x12\x10.v1.ResourceByID\x1a\x13.storage.Deployment\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/v1/deployments/{id}\x12r\n" + - "\x15GetDeploymentWithRisk\x12\x10.v1.ResourceByID\x1a!.v1.GetDeploymentWithRiskResponse\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/deploymentswithrisk/{id}\x12\\\n" + + "\x15GetDeploymentWithRisk\x12\x10.v1.ResourceByID\x1a!.v1.GetDeploymentWithRiskResponse\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/deploymentswithrisk/{id}\x12\x84\x01\n" + + "\x1aGetDeploymentRiskAISummary\x12\x10.v1.ResourceByID\x1a#.v1.DeploymentRiskAISummaryResponse\"/\x82\xd3\xe4\x93\x02)\x12'/v1/deploymentswithrisk/{id}/ai-summary\x12\\\n" + "\x10CountDeployments\x12\f.v1.RawQuery\x1a\x1c.v1.CountDeploymentsResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/v1/deploymentscount\x12U\n" + "\x0fListDeployments\x12\f.v1.RawQuery\x1a\x1b.v1.ListDeploymentsResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/deployments\x12\x82\x01\n" + "\x1eListDeploymentsWithProcessInfo\x12\f.v1.RawQuery\x1a*.v1.ListDeploymentsWithProcessInfoResponse\"&\x82\xd3\xe4\x93\x02 \x12\x1e/v1/deploymentswithprocessinfo\x12]\n" + @@ -509,7 +556,7 @@ func file_api_v1_deployment_service_proto_rawDescGZIP() []byte { return file_api_v1_deployment_service_proto_rawDescData } -var file_api_v1_deployment_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_api_v1_deployment_service_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_api_v1_deployment_service_proto_goTypes = []any{ (*DeploymentLabelsResponse)(nil), // 0: v1.DeploymentLabelsResponse (*ListDeploymentsResponse)(nil), // 1: v1.ListDeploymentsResponse @@ -518,43 +565,46 @@ var file_api_v1_deployment_service_proto_goTypes = []any{ (*GetDeploymentWithRiskResponse)(nil), // 4: v1.GetDeploymentWithRiskResponse (*ExportDeploymentRequest)(nil), // 5: v1.ExportDeploymentRequest (*ExportDeploymentResponse)(nil), // 6: v1.ExportDeploymentResponse - (*DeploymentLabelsResponse_LabelValues)(nil), // 7: v1.DeploymentLabelsResponse.LabelValues - nil, // 8: v1.DeploymentLabelsResponse.LabelsEntry - (*ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo)(nil), // 9: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo - (*storage.ListDeployment)(nil), // 10: storage.ListDeployment - (*storage.Deployment)(nil), // 11: storage.Deployment - (*storage.Risk)(nil), // 12: storage.Risk - (*storage.ContainerNameAndBaselineStatus)(nil), // 13: storage.ContainerNameAndBaselineStatus - (*ResourceByID)(nil), // 14: v1.ResourceByID - (*RawQuery)(nil), // 15: v1.RawQuery - (*Empty)(nil), // 16: v1.Empty + (*DeploymentRiskAISummaryResponse)(nil), // 7: v1.DeploymentRiskAISummaryResponse + (*DeploymentLabelsResponse_LabelValues)(nil), // 8: v1.DeploymentLabelsResponse.LabelValues + nil, // 9: v1.DeploymentLabelsResponse.LabelsEntry + (*ListDeploymentsWithProcessInfoResponse_DeploymentWithProcessInfo)(nil), // 10: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo + (*storage.ListDeployment)(nil), // 11: storage.ListDeployment + (*storage.Deployment)(nil), // 12: storage.Deployment + (*storage.Risk)(nil), // 13: storage.Risk + (*storage.ContainerNameAndBaselineStatus)(nil), // 14: storage.ContainerNameAndBaselineStatus + (*ResourceByID)(nil), // 15: v1.ResourceByID + (*RawQuery)(nil), // 16: v1.RawQuery + (*Empty)(nil), // 17: v1.Empty } var file_api_v1_deployment_service_proto_depIdxs = []int32{ - 8, // 0: v1.DeploymentLabelsResponse.labels:type_name -> v1.DeploymentLabelsResponse.LabelsEntry - 10, // 1: v1.ListDeploymentsResponse.deployments:type_name -> storage.ListDeployment - 9, // 2: v1.ListDeploymentsWithProcessInfoResponse.deployments:type_name -> v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo - 11, // 3: v1.GetDeploymentWithRiskResponse.deployment:type_name -> storage.Deployment - 12, // 4: v1.GetDeploymentWithRiskResponse.risk:type_name -> storage.Risk - 11, // 5: v1.ExportDeploymentResponse.deployment:type_name -> storage.Deployment - 7, // 6: v1.DeploymentLabelsResponse.LabelsEntry.value:type_name -> v1.DeploymentLabelsResponse.LabelValues - 10, // 7: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo.deployment:type_name -> storage.ListDeployment - 13, // 8: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo.baseline_statuses:type_name -> storage.ContainerNameAndBaselineStatus - 14, // 9: v1.DeploymentService.GetDeployment:input_type -> v1.ResourceByID - 14, // 10: v1.DeploymentService.GetDeploymentWithRisk:input_type -> v1.ResourceByID - 15, // 11: v1.DeploymentService.CountDeployments:input_type -> v1.RawQuery - 15, // 12: v1.DeploymentService.ListDeployments:input_type -> v1.RawQuery - 15, // 13: v1.DeploymentService.ListDeploymentsWithProcessInfo:input_type -> v1.RawQuery - 16, // 14: v1.DeploymentService.GetLabels:input_type -> v1.Empty - 5, // 15: v1.DeploymentService.ExportDeployments:input_type -> v1.ExportDeploymentRequest - 11, // 16: v1.DeploymentService.GetDeployment:output_type -> storage.Deployment - 4, // 17: v1.DeploymentService.GetDeploymentWithRisk:output_type -> v1.GetDeploymentWithRiskResponse - 2, // 18: v1.DeploymentService.CountDeployments:output_type -> v1.CountDeploymentsResponse - 1, // 19: v1.DeploymentService.ListDeployments:output_type -> v1.ListDeploymentsResponse - 3, // 20: v1.DeploymentService.ListDeploymentsWithProcessInfo:output_type -> v1.ListDeploymentsWithProcessInfoResponse - 0, // 21: v1.DeploymentService.GetLabels:output_type -> v1.DeploymentLabelsResponse - 6, // 22: v1.DeploymentService.ExportDeployments:output_type -> v1.ExportDeploymentResponse - 16, // [16:23] is the sub-list for method output_type - 9, // [9:16] is the sub-list for method input_type + 9, // 0: v1.DeploymentLabelsResponse.labels:type_name -> v1.DeploymentLabelsResponse.LabelsEntry + 11, // 1: v1.ListDeploymentsResponse.deployments:type_name -> storage.ListDeployment + 10, // 2: v1.ListDeploymentsWithProcessInfoResponse.deployments:type_name -> v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo + 12, // 3: v1.GetDeploymentWithRiskResponse.deployment:type_name -> storage.Deployment + 13, // 4: v1.GetDeploymentWithRiskResponse.risk:type_name -> storage.Risk + 12, // 5: v1.ExportDeploymentResponse.deployment:type_name -> storage.Deployment + 8, // 6: v1.DeploymentLabelsResponse.LabelsEntry.value:type_name -> v1.DeploymentLabelsResponse.LabelValues + 11, // 7: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo.deployment:type_name -> storage.ListDeployment + 14, // 8: v1.ListDeploymentsWithProcessInfoResponse.DeploymentWithProcessInfo.baseline_statuses:type_name -> storage.ContainerNameAndBaselineStatus + 15, // 9: v1.DeploymentService.GetDeployment:input_type -> v1.ResourceByID + 15, // 10: v1.DeploymentService.GetDeploymentWithRisk:input_type -> v1.ResourceByID + 15, // 11: v1.DeploymentService.GetDeploymentRiskAISummary:input_type -> v1.ResourceByID + 16, // 12: v1.DeploymentService.CountDeployments:input_type -> v1.RawQuery + 16, // 13: v1.DeploymentService.ListDeployments:input_type -> v1.RawQuery + 16, // 14: v1.DeploymentService.ListDeploymentsWithProcessInfo:input_type -> v1.RawQuery + 17, // 15: v1.DeploymentService.GetLabels:input_type -> v1.Empty + 5, // 16: v1.DeploymentService.ExportDeployments:input_type -> v1.ExportDeploymentRequest + 12, // 17: v1.DeploymentService.GetDeployment:output_type -> storage.Deployment + 4, // 18: v1.DeploymentService.GetDeploymentWithRisk:output_type -> v1.GetDeploymentWithRiskResponse + 7, // 19: v1.DeploymentService.GetDeploymentRiskAISummary:output_type -> v1.DeploymentRiskAISummaryResponse + 2, // 20: v1.DeploymentService.CountDeployments:output_type -> v1.CountDeploymentsResponse + 1, // 21: v1.DeploymentService.ListDeployments:output_type -> v1.ListDeploymentsResponse + 3, // 22: v1.DeploymentService.ListDeploymentsWithProcessInfo:output_type -> v1.ListDeploymentsWithProcessInfoResponse + 0, // 23: v1.DeploymentService.GetLabels:output_type -> v1.DeploymentLabelsResponse + 6, // 24: v1.DeploymentService.ExportDeployments:output_type -> v1.ExportDeploymentResponse + 17, // [17:25] is the sub-list for method output_type + 9, // [9:17] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name @@ -574,7 +624,7 @@ func file_api_v1_deployment_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_deployment_service_proto_rawDesc), len(file_api_v1_deployment_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/generated/api/v1/deployment_service.pb.gw.go b/generated/api/v1/deployment_service.pb.gw.go index 2e57466fb729a..004e1ad0b7d70 100644 --- a/generated/api/v1/deployment_service.pb.gw.go +++ b/generated/api/v1/deployment_service.pb.gw.go @@ -113,6 +113,45 @@ func local_request_DeploymentService_GetDeploymentWithRisk_0(ctx context.Context return msg, metadata, err } +func request_DeploymentService_GetDeploymentRiskAISummary_0(ctx context.Context, marshaler runtime.Marshaler, client DeploymentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.GetDeploymentRiskAISummary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_DeploymentService_GetDeploymentRiskAISummary_0(ctx context.Context, marshaler runtime.Marshaler, server DeploymentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ResourceByID + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.GetDeploymentRiskAISummary(ctx, &protoReq) + return msg, metadata, err +} + var filter_DeploymentService_CountDeployments_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} func request_DeploymentService_CountDeployments_0(ctx context.Context, marshaler runtime.Marshaler, client DeploymentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { @@ -313,6 +352,26 @@ func RegisterDeploymentServiceHandlerServer(ctx context.Context, mux *runtime.Se } forward_DeploymentService_GetDeploymentWithRisk_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_DeploymentService_GetDeploymentRiskAISummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v1.DeploymentService/GetDeploymentRiskAISummary", runtime.WithHTTPPathPattern("/v1/deploymentswithrisk/{id}/ai-summary")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_DeploymentService_GetDeploymentRiskAISummary_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DeploymentService_GetDeploymentRiskAISummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_DeploymentService_CountDeployments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -474,6 +533,23 @@ func RegisterDeploymentServiceHandlerClient(ctx context.Context, mux *runtime.Se } forward_DeploymentService_GetDeploymentWithRisk_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_DeploymentService_GetDeploymentRiskAISummary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v1.DeploymentService/GetDeploymentRiskAISummary", runtime.WithHTTPPathPattern("/v1/deploymentswithrisk/{id}/ai-summary")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_DeploymentService_GetDeploymentRiskAISummary_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DeploymentService_GetDeploymentRiskAISummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_DeploymentService_CountDeployments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -565,6 +641,7 @@ func RegisterDeploymentServiceHandlerClient(ctx context.Context, mux *runtime.Se var ( pattern_DeploymentService_GetDeployment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "deployments", "id"}, "")) pattern_DeploymentService_GetDeploymentWithRisk_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "deploymentswithrisk", "id"}, "")) + pattern_DeploymentService_GetDeploymentRiskAISummary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1", "deploymentswithrisk", "id", "ai-summary"}, "")) pattern_DeploymentService_CountDeployments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "deploymentscount"}, "")) pattern_DeploymentService_ListDeployments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "deployments"}, "")) pattern_DeploymentService_ListDeploymentsWithProcessInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "deploymentswithprocessinfo"}, "")) @@ -575,6 +652,7 @@ var ( var ( forward_DeploymentService_GetDeployment_0 = runtime.ForwardResponseMessage forward_DeploymentService_GetDeploymentWithRisk_0 = runtime.ForwardResponseMessage + forward_DeploymentService_GetDeploymentRiskAISummary_0 = runtime.ForwardResponseMessage forward_DeploymentService_CountDeployments_0 = runtime.ForwardResponseMessage forward_DeploymentService_ListDeployments_0 = runtime.ForwardResponseMessage forward_DeploymentService_ListDeploymentsWithProcessInfo_0 = runtime.ForwardResponseMessage diff --git a/generated/api/v1/deployment_service.swagger.json b/generated/api/v1/deployment_service.swagger.json index babf77c2d9216..f5e9523e7e413 100644 --- a/generated/api/v1/deployment_service.swagger.json +++ b/generated/api/v1/deployment_service.swagger.json @@ -329,6 +329,37 @@ ] } }, + "/v1/deploymentswithrisk/{id}/ai-summary": { + "get": { + "summary": "GetDeploymentRiskAISummary returns an AI-generated risk summary for a deployment.", + "operationId": "DeploymentService_GetDeploymentRiskAISummary", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeploymentRiskAISummaryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googleRpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "DeploymentService" + ] + } + }, "/v1/export/deployments": { "get": { "operationId": "DeploymentService_ExportDeployments", @@ -1250,6 +1281,14 @@ } } }, + "v1DeploymentRiskAISummaryResponse": { + "type": "object", + "properties": { + "summary": { + "type": "string" + } + } + }, "v1ExportDeploymentResponse": { "type": "object", "properties": { diff --git a/generated/api/v1/deployment_service_grpc.pb.go b/generated/api/v1/deployment_service_grpc.pb.go index b261e0e7c1a1d..caa666bb3286c 100644 --- a/generated/api/v1/deployment_service_grpc.pb.go +++ b/generated/api/v1/deployment_service_grpc.pb.go @@ -22,6 +22,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( DeploymentService_GetDeployment_FullMethodName = "/v1.DeploymentService/GetDeployment" DeploymentService_GetDeploymentWithRisk_FullMethodName = "/v1.DeploymentService/GetDeploymentWithRisk" + DeploymentService_GetDeploymentRiskAISummary_FullMethodName = "/v1.DeploymentService/GetDeploymentRiskAISummary" DeploymentService_CountDeployments_FullMethodName = "/v1.DeploymentService/CountDeployments" DeploymentService_ListDeployments_FullMethodName = "/v1.DeploymentService/ListDeployments" DeploymentService_ListDeploymentsWithProcessInfo_FullMethodName = "/v1.DeploymentService/ListDeploymentsWithProcessInfo" @@ -39,6 +40,8 @@ type DeploymentServiceClient interface { GetDeployment(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*storage.Deployment, error) // GetDeploymentWithRisk returns a deployment and its risk given its ID. GetDeploymentWithRisk(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*GetDeploymentWithRiskResponse, error) + // GetDeploymentRiskAISummary returns an AI-generated risk summary for a deployment. + GetDeploymentRiskAISummary(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*DeploymentRiskAISummaryResponse, error) // CountDeployments returns the number of deployments. CountDeployments(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*CountDeploymentsResponse, error) // ListDeployments returns the list of deployments. @@ -78,6 +81,16 @@ func (c *deploymentServiceClient) GetDeploymentWithRisk(ctx context.Context, in return out, nil } +func (c *deploymentServiceClient) GetDeploymentRiskAISummary(ctx context.Context, in *ResourceByID, opts ...grpc.CallOption) (*DeploymentRiskAISummaryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeploymentRiskAISummaryResponse) + err := c.cc.Invoke(ctx, DeploymentService_GetDeploymentRiskAISummary_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *deploymentServiceClient) CountDeployments(ctx context.Context, in *RawQuery, opts ...grpc.CallOption) (*CountDeploymentsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CountDeploymentsResponse) @@ -147,6 +160,8 @@ type DeploymentServiceServer interface { GetDeployment(context.Context, *ResourceByID) (*storage.Deployment, error) // GetDeploymentWithRisk returns a deployment and its risk given its ID. GetDeploymentWithRisk(context.Context, *ResourceByID) (*GetDeploymentWithRiskResponse, error) + // GetDeploymentRiskAISummary returns an AI-generated risk summary for a deployment. + GetDeploymentRiskAISummary(context.Context, *ResourceByID) (*DeploymentRiskAISummaryResponse, error) // CountDeployments returns the number of deployments. CountDeployments(context.Context, *RawQuery) (*CountDeploymentsResponse, error) // ListDeployments returns the list of deployments. @@ -171,6 +186,9 @@ func (UnimplementedDeploymentServiceServer) GetDeployment(context.Context, *Reso func (UnimplementedDeploymentServiceServer) GetDeploymentWithRisk(context.Context, *ResourceByID) (*GetDeploymentWithRiskResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetDeploymentWithRisk not implemented") } +func (UnimplementedDeploymentServiceServer) GetDeploymentRiskAISummary(context.Context, *ResourceByID) (*DeploymentRiskAISummaryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDeploymentRiskAISummary not implemented") +} func (UnimplementedDeploymentServiceServer) CountDeployments(context.Context, *RawQuery) (*CountDeploymentsResponse, error) { return nil, status.Error(codes.Unimplemented, "method CountDeployments not implemented") } @@ -242,6 +260,24 @@ func _DeploymentService_GetDeploymentWithRisk_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _DeploymentService_GetDeploymentRiskAISummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResourceByID) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeploymentServiceServer).GetDeploymentRiskAISummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DeploymentService_GetDeploymentRiskAISummary_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeploymentServiceServer).GetDeploymentRiskAISummary(ctx, req.(*ResourceByID)) + } + return interceptor(ctx, in, info, handler) +} + func _DeploymentService_CountDeployments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RawQuery) if err := dec(in); err != nil { @@ -340,6 +376,10 @@ var DeploymentService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetDeploymentWithRisk", Handler: _DeploymentService_GetDeploymentWithRisk_Handler, }, + { + MethodName: "GetDeploymentRiskAISummary", + Handler: _DeploymentService_GetDeploymentRiskAISummary_Handler, + }, { MethodName: "CountDeployments", Handler: _DeploymentService_CountDeployments_Handler, diff --git a/generated/api/v1/deployment_service_vtproto.pb.go b/generated/api/v1/deployment_service_vtproto.pb.go index 6b15be77cab20..0e20efed81604 100644 --- a/generated/api/v1/deployment_service_vtproto.pb.go +++ b/generated/api/v1/deployment_service_vtproto.pb.go @@ -248,6 +248,23 @@ func (m *ExportDeploymentResponse) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *DeploymentRiskAISummaryResponse) CloneVT() *DeploymentRiskAISummaryResponse { + if m == nil { + return (*DeploymentRiskAISummaryResponse)(nil) + } + r := new(DeploymentRiskAISummaryResponse) + r.Summary = m.Summary + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *DeploymentRiskAISummaryResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (this *DeploymentLabelsResponse_LabelValues) EqualVT(that *DeploymentLabelsResponse_LabelValues) bool { if this == that { return true @@ -536,6 +553,25 @@ func (this *ExportDeploymentResponse) EqualMessageVT(thatMsg proto.Message) bool } return this.EqualVT(that) } +func (this *DeploymentRiskAISummaryResponse) EqualVT(that *DeploymentRiskAISummaryResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Summary != that.Summary { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *DeploymentRiskAISummaryResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*DeploymentRiskAISummaryResponse) + if !ok { + return false + } + return this.EqualVT(that) +} func (m *DeploymentLabelsResponse_LabelValues) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1038,6 +1074,46 @@ func (m *ExportDeploymentResponse) MarshalToSizedBufferVT(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *DeploymentRiskAISummaryResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeploymentRiskAISummaryResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *DeploymentRiskAISummaryResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Summary) > 0 { + i -= len(m.Summary) + copy(dAtA[i:], m.Summary) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Summary))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *DeploymentLabelsResponse_LabelValues) SizeVT() (n int) { if m == nil { return 0 @@ -1233,6 +1309,20 @@ func (m *ExportDeploymentResponse) SizeVT() (n int) { return n } +func (m *DeploymentRiskAISummaryResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Summary) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *DeploymentLabelsResponse_LabelValues) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2249,6 +2339,89 @@ func (m *ExportDeploymentResponse) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *DeploymentRiskAISummaryResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeploymentRiskAISummaryResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentRiskAISummaryResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Summary", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Summary = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DeploymentLabelsResponse_LabelValues) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -3281,3 +3454,90 @@ func (m *ExportDeploymentResponse) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *DeploymentRiskAISummaryResponse) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeploymentRiskAISummaryResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeploymentRiskAISummaryResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Summary", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.Summary = stringValue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/proto/api/v1/deployment_service.proto b/proto/api/v1/deployment_service.proto index 09ad9de40870f..e88d70cea69e7 100644 --- a/proto/api/v1/deployment_service.proto +++ b/proto/api/v1/deployment_service.proto @@ -52,6 +52,10 @@ message ExportDeploymentResponse { storage.Deployment deployment = 1; } +message DeploymentRiskAISummaryResponse { + string summary = 1; +} + // DeploymentService APIs can be used to manage deployments. service DeploymentService { // GetDeployment returns a deployment given its ID. @@ -64,6 +68,11 @@ service DeploymentService { option (google.api.http) = {get: "/v1/deploymentswithrisk/{id}"}; } + // GetDeploymentRiskAISummary returns an AI-generated risk summary for a deployment. + rpc GetDeploymentRiskAISummary(ResourceByID) returns (DeploymentRiskAISummaryResponse) { + option (google.api.http) = {get: "/v1/deploymentswithrisk/{id}/ai-summary"}; + } + // CountDeployments returns the number of deployments. rpc CountDeployments(RawQuery) returns (CountDeploymentsResponse) { option (google.api.http) = {get: "/v1/deploymentscount"}; diff --git a/roxctl/central/export/deployments/deployments_test.go b/roxctl/central/export/deployments/deployments_test.go index 45f79a2264e54..276f48a0f1ec4 100644 --- a/roxctl/central/export/deployments/deployments_test.go +++ b/roxctl/central/export/deployments/deployments_test.go @@ -93,6 +93,10 @@ func (s *fakeDeploymentService) GetLabels(_ context.Context, _ *v1.Empty) (*v1.D return nil, errox.NotImplemented } +func (s *fakeDeploymentService) GetDeploymentRiskAISummary(_ context.Context, _ *v1.ResourceByID) (*v1.DeploymentRiskAISummaryResponse, error) { + return nil, errox.NotImplemented +} + // getDeploymentForSerializationTest returns a Mock Deployment for serialization testing purpose. func getDeploymentForSerializationTest() *storage.Deployment { deployment := fixtures.LightweightDeployment() From 081b72da79569bab6b332c354eb11a6131bb3eee Mon Sep 17 00:00:00 2001 From: David Vail Date: Mon, 31 Aug 2026 15:51:58 -0400 Subject: [PATCH 140/638] ROX-36377: Add AI risk summary UI plumbing (#22319) --- pkg/features/list.go | 6 +++ tests/e2e/lib.sh | 6 +++ .../src/services/AiIntegrationsService.ts | 45 +++++++++++++++++++ .../src/services/DeploymentsService.ts | 16 +++++++ ui/apps/platform/src/types/featureFlag.ts | 2 + 5 files changed, 75 insertions(+) create mode 100644 ui/apps/platform/src/services/AiIntegrationsService.ts diff --git a/pkg/features/list.go b/pkg/features/list.go index cee4a171d83f1..4a852f663bce6 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -151,6 +151,12 @@ var ( // ACMAccessControlDelegation enables AuthProviders with role lookup delegation to ACM access control information. ACMAccessControlDelegation = registerFeature("Enable ACS access control integration with Red Hat Advanced Cluster Management", "ROX_ACM_ACCESS_CONTROL_DELEGATION") + + // AIIntegrations enables AI integrations management + AIIntegrations = registerFeature("Enable AI integrations management", "ROX_AI_INTEGRATIONS") + + // LightspeedRiskSummary enables Lightspeed AI risk summary + LightspeedRiskSummary = registerFeature("Enable Lightspeed AI risk summary", "ROX_LIGHTSPEED_RISK_SUMMARY") ) // The following feature flags are related to Scanner V4. diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 466bbeeffd197..0f17518dafccd 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -445,6 +445,8 @@ export_test_environment() { ci_export ROX_INIT_CONTAINER_SUPPORT "${ROX_INIT_CONTAINER_SUPPORT:-true}" ci_export ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL "${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}" ci_export ROX_UI_SECRETS_PAGE_MIGRATION "${ROX_UI_SECRETS_PAGE_MIGRATION:-true}" + ci_export ROX_AI_INTEGRATIONS "${ROX_AI_INTEGRATIONS:-true}" + ci_export ROX_LIGHTSPEED_RISK_SUMMARY "${ROX_LIGHTSPEED_RISK_SUMMARY:-true}" ci_export SCANNER_V4_VULN_READINESS "${SCANNER_V4_VULN_READINESS:-true}" if is_in_PR_context && pr_has_label ci-fail-fast; then @@ -625,6 +627,10 @@ deploy_central_via_operator() { customize_envVars+=$'\n value: "'"${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}"'"' customize_envVars+=$'\n - name: ROX_UI_SECRETS_PAGE_MIGRATION' customize_envVars+=$'\n value: "'"${ROX_UI_SECRETS_PAGE_MIGRATION}"'"' + customize_envVars+=$'\n - name: ROX_AI_INTEGRATIONS' + customize_envVars+=$'\n value: "'"${ROX_AI_INTEGRATIONS}"'"' + customize_envVars+=$'\n - name: ROX_LIGHTSPEED_RISK_SUMMARY' + customize_envVars+=$'\n value: "'"${ROX_LIGHTSPEED_RISK_SUMMARY}"'"' if [[ "${ROX_VIRTUAL_MACHINES:-}" == "true" ]]; then customize_envVars+=$'\n - name: ROX_VIRTUAL_MACHINES' customize_envVars+=$'\n value: "true"' diff --git a/ui/apps/platform/src/services/AiIntegrationsService.ts b/ui/apps/platform/src/services/AiIntegrationsService.ts new file mode 100644 index 0000000000000..832aec8c52e25 --- /dev/null +++ b/ui/apps/platform/src/services/AiIntegrationsService.ts @@ -0,0 +1,45 @@ +import axios from './instance'; +import type { Empty } from './types'; + +const aiIntegrationsUrl = '/v2/ai-integrations'; + +export type AiIntegrationType = 'AI_INTEGRATION_TYPE_UNSPECIFIED' | 'AI_INTEGRATION_TYPE_OLS'; + +export type AiIntegration = { + id: string; + name: string; + type: AiIntegrationType; + serviceUrl: string; +}; + +type ListAiIntegrationsResponse = { + integrations: AiIntegration[]; +}; + +export function fetchAiIntegrations(): Promise { + return axios + .get(aiIntegrationsUrl) + .then((response) => response.data.integrations); +} + +export function fetchAiIntegration(id: string): Promise { + return axios.get(`${aiIntegrationsUrl}/${id}`).then((response) => response.data); +} + +export function createAiIntegration(data: AiIntegration): Promise { + return axios.post(aiIntegrationsUrl, data).then((response) => response.data); +} + +export function updateAiIntegration(data: AiIntegration): Promise { + return axios + .put(`${aiIntegrationsUrl}/${data.id}`, data) + .then((response) => response.data); +} + +export function deleteAiIntegration(id: string): Promise { + return axios.delete(`${aiIntegrationsUrl}/${id}`).then((response) => response.data); +} + +export function testAiIntegration(data: AiIntegration): Promise { + return axios.post(`${aiIntegrationsUrl}/test`, data).then((response) => response.data); +} diff --git a/ui/apps/platform/src/services/DeploymentsService.ts b/ui/apps/platform/src/services/DeploymentsService.ts index 40564d1394989..dbbf201879552 100644 --- a/ui/apps/platform/src/services/DeploymentsService.ts +++ b/ui/apps/platform/src/services/DeploymentsService.ts @@ -154,3 +154,19 @@ export type DeploymentWithRisk = { deployment: Deployment; risk?: Risk; }; + +export type DeploymentRiskSummary = { + summary: string; +}; + +/** + * Fetches an AI-generated risk summary for a deployment by ID. + */ +export function fetchDeploymentRiskSummary(id: string): Promise { + if (!id) { + throw new Error('Deployment ID must be specified'); + } + return axios + .get(`${deploymentWithRiskUrl}/${id}/ai-summary`) + .then((response) => response.data); +} diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index 8cc4f0cd42f32..000fd5b18b93f 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -2,6 +2,7 @@ // However, add strings in alphabetical order to minimize merge conflicts when multiple people add or delete strings. // prettier-ignore export type FeatureFlagEnvVar = + | 'ROX_AI_INTEGRATIONS' | 'ROX_BASE_IMAGE_DETECTION' | 'ROX_CISA_KEV' | 'ROX_CVE_FIX_TIMESTAMP' @@ -11,6 +12,7 @@ export type FeatureFlagEnvVar = | 'ROX_INIT_CONTAINER_SUPPORT' | 'ROX_LABEL_BASED_POLICY_SCOPING' | 'ROX_LEGACY_SCANNER' + | 'ROX_LIGHTSPEED_RISK_SUMMARY' | 'ROX_NODE_INDEX_ENABLED' | 'ROX_NODE_VULNERABILITY_REPORTS' | 'ROX_POLICY_CRITERIA_MODAL' From 2bdd164de37b5d4d3342cd1ba0bf46cce8fdee2e Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:27:12 -0500 Subject: [PATCH 141/638] ROX-35770: Ensure image upsert when duplicates removed (#22152) --- .../datastore/store/v2/postgres/store.go | 9 +- .../store/v2/postgres/store_hash_test.go | 108 ++++++++++++++++++ .../imagev2/datastore/store/postgres/store.go | 9 +- .../store/postgres/store_hash_test.go | 108 ++++++++++++++++++ 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 central/image/datastore/store/v2/postgres/store_hash_test.go create mode 100644 central/imagev2/datastore/store/postgres/store_hash_test.go diff --git a/central/image/datastore/store/v2/postgres/store.go b/central/image/datastore/store/v2/postgres/store.go index 5ffb370d854d1..d9abd944a4c44 100644 --- a/central/image/datastore/store/v2/postgres/store.go +++ b/central/image/datastore/store/v2/postgres/store.go @@ -423,10 +423,17 @@ func (s *storeImpl) isUpdated(oldImage, image *storage.Image) (bool, bool, error type hashWrapper struct { Components []*storage.EmbeddedImageScanComponent `hash:"set"` + Count int + VulnCount int } func populateImageScanHash(scan *storage.ImageScan) error { - hash, err := hashstructure.Hash(hashWrapper{scan.GetComponents()}, &hashstructure.HashOptions{ZeroNil: true}) + components := scan.GetComponents() + vulnCount := 0 + for _, c := range components { + vulnCount += len(c.GetVulns()) + } + hash, err := hashstructure.Hash(hashWrapper{Components: components, Count: len(components), VulnCount: vulnCount}, &hashstructure.HashOptions{ZeroNil: true}) if err != nil { return errors.Wrap(err, "calculating hash for image scan") } diff --git a/central/image/datastore/store/v2/postgres/store_hash_test.go b/central/image/datastore/store/v2/postgres/store_hash_test.go new file mode 100644 index 0000000000000..568bb7f32398f --- /dev/null +++ b/central/image/datastore/store/v2/postgres/store_hash_test.go @@ -0,0 +1,108 @@ +package postgres + +import ( + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPopulateImageScanHash_DuplicateComponentsChangeHash(t *testing.T) { + component := &storage.EmbeddedImageScanComponent{ + Name: "ubi10/ubi-micro", + Version: "1784668691", + } + + scanWith1 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{component}, + } + scanWith3 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{component, component, component}, + } + + require.NoError(t, populateImageScanHash(scanWith1)) + require.NoError(t, populateImageScanHash(scanWith3)) + + assert.NotEqual(t, scanWith1.GetHash(), scanWith3.GetHash(), + "removing duplicate components must produce a different hash") +} + +func TestPopulateImageScanHash_DuplicateVulnsChangeHash(t *testing.T) { + vuln := &storage.EmbeddedVulnerability{ + Cve: "CVE-2025-1234", + Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY, + } + + scanWith1Vuln := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{vuln}}, + }, + } + scanWith3Vulns := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{vuln, vuln, vuln}}, + }, + } + + require.NoError(t, populateImageScanHash(scanWith1Vuln)) + require.NoError(t, populateImageScanHash(scanWith3Vulns)) + + assert.NotEqual(t, scanWith1Vuln.GetHash(), scanWith3Vulns.GetHash(), + "removing duplicate vulns must produce a different hash") +} + +func TestPopulateImageScanHash_DistinctChangesDetected(t *testing.T) { + scanA := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-1111"}, + }}, + }, + } + scanB := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-2222"}, + }}, + }, + } + + require.NoError(t, populateImageScanHash(scanA)) + require.NoError(t, populateImageScanHash(scanB)) + + assert.NotEqual(t, scanA.GetHash(), scanB.GetHash(), + "different CVEs must produce a different hash") +} + +func TestPopulateImageScanHash_IdenticalScansMatch(t *testing.T) { + scan1 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg-a", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-1111"}, + }}, + {Name: "pkg-b", Version: "2.0"}, + }, + } + scan2 := scan1.CloneVT() + + require.NoError(t, populateImageScanHash(scan1)) + require.NoError(t, populateImageScanHash(scan2)) + + assert.Equal(t, scan1.GetHash(), scan2.GetHash(), + "identical scans must produce the same hash") +} + +func TestPopulateImageScanHash_OrderIndependent(t *testing.T) { + compA := &storage.EmbeddedImageScanComponent{Name: "aaa", Version: "1.0"} + compB := &storage.EmbeddedImageScanComponent{Name: "bbb", Version: "2.0"} + + scanAB := &storage.ImageScan{Components: []*storage.EmbeddedImageScanComponent{compA, compB}} + scanBA := &storage.ImageScan{Components: []*storage.EmbeddedImageScanComponent{compB, compA}} + + require.NoError(t, populateImageScanHash(scanAB)) + require.NoError(t, populateImageScanHash(scanBA)) + + assert.Equal(t, scanAB.GetHash(), scanBA.GetHash(), + "component order must not affect the hash") +} diff --git a/central/imagev2/datastore/store/postgres/store.go b/central/imagev2/datastore/store/postgres/store.go index 225bc9ade76a0..87c0382422b44 100644 --- a/central/imagev2/datastore/store/postgres/store.go +++ b/central/imagev2/datastore/store/postgres/store.go @@ -420,10 +420,17 @@ func (s *storeImpl) isUpdated(oldImage, image *storage.ImageV2) (bool, bool, err type hashWrapper struct { Components []*storage.EmbeddedImageScanComponent `hash:"set"` + Count int + VulnCount int } func populateImageScanHash(scan *storage.ImageScan) error { - hash, err := hashstructure.Hash(hashWrapper{scan.GetComponents()}, &hashstructure.HashOptions{ZeroNil: true}) + components := scan.GetComponents() + vulnCount := 0 + for _, c := range components { + vulnCount += len(c.GetVulns()) + } + hash, err := hashstructure.Hash(hashWrapper{Components: components, Count: len(components), VulnCount: vulnCount}, &hashstructure.HashOptions{ZeroNil: true}) if err != nil { return errors.Wrap(err, "calculating hash for image scan") } diff --git a/central/imagev2/datastore/store/postgres/store_hash_test.go b/central/imagev2/datastore/store/postgres/store_hash_test.go new file mode 100644 index 0000000000000..568bb7f32398f --- /dev/null +++ b/central/imagev2/datastore/store/postgres/store_hash_test.go @@ -0,0 +1,108 @@ +package postgres + +import ( + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPopulateImageScanHash_DuplicateComponentsChangeHash(t *testing.T) { + component := &storage.EmbeddedImageScanComponent{ + Name: "ubi10/ubi-micro", + Version: "1784668691", + } + + scanWith1 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{component}, + } + scanWith3 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{component, component, component}, + } + + require.NoError(t, populateImageScanHash(scanWith1)) + require.NoError(t, populateImageScanHash(scanWith3)) + + assert.NotEqual(t, scanWith1.GetHash(), scanWith3.GetHash(), + "removing duplicate components must produce a different hash") +} + +func TestPopulateImageScanHash_DuplicateVulnsChangeHash(t *testing.T) { + vuln := &storage.EmbeddedVulnerability{ + Cve: "CVE-2025-1234", + Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY, + } + + scanWith1Vuln := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{vuln}}, + }, + } + scanWith3Vulns := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{vuln, vuln, vuln}}, + }, + } + + require.NoError(t, populateImageScanHash(scanWith1Vuln)) + require.NoError(t, populateImageScanHash(scanWith3Vulns)) + + assert.NotEqual(t, scanWith1Vuln.GetHash(), scanWith3Vulns.GetHash(), + "removing duplicate vulns must produce a different hash") +} + +func TestPopulateImageScanHash_DistinctChangesDetected(t *testing.T) { + scanA := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-1111"}, + }}, + }, + } + scanB := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-2222"}, + }}, + }, + } + + require.NoError(t, populateImageScanHash(scanA)) + require.NoError(t, populateImageScanHash(scanB)) + + assert.NotEqual(t, scanA.GetHash(), scanB.GetHash(), + "different CVEs must produce a different hash") +} + +func TestPopulateImageScanHash_IdenticalScansMatch(t *testing.T) { + scan1 := &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "pkg-a", Version: "1.0", Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "CVE-2025-1111"}, + }}, + {Name: "pkg-b", Version: "2.0"}, + }, + } + scan2 := scan1.CloneVT() + + require.NoError(t, populateImageScanHash(scan1)) + require.NoError(t, populateImageScanHash(scan2)) + + assert.Equal(t, scan1.GetHash(), scan2.GetHash(), + "identical scans must produce the same hash") +} + +func TestPopulateImageScanHash_OrderIndependent(t *testing.T) { + compA := &storage.EmbeddedImageScanComponent{Name: "aaa", Version: "1.0"} + compB := &storage.EmbeddedImageScanComponent{Name: "bbb", Version: "2.0"} + + scanAB := &storage.ImageScan{Components: []*storage.EmbeddedImageScanComponent{compA, compB}} + scanBA := &storage.ImageScan{Components: []*storage.EmbeddedImageScanComponent{compB, compA}} + + require.NoError(t, populateImageScanHash(scanAB)) + require.NoError(t, populateImageScanHash(scanBA)) + + assert.Equal(t, scanAB.GetHash(), scanBA.GetHash(), + "component order must not affect the hash") +} From e0878af44c7ea5957d6c91dadbb3e8628f79357c Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Tue, 1 Sep 2026 13:06:42 +0200 Subject: [PATCH 142/638] ROX-36031: support ocp5-named profiles in benchmark grouping (#22031) Co-authored-by: Claude Opus 4.6 (1M context) --- central/complianceoperator/v2/benchmark/benchmark.go | 10 +++++----- .../v2/benchmark/fixtures/bsi-benchmark-2022.json | 8 +++++++- .../v2/benchmark/fixtures/cis-benchmark-1-4.json | 4 +++- .../v2/benchmark/fixtures/cis-benchmark-1-5.json | 4 +++- .../v2/benchmark/fixtures/cis-benchmark-1-7.json | 6 +++++- .../benchmark/fixtures/disa-stig-benchmark-v1r1.json | 5 ++++- .../benchmark/fixtures/disa-stig-benchmark-v2r1.json | 5 ++++- .../benchmark/fixtures/disa-stig-benchmark-v2r2.json | 5 ++++- .../benchmark/fixtures/disa-stig-benchmark-v2r3.json | 8 +++++++- .../v2/benchmark/fixtures/e8-benchmark.json | 4 +++- .../v2/benchmark/fixtures/nerc-benchmark.json | 5 ++++- .../fixtures/nist-high-benchmark-revision-4.json | 8 +++++++- .../fixtures/nist-moderate-benchmark-revision-4.json | 8 +++++++- .../v2/benchmark/fixtures/pci-benchmark-3-2-1.json | 6 +++++- .../v2/benchmark/fixtures/pci-benchmark-4-0.json | 4 +++- 15 files changed, 71 insertions(+), 19 deletions(-) diff --git a/central/complianceoperator/v2/benchmark/benchmark.go b/central/complianceoperator/v2/benchmark/benchmark.go index 28c677d0f6d84..53c3980bea2f1 100644 --- a/central/complianceoperator/v2/benchmark/benchmark.go +++ b/central/complianceoperator/v2/benchmark/benchmark.go @@ -22,11 +22,11 @@ var ( regexp.MustCompile(".+-nerc-cip$|.+-nerc-cip-.+"): {shortName: "NERC-CIP", provider: "NERC"}, regexp.MustCompile(".+-pci-dss$|.+-pci-dss-.+"): {shortName: "PCI-DSS", provider: "PCI"}, - regexp.MustCompile("^ocp4-cis$|^ocp4-cis-.+"): {shortName: "CIS-OCP", provider: "CIS"}, - regexp.MustCompile("^ocp4-high$|^ocp4-high-.+"): {shortName: "NIST-800-53", provider: "NIST"}, - regexp.MustCompile("^ocp4-moderate$|^ocp4-moderate-.+"): {shortName: "NIST-800-53", provider: "NIST"}, - regexp.MustCompile("^rhcos4-high$|^rhcos4-high-.+"): {shortName: "NIST-800-53", provider: "NIST"}, - regexp.MustCompile("^rhcos4-moderate$|^rhcos4-moderate-.+"): {shortName: "NIST-800-53", provider: "NIST"}, + regexp.MustCompile("^ocp[45]-cis(-.+)?$"): {shortName: "CIS-OCP", provider: "CIS"}, + regexp.MustCompile("^ocp[45]-high(-.+)?$"): {shortName: "NIST-800-53", provider: "NIST"}, + regexp.MustCompile("^ocp[45]-moderate(-.+)?$"): {shortName: "NIST-800-53", provider: "NIST"}, + regexp.MustCompile("^rhcos[45]-high(-.+)?$"): {shortName: "NIST-800-53", provider: "NIST"}, + regexp.MustCompile("^rhcos[45]-moderate(-.+)?$"): {shortName: "NIST-800-53", provider: "NIST"}, } log = logging.LoggerForModule() diff --git a/central/complianceoperator/v2/benchmark/fixtures/bsi-benchmark-2022.json b/central/complianceoperator/v2/benchmark/fixtures/bsi-benchmark-2022.json index 83c0710a20bfa..02cb0df653f4b 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/bsi-benchmark-2022.json +++ b/central/complianceoperator/v2/benchmark/fixtures/bsi-benchmark-2022.json @@ -8,6 +8,12 @@ { "profileName": "ocp4-bsi-2022" }, { "profileName": "ocp4-bsi-node-2022" }, { "profileName": "rhcos4-bsi" }, - { "profileName": "rhcos4-bsi-2022" } + { "profileName": "rhcos4-bsi-2022" }, + { "profileName": "ocp5-bsi" }, + { "profileName": "ocp5-bsi-node" }, + { "profileName": "ocp5-bsi-2022" }, + { "profileName": "ocp5-bsi-node-2022" }, + { "profileName": "rhcos5-bsi" }, + { "profileName": "rhcos5-bsi-2022" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-4.json b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-4.json index 4abed7a261874..c2eb1d26c2b20 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-4.json +++ b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-4.json @@ -4,6 +4,8 @@ "shortName": "CIS-OCP", "profiles": [ { "profileName": "ocp4-cis-1-4" }, - { "profileName": "ocp4-cis-node-1-4" } + { "profileName": "ocp4-cis-node-1-4" }, + { "profileName": "ocp5-cis-1-4" }, + { "profileName": "ocp5-cis-node-1-4" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-5.json b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-5.json index 1910846e70fbf..1b26b8c2cfe5d 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-5.json +++ b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-5.json @@ -4,6 +4,8 @@ "shortName": "CIS-OCP", "profiles": [ { "profileName": "ocp4-cis-1-5" }, - { "profileName": "ocp4-cis-node-1-5" } + { "profileName": "ocp4-cis-node-1-5" }, + { "profileName": "ocp5-cis-1-5" }, + { "profileName": "ocp5-cis-node-1-5" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-7.json b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-7.json index 951f7f4b4da16..2a7f7909aa843 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-7.json +++ b/central/complianceoperator/v2/benchmark/fixtures/cis-benchmark-1-7.json @@ -6,6 +6,10 @@ { "profileName": "ocp4-cis" }, { "profileName": "ocp4-cis-node" }, { "profileName": "ocp4-cis-1-7" }, - { "profileName": "ocp4-cis-node-1-7" } + { "profileName": "ocp4-cis-node-1-7" }, + { "profileName": "ocp5-cis" }, + { "profileName": "ocp5-cis-node" }, + { "profileName": "ocp5-cis-1-7" }, + { "profileName": "ocp5-cis-node-1-7" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v1r1.json b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v1r1.json index 80e96a831ccfb..dbdea4bdf9cd3 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v1r1.json +++ b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v1r1.json @@ -5,6 +5,9 @@ "profiles": [ { "profileName": "ocp4-stig-v1r1" }, { "profileName": "ocp4-stig-node-v1r1" }, - { "profileName": "rhcos4-stig-v1r1" } + { "profileName": "rhcos4-stig-v1r1" }, + { "profileName": "ocp5-stig-v1r1" }, + { "profileName": "ocp5-stig-node-v1r1" }, + { "profileName": "rhcos5-stig-v1r1" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r1.json b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r1.json index cee74738e8284..1e766128d4e01 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r1.json +++ b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r1.json @@ -5,6 +5,9 @@ "profiles": [ { "profileName": "ocp4-stig-v2r1" }, { "profileName": "ocp4-stig-node-v2r1" }, - { "profileName": "rhcos4-stig-v2r1" } + { "profileName": "rhcos4-stig-v2r1" }, + { "profileName": "ocp5-stig-v2r1" }, + { "profileName": "ocp5-stig-node-v2r1" }, + { "profileName": "rhcos5-stig-v2r1" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r2.json b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r2.json index 1a2f7a5d425d6..3905957e625bb 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r2.json +++ b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r2.json @@ -5,6 +5,9 @@ "profiles": [ { "profileName": "ocp4-stig-v2r2" }, { "profileName": "ocp4-stig-node-v2r2" }, - { "profileName": "rhcos4-stig-v2r2" } + { "profileName": "rhcos4-stig-v2r2" }, + { "profileName": "ocp5-stig-v2r2" }, + { "profileName": "ocp5-stig-node-v2r2" }, + { "profileName": "rhcos5-stig-v2r2" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r3.json b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r3.json index af9f11f83304b..9d84177943bfc 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r3.json +++ b/central/complianceoperator/v2/benchmark/fixtures/disa-stig-benchmark-v2r3.json @@ -8,6 +8,12 @@ { "profileName": "rhcos4-stig" }, { "profileName": "ocp4-stig-v2r3" }, { "profileName": "ocp4-stig-node-v2r3" }, - { "profileName": "rhcos4-stig-v2r3" } + { "profileName": "rhcos4-stig-v2r3" }, + { "profileName": "ocp5-stig" }, + { "profileName": "ocp5-stig-node" }, + { "profileName": "rhcos5-stig" }, + { "profileName": "ocp5-stig-v2r3" }, + { "profileName": "ocp5-stig-node-v2r3" }, + { "profileName": "rhcos5-stig-v2r3" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/e8-benchmark.json b/central/complianceoperator/v2/benchmark/fixtures/e8-benchmark.json index 410f8560979bd..08dea2d91bc10 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/e8-benchmark.json +++ b/central/complianceoperator/v2/benchmark/fixtures/e8-benchmark.json @@ -4,6 +4,8 @@ "shortName": "E8", "profiles": [ { "profileName": "ocp4-e8" }, - { "profileName": "rhcos4-e8" } + { "profileName": "rhcos4-e8" }, + { "profileName": "ocp5-e8" }, + { "profileName": "rhcos5-e8" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/nerc-benchmark.json b/central/complianceoperator/v2/benchmark/fixtures/nerc-benchmark.json index f39e001ca8353..902f626a95900 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/nerc-benchmark.json +++ b/central/complianceoperator/v2/benchmark/fixtures/nerc-benchmark.json @@ -5,6 +5,9 @@ "profiles": [ { "profileName": "ocp4-nerc-cip" }, { "profileName": "ocp4-nerc-cip-node" }, - { "profileName": "rhcos4-nerc-cip" } + { "profileName": "rhcos4-nerc-cip" }, + { "profileName": "ocp5-nerc-cip" }, + { "profileName": "ocp5-nerc-cip-node" }, + { "profileName": "rhcos5-nerc-cip" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/nist-high-benchmark-revision-4.json b/central/complianceoperator/v2/benchmark/fixtures/nist-high-benchmark-revision-4.json index cdee4264d7f50..e2fe05c6f5106 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/nist-high-benchmark-revision-4.json +++ b/central/complianceoperator/v2/benchmark/fixtures/nist-high-benchmark-revision-4.json @@ -8,6 +8,12 @@ { "profileName": "rhcos4-high" }, { "profileName": "ocp4-high-rev-4" }, { "profileName": "ocp4-high-node-rev-4" }, - { "profileName": "rhcos4-high-rev-4" } + { "profileName": "rhcos4-high-rev-4" }, + { "profileName": "ocp5-high" }, + { "profileName": "ocp5-high-node" }, + { "profileName": "rhcos5-high" }, + { "profileName": "ocp5-high-rev-4" }, + { "profileName": "ocp5-high-node-rev-4" }, + { "profileName": "rhcos5-high-rev-4" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/nist-moderate-benchmark-revision-4.json b/central/complianceoperator/v2/benchmark/fixtures/nist-moderate-benchmark-revision-4.json index b4e1d384f045c..f0e58f494995c 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/nist-moderate-benchmark-revision-4.json +++ b/central/complianceoperator/v2/benchmark/fixtures/nist-moderate-benchmark-revision-4.json @@ -8,6 +8,12 @@ { "profileName": "rhcos4-moderate" }, { "profileName": "ocp4-moderate-rev-4" }, { "profileName": "ocp4-moderate-node-rev-4" }, - { "profileName": "rhcos4-moderate-rev-4" } + { "profileName": "rhcos4-moderate-rev-4" }, + { "profileName": "ocp5-moderate" }, + { "profileName": "ocp5-moderate-node" }, + { "profileName": "rhcos5-moderate" }, + { "profileName": "ocp5-moderate-rev-4" }, + { "profileName": "ocp5-moderate-node-rev-4" }, + { "profileName": "rhcos5-moderate-rev-4" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-3-2-1.json b/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-3-2-1.json index e69ad1ee5ac6b..4627c8f76eece 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-3-2-1.json +++ b/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-3-2-1.json @@ -6,6 +6,10 @@ { "profileName": "ocp4-pci-dss" }, { "profileName": "ocp4-pci-dss-node" }, { "profileName": "ocp4-pci-dss-3-2" }, - { "profileName": "ocp4-pci-dss-node-3-2" } + { "profileName": "ocp4-pci-dss-node-3-2" }, + { "profileName": "ocp5-pci-dss" }, + { "profileName": "ocp5-pci-dss-node" }, + { "profileName": "ocp5-pci-dss-3-2" }, + { "profileName": "ocp5-pci-dss-node-3-2" } ] } diff --git a/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-4-0.json b/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-4-0.json index b2965bca8b530..2e8d506a5bc57 100644 --- a/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-4-0.json +++ b/central/complianceoperator/v2/benchmark/fixtures/pci-benchmark-4-0.json @@ -4,6 +4,8 @@ "shortName": "PCI-DSS", "profiles": [ { "profileName": "ocp4-pci-dss-4-0" }, - { "profileName": "ocp4-pci-dss-node-4-0" } + { "profileName": "ocp4-pci-dss-node-4-0" }, + { "profileName": "ocp5-pci-dss-4-0" }, + { "profileName": "ocp5-pci-dss-node-4-0" } ] } From a61985a7f569fd66f32fff0deec4ed11eb27bfe9 Mon Sep 17 00:00:00 2001 From: David Vail Date: Tue, 1 Sep 2026 08:46:52 -0400 Subject: [PATCH 143/638] ROX-30367: Add AI integrations tab, Lightspeed tile, and list page (#22332) --- .../IntegrationForm/IntegrationForm.tsx | 5 + .../IntegrationTiles/AiIntegrationsTab.tsx | 32 ++++++ .../IntegrationTiles/LightspeedTile.tsx | 32 ++++++ .../Integrations/IntegrationsPage.tsx | 2 + .../Integrations/hooks/useIntegrations.ts | 14 +++ .../Integrations/utils/integrationsList.ts | 21 ++++ .../utils/tableColumnDescriptor.ts | 9 ++ ui/apps/platform/src/images/rh-lightspeed.svg | 107 ++++++++++++++++++ .../src/services/AiIntegrationsService.ts | 4 +- ui/apps/platform/src/types/integration.ts | 6 +- 10 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Integrations/IntegrationTiles/AiIntegrationsTab.tsx create mode 100644 ui/apps/platform/src/Containers/Integrations/IntegrationTiles/LightspeedTile.tsx create mode 100644 ui/apps/platform/src/images/rh-lightspeed.svg diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx index 0f278a16d937a..00ca8a2b668eb 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx @@ -140,6 +140,11 @@ function IntegrationForm({ const Form: FunctionComponent> = ComponentFormMap?.[source]?.[type]; if (!Form) { + // The Lightspeed (aiIntegrations) form is added in ROX-36378. Until then, + // render nearly nothing rather than crashing the details/create page. + if (source === 'aiIntegrations') { + return <>; + } throw new Error( `There are no integration form components for source (${source}) and type (${type})` ); diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/AiIntegrationsTab.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/AiIntegrationsTab.tsx new file mode 100644 index 0000000000000..8dfc90783b226 --- /dev/null +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/AiIntegrationsTab.tsx @@ -0,0 +1,32 @@ +import type { ReactElement } from 'react'; +import { Alert, Gallery } from '@patternfly/react-core'; + +import useRestQuery from 'hooks/useRestQuery'; +import { fetchAiIntegrations } from 'services/AiIntegrationsService'; +import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; + +import IntegrationsTabPage from './IntegrationsTabPage'; +import type { IntegrationsTabProps } from './IntegrationsTab.types'; + +import LightspeedTile from './LightspeedTile'; +import { aiIntegrationsSource } from '../utils/integrationsList'; + +function AiIntegrationsTab({ sourcesEnabled }: IntegrationsTabProps): ReactElement { + const { data, error } = useRestQuery(fetchAiIntegrations); + const integrations = data?.integrations ?? []; + + return ( + + {error && ( + + {getAxiosErrorMessage(error)} + + )} + + + + + ); +} + +export default AiIntegrationsTab; diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/LightspeedTile.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/LightspeedTile.tsx new file mode 100644 index 0000000000000..c95f50c872f9d --- /dev/null +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationTiles/LightspeedTile.tsx @@ -0,0 +1,32 @@ +import type { ReactElement } from 'react'; + +import type { AiIntegration } from 'services/AiIntegrationsService'; + +import { + aiIntegrationsSource as source, + getIntegrationsListPath, + lightspeedDescriptor as descriptor, +} from '../utils/integrationsList'; +import IntegrationTile from './IntegrationTile'; +import { integrationTypeCounter } from './integrationTiles.utils'; + +const { Logo, label, type } = descriptor; + +export type LightspeedTileProps = { + integrations: AiIntegration[]; +}; + +function LightspeedTile({ integrations }: LightspeedTileProps): ReactElement { + const countIntegrations = integrationTypeCounter(integrations); + + return ( + + ); +} + +export default LightspeedTile; diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx index 8b14d7ca933ae..e5c22c99db055 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationsPage.tsx @@ -11,6 +11,7 @@ import CreateIntegrationPage from './CreateIntegrationPage'; import EditIntegrationPage from './EditIntegrationPage'; import IntegrationDetailsPage from './IntegrationDetailsPage'; +import AiIntegrationsTab from './IntegrationTiles/AiIntegrationsTab'; import ApiClientIntegrationsTab from './IntegrationTiles/ApiClientIntegrationsTab'; import AuthenticationIntegrationsTab from './IntegrationTiles/AuthenticationIntegrationsTab'; import BackupIntegrationsTab from './IntegrationTiles/BackupIntegrationsTab'; @@ -31,6 +32,7 @@ const integrationsTabElementMap: Record + (integration as { type: string }).type === 'AI_INTEGRATION_TYPE_OLS' + ); + } + return aiIntegrations; + } case 'apiClients': return []; default: @@ -86,6 +97,9 @@ const useIntegrations = ({ source, type }: UseIntegrationsParams): UseIntegratio if (source === 'cloudSources') { return fetchCloudSources(); } + if (source === 'aiIntegrations') { + return fetchAiIntegrations(); + } if (source === 'apiClients') { return Promise.resolve>({}); } diff --git a/ui/apps/platform/src/Containers/Integrations/utils/integrationsList.ts b/ui/apps/platform/src/Containers/Integrations/utils/integrationsList.ts index 45a118bd8e0a2..5d02a8c6daf6f 100644 --- a/ui/apps/platform/src/Containers/Integrations/utils/integrationsList.ts +++ b/ui/apps/platform/src/Containers/Integrations/utils/integrationsList.ts @@ -13,6 +13,7 @@ import GoogleArtifactSvg from 'images/google-artifact.svg?react'; import GoogleRegistrySvg from 'images/google-container.svg?react'; import IbmSvg from 'images/ibm-ccr.svg?react'; import JiraSvg from 'images/jira.svg?react'; +import LightspeedSvg from 'images/rh-lightspeed.svg?react'; import LogoSvg from 'images/StackRox-integration-logo.svg?react'; import MicrosoftSentinelSvg from 'images/microsoft_sentinel.svg?react'; import NexusSvg from 'images/nexus.svg?react'; @@ -43,6 +44,7 @@ import type { CentralCapabilitiesFlags } from 'services/MetadataService'; import type { FeatureFlagEnvVar } from 'types/featureFlag'; import { integrationSources } from 'types/integration'; import type { + AiIntegrationType, AuthProviderType, BackupIntegrationType, CloudSourceIntegrationType, @@ -86,6 +88,10 @@ export type CloudSourceDescriptor = { type: CloudSourceIntegrationType; } & BaseIntegrationDescriptor; +export type AiIntegrationDescriptor = { + type: AiIntegrationType; +} & BaseIntegrationDescriptor; + export type BaseIntegrationDescriptor = { type: string; label: string; @@ -323,6 +329,17 @@ export const ocmDescriptor: CloudSourceDescriptor = { const cloudSourceDescriptors = [paladinCloudDescriptor, ocmDescriptor]; +export const aiIntegrationsSource = 'aiIntegrations'; + +export const lightspeedDescriptor: AiIntegrationDescriptor = { + Logo: LightspeedSvg, + label: 'OpenShift Lightspeed', + type: 'lightspeed', + featureFlagDependency: ['ROX_AI_INTEGRATIONS'], +}; + +const aiIntegrationsDescriptors = [lightspeedDescriptor]; + function getDescriptors(source: string): BaseIntegrationDescriptor[] { switch (source) { case 'imageIntegrations': @@ -337,6 +354,8 @@ function getDescriptors(source: string): BaseIntegrationDescriptor[] { return authenticationTokensDescriptors; case 'cloudSources': return cloudSourceDescriptors; + case 'aiIntegrations': + return aiIntegrationsDescriptors; default: return []; } @@ -360,6 +379,7 @@ const integrationSourceRequirementsMap: Record = { notifiers: 'Notifier', backups: 'Backup', cloudSources: 'Cloud source', + aiIntegrations: 'AI', authProviders: 'Authentication', apiClients: 'API clients', }; diff --git a/ui/apps/platform/src/Containers/Integrations/utils/tableColumnDescriptor.ts b/ui/apps/platform/src/Containers/Integrations/utils/tableColumnDescriptor.ts index db562e1666bfc..a0368ef4650ee 100644 --- a/ui/apps/platform/src/Containers/Integrations/utils/tableColumnDescriptor.ts +++ b/ui/apps/platform/src/Containers/Integrations/utils/tableColumnDescriptor.ts @@ -1,5 +1,6 @@ import type { AuthMachineToMachineConfig } from 'services/MachineAccessService'; import type { CloudSourceIntegration } from 'services/CloudSourceService'; +import type { AiIntegration } from 'services/AiIntegrationsService'; import type { ApiToken } from 'types/apiToken.proto'; import type { BaseBackupIntegration } from 'types/externalBackup.proto'; import type { FeatureFlagEnvVar } from 'types/featureFlag'; @@ -12,6 +13,7 @@ import type { QuayImageIntegration, } from 'types/imageIntegration.proto'; import type { + AiIntegrationType, AuthProviderIntegration, AuthProviderType, BackupIntegrationType, @@ -88,6 +90,7 @@ type IntegrationTableColumnDescriptorMap = { CloudSourceIntegrationType, IntegrationTableColumnDescriptor[] >; + aiIntegrations: Record[]>; }; const originColumnDescriptor = { @@ -370,6 +373,12 @@ const tableColumnDescriptor: Readonly = { { accessor: 'ocm.endpoint', Header: 'Endpoint' }, ], }, + aiIntegrations: { + lightspeed: [ + { accessor: 'name', Header: 'Name' }, + { accessor: 'serviceUrl', Header: 'Service URL' }, + ], + }, }; export default tableColumnDescriptor; diff --git a/ui/apps/platform/src/images/rh-lightspeed.svg b/ui/apps/platform/src/images/rh-lightspeed.svg new file mode 100644 index 0000000000000..8b0a6226e3354 --- /dev/null +++ b/ui/apps/platform/src/images/rh-lightspeed.svg @@ -0,0 +1,107 @@ + + + Red Hat Lightspeed icon + IBM watsonx code assistant, AI, ML, artificial intelligence, machine learning + + + + + + 2024-03-01T15:25:40.503Z + pending + TRA042cdfc0-8130-4acf-88da-1a324d8a8d9c + Icon + 2024-03-01T15:25:40.503Z + square + true + pending + 2024-07-12T23:52:22.997Z + rhcc-audience:internal + no + Technology icon + DER042cdfc0-8130-4acf-88da-1a324d8a8d9c + pmeilleu@redhat.com + Black + yes + + + rhcc-product:red-hat-ansible-automation-platform + rhcc-product:red-hat-enterprise-linux + rhcc-product:red-hat-openshift + + + + + Technology icon + + + image/svg+xml + 2024-05-10T12:27:57.413Z + + + Red Hat Lightspeed icon + + + + + IBM watsonx code assistant, AI, ML, artificial + intelligence, machine learning + + + Activate + Activate + 2024-10-02T19:50:21.898Z + workflow-process-service + Activate + workflow-process-service + false + 2024-10-02T19:50:21.898Z + workflow-process-service + 2024-10-02T19:50:21.898Z + 38 + 38 + + + Use technology icons to represent Red Hat products and + components. Do not remove the icon from the bounding shape. + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/apps/platform/src/services/AiIntegrationsService.ts b/ui/apps/platform/src/services/AiIntegrationsService.ts index 832aec8c52e25..c01add7599062 100644 --- a/ui/apps/platform/src/services/AiIntegrationsService.ts +++ b/ui/apps/platform/src/services/AiIntegrationsService.ts @@ -16,10 +16,10 @@ type ListAiIntegrationsResponse = { integrations: AiIntegration[]; }; -export function fetchAiIntegrations(): Promise { +export function fetchAiIntegrations(): Promise<{ integrations: AiIntegration[] }> { return axios .get(aiIntegrationsUrl) - .then((response) => response.data.integrations); + .then((response) => response.data); } export function fetchAiIntegration(id: string): Promise { diff --git a/ui/apps/platform/src/types/integration.ts b/ui/apps/platform/src/types/integration.ts index abd6823411a73..4fe5aad6f4bc1 100644 --- a/ui/apps/platform/src/types/integration.ts +++ b/ui/apps/platform/src/types/integration.ts @@ -7,6 +7,7 @@ export const integrationSources = [ 'notifiers', 'backups', 'cloudSources', + 'aiIntegrations', 'authProviders', 'apiClients', ] as const; @@ -23,7 +24,8 @@ export type IntegrationType = | ImageIntegrationType | NotifierIntegrationType | SignatureIntegrationType - | CloudSourceIntegrationType; + | CloudSourceIntegrationType + | AiIntegrationType; export type AuthProviderType = 'apitoken' | 'machineAccess'; @@ -73,6 +75,8 @@ export type SignatureIntegrationType = 'signature'; export type CloudSourceIntegrationType = 'paladinCloud' | 'ocm'; +export type AiIntegrationType = 'lightspeed'; + export type BaseIntegration = { id: string; name: string; From 85891a3f7f78685d4962819b5efc2ff74a24a0e5 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:44:06 +0200 Subject: [PATCH 144/638] ROX-35584: Inject CI feature flags after release generate (#22519) Co-authored-by: Cursor --- .github/workflows/e2e-nongroovy-tests.yaml | 11 ++- deploy/common/feature-flag-env.sh | 52 ++++++++++++ deploy/common/k8sbased.sh | 39 ++++++++- tests/e2e/bats/feature_flag_env.bats | 97 ++++++++++++++++++++++ 4 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 deploy/common/feature-flag-env.sh create mode 100644 tests/e2e/bats/feature_flag_env.bats diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index 5122b4b80aba2..e95b1990c531d 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -83,9 +83,16 @@ jobs: echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" - name: Build roxctl - # roxctl is used for various CLI e2e tests (token-file, authz-trace, istio-support, etc.) - # Copy to /usr/local/bin so it survives bats tests which mv the binary out of bin/ + # roxctl is used for generate (deploy) and CLI e2e tests (token-file, etc). + # Copy to /usr/local/bin so it survives bats tests which mv the binary out of bin/. + # A release Central must be generated with a release roxctl; otherwise generate + # copies ROX_* into the bundle and TestFeatureFlagSettings cannot fail the way + # nightlies do. run: | + if [[ "${GITHUB_REF}" =~ ^refs/tags/ ]] || [[ "${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci-release-build') }}" == "true" ]]; then + export GOTAGS=release + echo "Building release roxctl (tag or ci-release-build)" + fi make roxctl_linux-amd64 sudo cp bin/linux_amd64/roxctl /usr/local/bin/roxctl echo "${HOME}/go/bin" >> "$GITHUB_PATH" diff --git a/deploy/common/feature-flag-env.sh b/deploy/common/feature-flag-env.sh new file mode 100644 index 0000000000000..0113a4b415578 --- /dev/null +++ b/deploy/common/feature-flag-env.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# ROX_SCANNER_V4 and ROX_LEGACY_SCANNER are not ordinary feature-flag env. +# The Central Helm chart already writes them onto the Central container from +# scannerV4 / scanner config (01-central-13-deployment.yaml.htpl). Helm +# customize.central.envVars is rendered by srox.envVars, which appends to +# that list, so injecting the same name produces a second env entry. +# Kubernetes keeps both (last wins at runtime), but kubectl apply cannot +# patch a Deployment that has duplicate env names, and tests that expect a +# single value fail. Scanner V4 / legacy scanner still follow the chart +# values; this skip only avoids copying the env name twice. +# Kubectl inject does not skip these flags: generated manifests omit those +# names, and kubectl set env --local overwrites by name. +is_helm_owned_feature_flag() { + case "$1" in + ROX_SCANNER_V4 | ROX_LEGACY_SCANNER) return 0 ;; + *) return 1 ;; + esac +} + +# omit_helm_owned_feature_flags drops Helm-owned scanner flags from a stream +# of VAR=value lines. Use on the Helm inject path only. +omit_helm_owned_feature_flags() { + local assignment var + while IFS= read -r assignment; do + [[ -z "${assignment}" ]] && continue + var="${assignment%%=*}" + if is_helm_owned_feature_flag "${var}"; then + continue + fi + printf '%s\n' "${assignment}" + done +} + +# feature_flag_env_assignments prints VAR=value for every pkg/features flag +# that is set and non-empty in this process. Release roxctl generate does not +# copy these into the bundle; callers apply them to Central after generate. +feature_flag_env_assignments() { + local list_file="$1" + local var + + if [[ ! -f "${list_file}" ]]; then + echo >&2 "WARNING: feature flag list ${list_file} not found; Central will not receive feature-flag env" + return 0 + fi + + while IFS= read -r var; do + if [[ -n "${!var:-}" ]]; then + printf '%s=%s\n' "${var}" "${!var}" + fi + done < <(grep -oE '"ROX_[A-Z0-9_]+"' "${list_file}" | tr -d '"' | sort -u) +} diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index c2ad604ff3eef..fd25f055ce57b 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# shellcheck source=./feature-flag-env.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/feature-flag-env.sh" + function realpath { [[ -n "$1" ]] || return 0 python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1" @@ -343,6 +346,16 @@ function launch_central { ${KUBE_COMMAND:-kubectl} get namespace "${central_namespace}" &>/dev/null || \ ${KUBE_COMMAND:-kubectl} create namespace "${central_namespace}" + # Release roxctl generate omits feature-flag env from the bundle. Copy + # whatever is set in this process onto Central so CI/dev overrides apply. + local feature_flag_list_file + feature_flag_list_file="$(realpath "${common_dir}/../../pkg/features/list.go")" + local feature_flag_env=() + local _ff_assignment + while IFS= read -r _ff_assignment; do + feature_flag_env+=("${_ff_assignment}") + done < <(feature_flag_env_assignments "${feature_flag_list_file}") + if [[ -f "$unzip_dir/values-public.yaml" ]]; then echo "Deploying central using Helm..." if [[ -n "${REGISTRY_USERNAME}" ]]; then @@ -384,6 +397,25 @@ function launch_central { # Shorten signing key watcher poll for e2e tests (production default is 4h). helm_args+=(--set customize.central.envVars.ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL=5s) + # Skip ROX_SCANNER_V4 / ROX_LEGACY_SCANNER: the chart already sets them + # from scanner config; customize.envVars would add a duplicate env name. + # See is_helm_owned_feature_flag in feature-flag-env.sh. + local helm_feature_flag_env=() + if (( ${#feature_flag_env[@]} > 0 )); then + while IFS= read -r _ff_assignment; do + helm_feature_flag_env+=("${_ff_assignment}") + done < <(printf '%s\n' "${feature_flag_env[@]}" | omit_helm_owned_feature_flags) + fi + if (( ${#helm_feature_flag_env[@]} > 0 )); then + echo "Injecting feature flag env into Central: ${helm_feature_flag_env[*]}" + local _ff_var _ff_val + for _ff_assignment in "${helm_feature_flag_env[@]}"; do + _ff_var="${_ff_assignment%%=*}" + _ff_val="${_ff_assignment#*=}" + helm_args+=(--set-string "customize.central.envVars.${_ff_var}=${_ff_val}") + done + fi + if [[ -n "$POD_SECURITY_POLICIES" ]]; then helm_args+=( --set system.enablePodSecurityPolicies="${POD_SECURITY_POLICIES}" @@ -509,8 +541,13 @@ function launch_central { # requiring a second rollout via a post-launch `set env`. central_deployment="${unzip_dir}/central/01-central-13-deployment.yaml" if [[ -f "${central_deployment}" ]]; then + local central_env=(ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL=5s) + if (( ${#feature_flag_env[@]} > 0 )); then + echo "Injecting feature flag env into Central: ${feature_flag_env[*]}" + central_env+=("${feature_flag_env[@]}") + fi ${ORCH_CMD} set env --local -o yaml -f "${central_deployment}" -c central \ - ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL=5s > "${central_deployment}.tmp" + "${central_env[@]}" > "${central_deployment}.tmp" mv "${central_deployment}.tmp" "${central_deployment}" else echo >&2 "WARNING: ${central_deployment} not found; Central will deploy with the default signing key watch interval and rely on the test's set env fallback." diff --git a/tests/e2e/bats/feature_flag_env.bats b/tests/e2e/bats/feature_flag_env.bats new file mode 100644 index 0000000000000..39d9ef84ddb1a --- /dev/null +++ b/tests/e2e/bats/feature_flag_env.bats @@ -0,0 +1,97 @@ +#!/usr/bin/env bats + +# bats tests/e2e/bats/feature_flag_env.bats + +# shellcheck disable=SC1091 +load "../../../scripts/test_helpers.bats" + +function setup() { + source "${BATS_TEST_DIRNAME}/../../../deploy/common/feature-flag-env.sh" +} + +@test "feature_flag_env_assignments is empty when list.go is missing" { + run feature_flag_env_assignments "${BATS_TEST_TMPDIR}/does-not-exist.go" + assert_success + assert_output --partial "WARNING" + refute_line --regexp '^ROX_' +} + +@test "feature_flag_env_assignments emits only set, non-empty feature flags" { + list_file="${BATS_TEST_TMPDIR}/list.go" + cat > "${list_file}" <<'EOF' + Foo = registerFeature("foo", "ROX_CISA_KEV") + Bar = registerFeature("bar", "ROX_VULN_MGMT_LEGACY_SNOOZE") + Baz = registerFeature("baz", "ROX_NETWORK_GRAPH_EXTERNAL_IPS") + // URL via the ROX_SCANNER_V4_MAVEN_SEARCH_URL environment variable. + Qux = registerFeature("qux", "ROX_SCANNER_V4_MAVEN_SEARCH") +EOF + export ROX_CISA_KEV=true + export ROX_NETWORK_GRAPH_EXTERNAL_IPS=false + export ROX_SCANNER_V4_MAVEN_SEARCH_URL=http://example.invalid + unset ROX_VULN_MGMT_LEGACY_SNOOZE || true + export ROX_SCANNER_V4_MAVEN_SEARCH="" + + run feature_flag_env_assignments "${list_file}" + assert_success + assert_line "ROX_CISA_KEV=true" + assert_line "ROX_NETWORK_GRAPH_EXTERNAL_IPS=false" + refute_line --partial "ROX_VULN_MGMT_LEGACY_SNOOZE" + refute_line --partial "ROX_SCANNER_V4_MAVEN_SEARCH_URL" + refute_line --partial "ROX_SCANNER_V4_MAVEN_SEARCH=" +} + +@test "feature_flag_env_assignments reads pkg/features/list.go" { + export ROX_CISA_KEV=true + run feature_flag_env_assignments "${BATS_TEST_DIRNAME}/../../../pkg/features/list.go" + assert_success + assert_line "ROX_CISA_KEV=true" +} + +@test "is_helm_owned_feature_flag covers Central chart first-class scanner env" { + run is_helm_owned_feature_flag ROX_SCANNER_V4 + assert_success + run is_helm_owned_feature_flag ROX_LEGACY_SCANNER + assert_success + run is_helm_owned_feature_flag ROX_CISA_KEV + assert_failure + run is_helm_owned_feature_flag ROX_VIRTUAL_MACHINES + assert_failure +} + +@test "omit_helm_owned_feature_flags drops chart-owned names and keeps the rest" { + run omit_helm_owned_feature_flags <<'EOF' +ROX_CISA_KEV=true +ROX_SCANNER_V4=true +ROX_LEGACY_SCANNER=false +ROX_NETWORK_GRAPH_EXTERNAL_IPS=false +EOF + assert_success + assert_line "ROX_CISA_KEV=true" + assert_line "ROX_NETWORK_GRAPH_EXTERNAL_IPS=false" + refute_line --partial "ROX_SCANNER_V4" + refute_line --partial "ROX_LEGACY_SCANNER" +} + +@test "feature_flag_env_assignments still emits helm-owned flags for kubectl inject" { + list_file="${BATS_TEST_TMPDIR}/list.go" + cat > "${list_file}" <<'EOF' + ScannerV4 = registerFeature("scanner v4", "ROX_SCANNER_V4") + Legacy = registerFeature("legacy scanner", "ROX_LEGACY_SCANNER") + Foo = registerFeature("foo", "ROX_CISA_KEV") +EOF + export ROX_SCANNER_V4=true + export ROX_LEGACY_SCANNER=false + export ROX_CISA_KEV=true + + run feature_flag_env_assignments "${list_file}" + assert_success + assert_line "ROX_SCANNER_V4=true" + assert_line "ROX_LEGACY_SCANNER=false" + assert_line "ROX_CISA_KEV=true" + + run omit_helm_owned_feature_flags < <(feature_flag_env_assignments "${list_file}") + assert_success + assert_line "ROX_CISA_KEV=true" + refute_line --partial "ROX_SCANNER_V4" + refute_line --partial "ROX_LEGACY_SCANNER" +} From 1141f5ef8894c55dd6ced8f650d961747e89265a Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Tue, 1 Sep 2026 10:52:19 -0700 Subject: [PATCH 145/638] ROX-35027: stream report generation to prevent OOM (#20990) --- central/blob/datastore/datastore.go | 8 + central/blob/datastore/mocks/datastore.go | 15 + central/blob/datastore/store/mocks/store.go | 15 + central/blob/datastore/store/store.go | 76 ++++ central/blob/datastore/test_utils.go | 4 +- .../scheduler/v2/reportgenerator/csv_gen.go | 76 +++- .../v2/reportgenerator/csv_gen_test.go | 71 ++++ .../v2/reportgenerator/report_gen_impl.go | 350 +++++++++++++++--- .../report_gen_memory_bench_test.go | 310 ++++++++++++++++ ...report_gen_transaction_integration_test.go | 299 +++++++++++++++ pkg/env/postgres_query.go | 4 + pkg/features/list.go | 3 + pkg/search/postgres/common.go | 5 +- pkg/search/postgres/select.go | 118 ++++++ pkg/search/postgres/select_cursor_test.go | 266 +++++++++++++ 15 files changed, 1549 insertions(+), 71 deletions(-) create mode 100644 central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go create mode 100644 central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go create mode 100644 pkg/search/postgres/select_cursor_test.go diff --git a/central/blob/datastore/datastore.go b/central/blob/datastore/datastore.go index 07ada065363d1..032932e4afbd2 100644 --- a/central/blob/datastore/datastore.go +++ b/central/blob/datastore/datastore.go @@ -10,6 +10,7 @@ import ( v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/errox" + "github.com/stackrox/rox/pkg/postgres" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/sac/resources" pkgSearch "github.com/stackrox/rox/pkg/search" @@ -27,6 +28,7 @@ var ( type Datastore interface { GetIDs(ctx context.Context) ([]string, error) Upsert(ctx context.Context, obj *storage.Blob, reader io.Reader) error + UpsertWithWriter(ctx context.Context, tx *postgres.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error Get(ctx context.Context, name string, writer io.Writer) (*storage.Blob, bool, error) Delete(ctx context.Context, name string) error GetMetadata(ctx context.Context, name string) (*storage.Blob, bool, error) @@ -53,6 +55,12 @@ func (d *datastoreImpl) Upsert(ctx context.Context, obj *storage.Blob, reader io return d.store.Upsert(ctx, obj, reader) } +// UpsertWithWriter writes a blob using the caller's transaction and a callback +// that writes directly to the large object. +func (d *datastoreImpl) UpsertWithWriter(ctx context.Context, tx *postgres.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error { + return d.store.UpsertWithWriter(ctx, tx, obj, writeFn) +} + // Get retrieves a blob from the database func (d *datastoreImpl) Get(ctx context.Context, name string, writer io.Writer) (*storage.Blob, bool, error) { return d.store.Get(ctx, name, writer) diff --git a/central/blob/datastore/mocks/datastore.go b/central/blob/datastore/mocks/datastore.go index b6ed7a5d2e1a6..84fb9b90926ab 100644 --- a/central/blob/datastore/mocks/datastore.go +++ b/central/blob/datastore/mocks/datastore.go @@ -17,6 +17,7 @@ import ( v1 "github.com/stackrox/rox/generated/api/v1" storage "github.com/stackrox/rox/generated/storage" + postgres "github.com/stackrox/rox/pkg/postgres" search "github.com/stackrox/rox/pkg/search" gomock "go.uber.org/mock/gomock" ) @@ -181,3 +182,17 @@ func (mr *MockDatastoreMockRecorder) Upsert(ctx, obj, reader any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockDatastore)(nil).Upsert), ctx, obj, reader) } + +// UpsertWithWriter mocks base method. +func (m *MockDatastore) UpsertWithWriter(ctx context.Context, tx *postgres.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertWithWriter", ctx, tx, obj, writeFn) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertWithWriter indicates an expected call of UpsertWithWriter. +func (mr *MockDatastoreMockRecorder) UpsertWithWriter(ctx, tx, obj, writeFn any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWithWriter", reflect.TypeOf((*MockDatastore)(nil).UpsertWithWriter), ctx, tx, obj, writeFn) +} diff --git a/central/blob/datastore/store/mocks/store.go b/central/blob/datastore/store/mocks/store.go index 8982a9a900b89..53e4bab0a7ee4 100644 --- a/central/blob/datastore/store/mocks/store.go +++ b/central/blob/datastore/store/mocks/store.go @@ -16,6 +16,7 @@ import ( v1 "github.com/stackrox/rox/generated/api/v1" storage "github.com/stackrox/rox/generated/storage" + postgres "github.com/stackrox/rox/pkg/postgres" search "github.com/stackrox/rox/pkg/search" gomock "go.uber.org/mock/gomock" ) @@ -163,3 +164,17 @@ func (mr *MockStoreMockRecorder) Upsert(ctx, obj, reader any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockStore)(nil).Upsert), ctx, obj, reader) } + +// UpsertWithWriter mocks base method. +func (m *MockStore) UpsertWithWriter(ctx context.Context, tx *postgres.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertWithWriter", ctx, tx, obj, writeFn) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertWithWriter indicates an expected call of UpsertWithWriter. +func (mr *MockStoreMockRecorder) UpsertWithWriter(ctx, tx, obj, writeFn any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertWithWriter", reflect.TypeOf((*MockStore)(nil).UpsertWithWriter), ctx, tx, obj, writeFn) +} diff --git a/central/blob/datastore/store/store.go b/central/blob/datastore/store/store.go index 3fd5064c5cbc8..c5d3fd693053b 100644 --- a/central/blob/datastore/store/store.go +++ b/central/blob/datastore/store/store.go @@ -30,6 +30,7 @@ type Store interface { GetIDs(ctx context.Context) ([]string, error) GetMetadata(ctx context.Context, name string) (*storage.Blob, bool, error) Upsert(ctx context.Context, obj *storage.Blob, reader io.Reader) error + UpsertWithWriter(ctx context.Context, tx *pgPkg.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error Delete(ctx context.Context, name string) error } @@ -139,6 +140,81 @@ func (s *storeImpl) Upsert(ctx context.Context, obj *storage.Blob, reader io.Rea return tx.Commit(ctx) } +// UpsertWithWriter writes a blob using the caller's transaction and a callback +// that writes directly to the large object. The caller is responsible for +// committing or rolling back the transaction. ctx must already carry the +// transaction (via postgres.ContextWithTx) so that internal store operations +// reuse the same connection. writeFn receives a writer backed by the PostgreSQL +// large object; all writes happen on the calling goroutine, so the caller can +// safely interleave other operations on the same transaction (e.g. cursor reads). +func (s *storeImpl) UpsertWithWriter(ctx context.Context, tx *pgPkg.Tx, obj *storage.Blob, writeFn func(io.Writer) error) error { + if err := sac.VerifyAuthzOK(scopeChecker.WriteAllowed(ctx)); err != nil { + return err + } + ctx = sac.WithGlobalAccessScopeChecker(ctx, + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS, storage.Access_READ_WRITE_ACCESS), + sac.ResourceScopeKeys(resources.Administration))) + + existingBlob, exists, err := s.store.Get(ctx, obj.GetName()) + if err != nil { + return err + } + + los := tx.LargeObjects() + var lo *pgx.LargeObject + if exists { + lo, err = los.Open(ctx, existingBlob.GetOid(), pgx.LargeObjectModeWrite) + if err != nil { + return errors.Wrapf(err, "opening blob with oid %d", existingBlob.GetOid()) + } + if err := lo.Truncate(0); err != nil { + return errors.Wrapf(err, "truncating blob with oid %d", existingBlob.GetOid()) + } + obj.Oid = existingBlob.GetOid() + } else { + oid, err := los.Create(ctx, 0) + if err != nil { + return errors.Wrap(err, "error creating new blob") + } + lo, err = los.Open(ctx, oid, pgx.LargeObjectModeWrite) + if err != nil { + return errors.Wrapf(err, "opening blob with oid %d", oid) + } + obj.Oid = oid + } + + cw := &countingWriter{w: lo} + if err := writeFn(cw); err != nil { + return err + } + + if err := lo.Close(); err != nil { + return errors.Wrap(err, "closing large object for blob") + } + if obj.GetLength() < 0 { + obj.Length = cw.n + } else if cw.n != obj.GetLength() { + return errors.Errorf("Blob metadata mismatch. Blob metadata shows %d in length, but data has length of %d", obj.GetLength(), cw.n) + } + + if err := s.store.Upsert(ctx, obj); err != nil { + return errors.Wrapf(err, "error upserting blob %q", obj.GetName()) + } + return nil +} + +type countingWriter struct { + w io.Writer + n int64 +} + +func (cw *countingWriter) Write(p []byte) (int, error) { + n, err := cw.w.Write(p) + cw.n += int64(n) + return n, err +} + // Get returns a blob from the database func (s *storeImpl) Get(ctx context.Context, name string, writer io.Writer) (*storage.Blob, bool, error) { tx, err := s.db.Begin(ctx) diff --git a/central/blob/datastore/test_utils.go b/central/blob/datastore/test_utils.go index 5f6e54c03d647..845e95189aff1 100644 --- a/central/blob/datastore/test_utils.go +++ b/central/blob/datastore/test_utils.go @@ -7,8 +7,8 @@ import ( pgPkg "github.com/stackrox/rox/pkg/postgres" ) -// NewTestDatastore creates the data store for testing. -func NewTestDatastore(_ *testing.T, db pgPkg.DB) Datastore { +// NewTestDatastore creates the data store for testing and benchmarks. +func NewTestDatastore(_ testing.TB, db pgPkg.DB) Datastore { return &datastoreImpl{ store: store.New(db), } diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 451ce5e985b25..54f6c9fb6cf8e 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -61,16 +61,72 @@ func originDisplayName(origin storage.VulnOrigin) string { return origin.String() } -// GenerateCSV takes in the results of vuln report query, converts to CSV and returns zipped data -func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*bytes.Buffer, error) { - // add header for component version +func formatCSVRow(r *ImageCVEQueryResponse) []string { + var epssScore string + if r.GetEPSSProbability() != nil { + epssScore = strconv.FormatFloat(*r.GetEPSSProbability()*100, 'f', 3, 64) + } else { + epssScore = "Not Available" + } + + csvRow := []string{ + r.GetCluster(), + r.GetNamespace(), + r.GetDeployment(), + r.GetImage(), + r.GetComponent(), + r.GetComponentVersion(), + r.GetCVE(), + strconv.FormatBool(r.GetFixable()), + r.GetFixedByVersion(), + strings.ToTitle(stringutils.GetUpTo(r.GetSeverity().String(), "_")), + strconv.FormatFloat(r.GetCVSS(), 'f', 2, 64), + strconv.FormatFloat(r.GetNVDCVSS(), 'f', 2, 64), + epssScore} + + if features.KnownExploitedVulnerabilities.Enabled() { + var cisaKev string + if r.GetCisaKev() != nil { + cisaKev = strconv.FormatBool(*r.GetCisaKev()) + } else { + cisaKev = "Not Available" + } + csvRow = append(csvRow, cisaKev) + } + + csvRow = append(csvRow, + r.GetDiscoveredAtImage(), + r.Link, + r.GetAdvisoryName(), + r.GetAdvisoryLink(), + originDisplayName(r.GetOrigin()), + ) + + return csvRow +} + +func formatCol() []string { csvHeaderCols := csvHeader if features.KnownExploitedVulnerabilities.Enabled() { csvHeaderCols = append(csvHeaderCols[:0:0], csvHeader...) epssIdx := slices.Index(csvHeaderCols, "EPSS Probability Percentage") csvHeaderCols = slices.Insert(csvHeaderCols, epssIdx+1, "CISA KEV") } - csvWriter := csv.NewGenericWriter(csvHeaderCols, true) + return csvHeaderCols +} + +func csvReportName(configName string) string { + truncatedName := configName + if len(configName) > 80 { + truncatedName = configName[0:80] + "..." + } + now := time.Now() + return fmt.Sprintf("RHACS_Vulnerability_Report_%s_%s.csv", truncatedName, now.Format("02_January_2006")) +} + +// GenerateCSV takes in the results of vuln report query, converts to CSV and returns zipped data +func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*bytes.Buffer, error) { + csvWriter := csv.NewGenericWriter(formatCol(), true) for _, r := range cveResponses { var epssScore string @@ -121,17 +177,10 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt var zipBuf bytes.Buffer zipWriter := zip.NewWriter(&zipBuf) - truncatedName := configName - if len(configName) > 80 { - truncatedName = configName[0:80] + "..." - } - - now := time.Now() - reportName := fmt.Sprintf("RHACS_Vulnerability_Report_%s_%s.csv", truncatedName, now.Format("02_January_2006")) header := &zip.FileHeader{ - Name: reportName, + Name: csvReportName(configName), Method: zip.Deflate, - Modified: now, + Modified: time.Now(), } zipFile, err := zipWriter.CreateHeader(header) if err != nil { @@ -146,4 +195,5 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt return nil, errors.Wrapf(err, "unable to close the zip file for report config %s", configName) } return &zipBuf, nil + } diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go index 8f06b9fbfe369..be141ddc0b71b 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go @@ -3,9 +3,11 @@ package reportgenerator import ( "archive/zip" "bytes" + "strings" "testing" "time" + "github.com/stackrox/rox/generated/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,3 +23,72 @@ func TestGenerateCSV_ZipEntryHasModifiedTimestamp(t *testing.T) { assert.False(t, reader.File[0].Modified.IsZero(), "ZIP entry Modified timestamp should not be zero") assert.True(t, reader.File[0].Modified.After(before), "ZIP entry Modified timestamp should be recent") } + +func TestFormatCSVRow(t *testing.T) { + cluster := "test-cluster" + ns := "test-ns" + deploy := "test-deploy" + image := "quay.io/test:latest" + component := "openssl" + compVersion := "1.1.1" + cve := "CVE-2024-1234" + fixable := true + fixedBy := "1.1.2" + severity := storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY + cvss := 9.8 + nvdcvss := 9.5 + epss := 0.95 + discovered := time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC) + advisoryName := "RHSA-2024:1234" + advisoryLink := "https://access.redhat.com/errata/RHSA-2024:1234" + + r := &ImageCVEQueryResponse{ + Cluster: &cluster, + Namespace: &ns, + Deployment: &deploy, + Image: &image, + Component: &component, + ComponentVersion: &compVersion, + CVE: &cve, + Fixable: &fixable, + FixedByVersion: &fixedBy, + Severity: &severity, + CVSS: &cvss, + NVDCVSS: &nvdcvss, + EPSSProbability: &epss, + DiscoveredAtImage: &discovered, + AdvisoryName: &advisoryName, + AdvisoryLink: &advisoryLink, + Link: "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", + } + + row := formatCSVRow(r) + assert.Equal(t, len(csvHeader), len(row), "row should have same number of columns as header") + assert.Equal(t, "test-cluster", row[0]) + assert.Equal(t, "test-ns", row[1]) + assert.Equal(t, "test-deploy", row[2]) + assert.Equal(t, "CVE-2024-1234", row[6]) + assert.Equal(t, "true", row[7]) + assert.Equal(t, "CRITICAL", row[9]) + assert.Equal(t, "95.000", row[12]) + assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[14]) +} + +func TestFormatCSVRow_NilFields(t *testing.T) { + r := &ImageCVEQueryResponse{} + row := formatCSVRow(r) + assert.Equal(t, len(csvHeader), len(row)) + assert.Equal(t, "", row[0]) + assert.Equal(t, "Not Available", row[12]) + assert.Equal(t, "Not Available", row[13]) +} + +func TestCsvReportName(t *testing.T) { + name := csvReportName("My Report") + assert.Contains(t, name, "RHACS_Vulnerability_Report_My Report_") + assert.Contains(t, name, ".csv") + + longName := strings.Repeat("a", 100) + name = csvReportName(longName) + assert.Contains(t, name, "...") +} diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index 7ba471a1fd744..cc0de655a13e4 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -1,8 +1,11 @@ package reportgenerator import ( + "archive/zip" "bytes" "context" + "encoding/csv" + "io" "slices" "time" @@ -24,6 +27,7 @@ import ( "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errorhelpers" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/grpc/authz/allow" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/notifier" @@ -64,6 +68,7 @@ var ( Offset(int32(0)). AddSortOption(search.NewSortOption(search.ImageName)).Proto(), } + cursorBatchSize = env.PostgresDefaultCursorBatchSize.IntegerSetting() ) type reportGeneratorImpl struct { @@ -143,12 +148,16 @@ func (rg *reportGeneratorImpl) generateReportAndNotify(ctx context.Context, req // Get the results of running the report query var err error var reportData *ReportData - if req.ReportSnapshot.GetVulnReportFilters() != nil { - reportData, err = rg.getReportDataSQF(ctx, req.ReportSnapshot, req.Collection, req.DataStartTime) - } - if req.ReportSnapshot.GetViewBasedVulnReportFilters() != nil { - reportData, err = rg.getReportDataViewBased(ctx, req.ReportSnapshot) + if req.ReportSnapshot.GetReportStatus().GetReportNotificationMethod() == storage.ReportStatus_DOWNLOAD { + if features.VulnerabilityReportStreamingDownload.Enabled() { + log.Info("Streaming report generation") + return rg.generateReportTransaction(ctx, req) + } + return rg.generateReportInMemoryDownload(ctx, req) } + + // EMAIL path: use existing in-memory approach (email attachments have practical size limits) + reportData, err = rg.getReportDataSQF(ctx, req.ReportSnapshot, req.Collection, req.DataStartTime) if err != nil { return err } @@ -172,72 +181,306 @@ func (rg *reportGeneratorImpl) generateReportAndNotify(ctx context.Context, req return err } - switch req.ReportSnapshot.GetReportStatus().GetReportNotificationMethod() { - case storage.ReportStatus_DOWNLOAD: - parentDir := req.ReportSnapshot.GetReportConfigurationId() - if req.ReportSnapshot.GetVulnReportFilters() == nil { - parentDir = "view-based-report" + defaultEmailSubject, err := FormatEmailSubject(defaultEmailSubjectTemplate, req.ReportSnapshot) + if err != nil { + return errors.Wrap(err, "Error generating email subject") + } + templateStr := defaultEmailBodyTemplate + if reportData.NumDeployedImageResults == 0 && reportData.NumWatchedImageResults == 0 { + zippedCSVData = nil + templateStr = defaultNoVulnsEmailBodyTemplate + } + + defaultEmailBody, err := FormatEmailBody(templateStr) + if err != nil { + return errors.Wrap(err, "Error generating email body") + } + + configDetailsHTML, err := formatReportConfigDetails(req.ReportSnapshot, reportData.NumDeployedImageResults, + reportData.NumWatchedImageResults) + if err != nil { + return errors.Wrap(err, "Error adding report config details") + } + + errorList := errorhelpers.NewErrorList("Error sending email notifications: ") + for _, notifierSnap := range req.ReportSnapshot.GetNotifiers() { + nf := rg.notificationProcessor.GetNotifier(reportGenCtx, notifierSnap.GetEmailConfig().GetNotifierId()) + reportNotifier, ok := nf.(notifiers.ReportNotifier) + if !ok { + errorList.AddError(errors.Errorf("incorrect type of notifier '%s'", notifierSnap.GetEmailConfig().GetNotifierId())) + continue } - if err = rg.saveReportData(ctx, parentDir, - req.ReportSnapshot.GetReportId(), zippedCSVData); err != nil { - return errors.Wrap(err, "error persisting blob") + customBody := notifierSnap.GetEmailConfig().GetCustomBody() + emailBody := defaultEmailBody + if customBody != "" { + emailBody = customBody } + customSubject := notifierSnap.GetEmailConfig().GetCustomSubject() + emailSubject := defaultEmailSubject + if customSubject != "" { + emailSubject = customSubject + } + emailBodyWithConfigDetails := AddReportConfigDetails(emailBody, configDetailsHTML) + reportName := req.ReportSnapshot.GetName() + err := rg.retryableSendReportResults(reportNotifier, notifierSnap.GetEmailConfig().GetMailingLists(), + zippedCSVData, emailSubject, emailBodyWithConfigDetails, reportName) + if err != nil { + errorList.AddError(errors.Errorf("Error sending email for notifier '%s': %s", + notifierSnap.GetEmailConfig().GetNotifierId(), err)) + } + } + if !errorList.Empty() { + return errorList.ToError() + } + return nil +} + +// generateReportInMemoryDownload accumulates report data in memory, builds the CSV/ZIP, and stores it in blob storage. +func (rg *reportGeneratorImpl) generateReportInMemoryDownload(ctx context.Context, req *ReportRequest) error { + snap := req.ReportSnapshot + + var reportData *ReportData + var err error + if snap.GetViewBasedVulnReportFilters() != nil { + reportData, err = rg.getReportDataViewBased(ctx, snap) + } else { + reportData, err = rg.getReportDataSQF(ctx, snap, req.Collection, req.DataStartTime) + } + if err != nil { + return err + } + + zippedCSVData, err := GenerateCSV(reportData.CVEResponses, snap.GetName()) + if err != nil { + return err + } + + parentDir := snap.GetReportConfigurationId() + if snap.GetVulnReportFilters() == nil { + parentDir = "view-based-report" + } + if err := rg.saveReportData(ctx, parentDir, snap.GetReportId(), zippedCSVData); err != nil { + return errors.Wrap(err, "error saving report to blob store") + } + + snap.ReportStatus.CompletedAt = protocompat.TimestampNow() + if err := rg.updateReportStatus(snap, storage.ReportStatus_GENERATED); err != nil { + return errors.Wrap(err, "Error changing report status to GENERATED") + } + return nil +} + +type querySpec struct { + schema *walker.Schema + query *v1.Query +} + +// buildReportQueries constructs the cursor queries for a report request. +func (rg *reportGeneratorImpl) buildReportQueries(ctx context.Context, req *ReportRequest) ([]querySpec, error) { + var queries []querySpec + snap := req.ReportSnapshot - case storage.ReportStatus_EMAIL: - defaultEmailSubject, err := FormatEmailSubject(defaultEmailSubjectTemplate, req.ReportSnapshot) + if snap.GetVulnReportFilters() != nil { + rQuery, err := rg.buildReportQuery(ctx, snap, req.Collection, req.DataStartTime) if err != nil { - return errors.Wrap(err, "Error generating email subject") + return nil, err } - // If it is an empty report, do not send an attachment in the final notification email and the email body - // will indicate that no vulns were found - templateStr := defaultEmailBodyTemplate - if reportData.NumDeployedImageResults == 0 && reportData.NumWatchedImageResults == 0 { - // If it is an empty report, the email body will indicate that no vulns were found - zippedCSVData = nil - templateStr = defaultNoVulnsEmailBodyTemplate + cveFilterQuery, err := search.ParseQuery(rQuery.CveFieldsQuery, search.MatchAllIfEmpty()) + if err != nil { + return nil, err } - - defaultEmailBody, err := FormatEmailBody(templateStr) + if slices.Contains(snap.GetVulnReportFilters().GetImageTypes(), storage.VulnerabilityReportFilters_DEPLOYED) { + q := search.ConjunctionQuery(rQuery.DeploymentsQuery, cveFilterQuery) + q.Pagination = deployedImagesQueryParts.Pagination + q.Selects = deployedImagesQueryParts.Selects + queries = append(queries, querySpec{schema: deployedImagesQueryParts.Schema, query: q}) + } + if slices.Contains(snap.GetVulnReportFilters().GetImageTypes(), storage.VulnerabilityReportFilters_WATCHED) { + watchedImages, err := rg.getWatchedImages(ctx) + if err != nil { + return nil, err + } + if len(watchedImages) != 0 { + q := search.ConjunctionQuery( + search.NewQueryBuilder().AddExactMatches(search.ImageName, watchedImages...).ProtoQuery(), + cveFilterQuery) + q.Pagination = watchedImagesQueryParts.Pagination + q.Selects = watchedImagesQueryParts.Selects + queries = append(queries, querySpec{schema: watchedImagesQueryParts.Schema, query: q}) + } + } + } + if snap.GetViewBasedVulnReportFilters() != nil { + watchedImages, err := rg.getWatchedImages(ctx) if err != nil { - return errors.Wrap(err, "Error generating email body") + return nil, err } - - configDetailsHTML, err := formatReportConfigDetails(req.ReportSnapshot, reportData.NumDeployedImageResults, - reportData.NumWatchedImageResults) + vbQuery, err := rg.buildReportQueryViewBased(ctx, snap, watchedImages) if err != nil { - return errors.Wrap(err, "Error adding report config details") + return nil, err } + vbQuery.DeployedImagesQuery.Pagination = deployedImagesQueryParts.Pagination + vbQuery.DeployedImagesQuery.Selects = deployedImagesQueryParts.Selects + queries = append(queries, querySpec{schema: deployedImagesQueryParts.Schema, query: vbQuery.DeployedImagesQuery}) + + if len(watchedImages) != 0 { + vbQuery.WatchedImagesQuery.Pagination = watchedImagesQueryParts.Pagination + vbQuery.WatchedImagesQuery.Selects = watchedImagesQueryParts.Selects + queries = append(queries, querySpec{schema: watchedImagesQueryParts.Schema, query: vbQuery.WatchedImagesQuery}) + } + } + return queries, nil +} - errorList := errorhelpers.NewErrorList("Error sending email notifications: ") - for _, notifierSnap := range req.ReportSnapshot.GetNotifiers() { - nf := rg.notificationProcessor.GetNotifier(reportGenCtx, notifierSnap.GetEmailConfig().GetNotifierId()) - reportNotifier, ok := nf.(notifiers.ReportNotifier) - if !ok { - errorList.AddError(errors.Errorf("incorrect type of notifier '%s'", notifierSnap.GetEmailConfig().GetNotifierId())) - continue +// generateReportTransaction runs all report operations — cursor reads, CVE +// lookups, CSV/ZIP generation, and blob store write — within a single database +// transaction. The writeFn callback writes directly to a PostgreSQL large +// object, so memory usage stays constant regardless of report size. +// +// Connection usage: 1 connection. All operations (cursor FETCH, CVE lookups, +// large object writes, metadata upsert) share the same transaction on a single +// goroutine, so there are no concurrent-tx-access issues. +func (rg *reportGeneratorImpl) generateReportTransaction(ctx context.Context, req *ReportRequest) error { + snap := req.ReportSnapshot + queries, err := rg.buildReportQueries(ctx, req) + if err != nil { + return err + } + + tx, err := rg.db.Begin(ctx) + if err != nil { + return errors.Wrap(err, "starting report transaction") + } + committed := false + defer func() { + if !committed { + if rbErr := tx.Rollback(context.Background()); rbErr != nil { + log.Errorf("failed to rollback report transaction: %v", rbErr) } - customBody := notifierSnap.GetEmailConfig().GetCustomBody() - emailBody := defaultEmailBody - if customBody != "" { - emailBody = customBody + } + }() + txCtx := postgres.ContextWithTx(ctx, tx) + + parentDir := snap.GetReportConfigurationId() + if snap.GetVulnReportFilters() == nil { + parentDir = "view-based-report" + } + blobPath := common.GetReportBlobPath(parentDir, snap.GetReportId()) + blob := &storage.Blob{ + Name: blobPath, + LastUpdated: protocompat.TimestampNow(), + ModifiedTime: protocompat.TimestampNow(), + Length: -1, + } + + // cveRefLinksCache is a map[string]string that maps CVE ID -> reference URL. + // It caches the "Reference" link for each CVE (the external URL pointing to the CVE advisory, e.g., on NVD). + cveRefLinksCache := make(map[string]string) + rowCount := 0 + + err = rg.blobStore.UpsertWithWriter(txCtx, tx, blob, func(w io.Writer) error { + zipWriter := zip.NewWriter(w) + zipEntry, err := zipWriter.Create(csvReportName(snap.GetName())) + if err != nil { + return errors.Wrap(err, "creating zip entry") + } + csvW := csv.NewWriter(zipEntry) + csvW.UseCRLF = true + if err := csvW.Write(formatCol()); err != nil { + return errors.Wrap(err, "writing CSV header") + } + + for _, qs := range queries { + if err := rg.streamQueryToCSV(txCtx, qs.schema, qs.query, csvW, cveRefLinksCache, &rowCount); err != nil { + return err } - customSubject := notifierSnap.GetEmailConfig().GetCustomSubject() - emailSubject := defaultEmailSubject - if customSubject != "" { - emailSubject = customSubject + } + + csvW.Flush() + if err := csvW.Error(); err != nil { + return errors.Wrap(err, "flushing CSV writer") + } + return zipWriter.Close() + }) + if err != nil { + return errors.Wrap(err, "error streaming report to blob store") + } + + if err := tx.Commit(context.Background()); err != nil { + return errors.Wrap(err, "committing report transaction") + } + committed = true + + snap.ReportStatus.CompletedAt = protocompat.TimestampNow() + if err := rg.updateReportStatus(snap, storage.ReportStatus_GENERATED); err != nil { + return errors.Wrap(err, "Error changing report status to GENERATED") + } + return nil +} + +// streamQueryToCSV runs a cursor-based query and streams each row directly through CSV formatting +// to the provided csv.Writer. CVE reference links are resolved incrementally in batches and cached. +func (rg *reportGeneratorImpl) streamQueryToCSV( + ctx context.Context, + schema *walker.Schema, + query *v1.Query, + csvW *csv.Writer, + cveRefLinksCache map[string]string, + rowCount *int, +) error { + var batch []*ImageCVEQueryResponse + + flushBatch := func() error { + unseenIDs := set.NewStringSet() + for _, r := range batch { + id := r.GetCVEID() + if id != "" { + if _, cached := cveRefLinksCache[id]; !cached { + unseenIDs.Add(id) + } } - emailBodyWithConfigDetails := AddReportConfigDetails(emailBody, configDetailsHTML) - reportName := req.ReportSnapshot.GetName() - err := rg.retryableSendReportResults(reportNotifier, notifierSnap.GetEmailConfig().GetMailingLists(), - zippedCSVData, emailSubject, emailBodyWithConfigDetails, reportName) + } + if unseenIDs.Cardinality() > 0 { + cves, err := rg.imageCVE2Datastore.GetBatch(ctx, unseenIDs.AsSlice()) if err != nil { - errorList.AddError(errors.Errorf("Error sending email for notifier '%s': %s", - notifierSnap.GetEmailConfig().GetNotifierId(), err)) + return errors.Wrap(err, "fetching CVE reference links") + } + for _, cve := range cves { + cveRefLinksCache[cve.GetId()] = cve.GetCveBaseInfo().GetLink() } } - if !errorList.Empty() { - return errorList.ToError() + for _, r := range batch { + if link, ok := cveRefLinksCache[r.GetCVEID()]; ok { + r.Link = link + } + if err := csvW.Write(formatCSVRow(r)); err != nil { + return err + } } + batch = batch[:0] + return nil + } + + err := pgSearch.RunSelectCursorForSchemaFn[ImageCVEQueryResponse]( + ctx, rg.db, schema, query, + func(r *ImageCVEQueryResponse) error { + batch = append(batch, r) + *rowCount++ + if len(batch) >= cursorBatchSize { + return flushBatch() + } + return nil + }) + if err != nil { + return err + } + + if err := ctx.Err(); err != nil { + return err + } + + if len(batch) > 0 { + return flushBatch() } return nil } @@ -497,6 +740,7 @@ func (rg *reportGeneratorImpl) withCVEReferenceLinks(ctx context.Context, imageC cves = append(cves, v2) } + // cveRefLinks is a map[string]string that maps CVE ID -> reference URL cveRefLinks := make(map[string]string) for _, cve := range cves { cveRefLinks[cve.GetId()] = cve.GetCveBaseInfo().GetLink() diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go new file mode 100644 index 0000000000000..8f6bba1c35605 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go @@ -0,0 +1,310 @@ +//go:build sql_integration + +package reportgenerator + +import ( + "context" + "runtime" + "testing" + "time" + + "github.com/graph-gophers/graphql-go" + blobDS "github.com/stackrox/rox/central/blob/datastore" + clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" + "github.com/stackrox/rox/central/graphql/resolvers" + "github.com/stackrox/rox/central/graphql/resolvers/loaders" + namespaceDS "github.com/stackrox/rox/central/namespace/datastore" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" + collectionPostgres "github.com/stackrox/rox/central/resourcecollection/datastore/store/postgres" + deploymentsView "github.com/stackrox/rox/central/views/deployments" + imagesView "github.com/stackrox/rox/central/views/images" + watchedImageDS "github.com/stackrox/rox/central/watchedimage/datastore" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" + imageUtils "github.com/stackrox/rox/pkg/images/utils" + "github.com/stackrox/rox/pkg/postgres" + "github.com/stackrox/rox/pkg/postgres/pgtest" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// memoryBenchSuite holds shared test infrastructure for the memory benchmarks. +type memoryBenchSuite struct { + b *testing.B + ctx context.Context + testDB *pgtest.TestPostgres + rg *reportGeneratorImpl + collection *storage.ResourceCollection + snap *storage.ReportSnapshot + rowCount int +} + +func setupMemoryBench(b *testing.B, numNamespacesPerCluster, numDeploymentsPerNamespace, numWatchedImages int) *memoryBenchSuite { + b.Helper() + + ctx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + mockCtrl := gomock.NewController(b) + testDB := pgtest.ForT(b) + + watchedImageDatastore := watchedImageDS.GetTestPostgresDataStore(b, testDB.DB) + + var resolver *resolvers.Resolver + var schema *graphql.Schema + if features.FlattenImageData.Enabled() { + imgV2DataStore := resolvers.CreateTestImageV2Datastore(b, testDB, mockCtrl) + resolver, schema = resolvers.SetupTestResolver(b, + imagesView.NewImageView(testDB.DB), + imgV2DataStore, + resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), + resolvers.CreateTestImageCVEV2Datastore(b, testDB), + resolvers.CreateTestDeploymentDatastoreWithImageV2(b, testDB, mockCtrl, imgV2DataStore), + deploymentsView.NewDeploymentView(testDB.DB), + ) + } else { + imageDataStore := resolvers.CreateTestImageDatastore(b, testDB, mockCtrl) + resolver, schema = resolvers.SetupTestResolver(b, + imagesView.NewImageView(testDB.DB), + imageDataStore, + resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), + resolvers.CreateTestImageCVEV2Datastore(b, testDB), + resolvers.CreateTestDeploymentDatastore(b, testDB, mockCtrl, imageDataStore), + deploymentsView.NewDeploymentView(testDB.DB), + ) + } + + collectionStore := collectionPostgres.New(testDB) + _, collectionQueryResolver, err := collectionDS.New(collectionStore) + require.NoError(b, err) + + clusterDatastore := clusterDSMocks.NewMockDataStore(mockCtrl) + nsDatastore, err := namespaceDS.GetTestPostgresDataStore(b, testDB.DB) + require.NoError(b, err) + + blobStore := blobDS.NewTestDatastore(b, testDB.DB) + reportSnapshotStore := reportSnapshotDS.GetTestPostgresDataStore(b, testDB.DB) + + rg := newReportGeneratorImpl(testDB, reportSnapshotStore, resolver.DeploymentDataStore, + watchedImageDatastore, collectionQueryResolver, nil, blobStore, clusterDatastore, + nsDatastore, resolver.ImageCVEV2DataStore, schema) + + // pgtest limits all pools to pool_max_conns=1 which is too restrictive for + // report generation. Use a shared pool with enough connections. + multiConnCfg := testDB.DB.Config().Copy() + multiConnCfg.Config.MaxConns = 4 + multiConnPool, err := postgres.New(context.Background(), multiConnCfg) + require.NoError(b, err) + b.Cleanup(func() { multiConnPool.Close() }) + rg.db = multiConnPool + rg.blobStore = blobDS.NewTestDatastore(b, multiConnPool) + + clusters := []*storage.Cluster{ + {Id: uuid.NewV4().String(), Name: "c1"}, + {Id: uuid.NewV4().String(), Name: "c2"}, + } + namespaces := testNamespaces(clusters, numNamespacesPerCluster) + for _, ns := range namespaces { + require.NoError(b, nsDatastore.AddNamespace(ctx, ns)) + } + + deployments, images := testDeploymentsWithImages(namespaces, numDeploymentsPerNamespace) + if features.FlattenImageData.Enabled() { + for _, img := range images { + require.NoError(b, resolver.ImageV2DataStore.UpsertImage(ctx, imageUtils.ConvertToV2(img))) + } + } else { + for _, img := range images { + require.NoError(b, resolver.ImageDataStore.UpsertImage(ctx, img)) + } + } + for _, dep := range deployments { + require.NoError(b, resolver.DeploymentDataStore.UpsertDeployment(ctx, dep)) + } + + watchedImages := testWatchedImages(numWatchedImages) + if features.FlattenImageData.Enabled() { + for _, img := range watchedImages { + require.NoError(b, resolver.ImageV2DataStore.UpsertImage(ctx, imageUtils.ConvertToV2(img))) + } + } else { + for _, img := range watchedImages { + require.NoError(b, resolver.ImageDataStore.UpsertImage(ctx, img)) + } + } + for _, img := range watchedImages { + require.NoError(b, watchedImageDatastore.UpsertWatchedImage(ctx, img.GetName().GetFullName())) + } + + clusterDatastore.EXPECT().GetClusters(gomock.Any()).Return(clusters, nil).AnyTimes() + + collection := testCollection("col_bench", "", "", "") + imageTypes := []storage.VulnerabilityReportFilters_ImageType{ + storage.VulnerabilityReportFilters_DEPLOYED, + storage.VulnerabilityReportFilters_WATCHED, + } + snap := testReportSnapshot(collection.GetId(), storage.VulnerabilityReportFilters_BOTH, allSeverities(), imageTypes, nil) + // updateReportStatus upserts the snapshot into report_snapshots. That table has a FK to + // report_configurations, so the parent config row must exist first. + reportConfigStore := reportConfigDS.GetTestPostgresDataStore(b, testDB.DB) + _, err = reportConfigStore.AddReportConfiguration(ctx, &storage.ReportConfiguration{ + Id: snap.GetReportConfigurationId(), + Name: "bench-report-config", + Type: storage.ReportConfiguration_VULNERABILITY, + }) + require.NoError(b, err) + // updateReportStatus also requires a non-empty ReportId. + snap.ReportId = uuid.NewV4().String() + + // Each image has 2 CVEs, so: + // deployed rows = 2 clusters * numNamespacesPerCluster * numDeploymentsPerNamespace * 2 + // watched rows = numWatchedImages * 2 + expectedRows := 2*numNamespacesPerCluster*numDeploymentsPerNamespace*2 + numWatchedImages*2 + + return &memoryBenchSuite{ + b: b, + ctx: ctx, + testDB: testDB, + rg: rg, + collection: collection, + snap: snap, + rowCount: expectedRows, + } +} + +func forceGC() { + runtime.GC() + runtime.GC() +} + +func heapAllocBytes() uint64 { + var m runtime.MemStats + runtime.ReadMemStats(&m) + return m.HeapAlloc +} + +// BenchmarkMemoryComparison runs InMemory and Transaction paths side-by-side for easy comparison. +// Use: go test -tags sql_integration -bench BenchmarkMemoryComparison -benchtime 3x -v +func BenchmarkMemoryComparison(b *testing.B) { + s := setupMemoryBench(b, 10, 50, 500) + if !features.FlattenImageData.Enabled() { + b.Skip("benchmark requires FlattenImageData (image v2 model) to be enabled") + } + + b.Run("InMemory", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + iterCtx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + forceGC() + before := heapAllocBytes() + + reportData, err := s.rg.getReportDataSQF(iterCtx, s.snap, s.collection, time.Time{}) + require.NoError(b, err) + + zippedCSV, err := GenerateCSV(reportData.CVEResponses, s.snap.GetName()) + require.NoError(b, err) + + after := heapAllocBytes() + b.ReportMetric(float64(after-before), "heap-delta-bytes") + + err = s.rg.saveReportData(iterCtx, s.snap.GetReportConfigurationId(), s.snap.GetReportId(), zippedCSV) + require.NoError(b, err) + } + }) + + s.snap.ReportStatus.ReportNotificationMethod = storage.ReportStatus_DOWNLOAD + b.Run("Transaction", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + iterCtx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + forceGC() + before := heapAllocBytes() + + err := s.rg.generateReportTransaction(iterCtx, &ReportRequest{ + ReportSnapshot: s.snap, + Collection: s.collection, + DataStartTime: time.Time{}, + }) + require.NoError(b, err) + + after := heapAllocBytes() + b.ReportMetric(float64(after-before), "heap-delta-bytes") + } + }) +} + +// BenchmarkMemoryAtScale uses larger data volumes to amplify the difference. +// 2 clusters * 20 namespaces * 100 deployments * 2 CVEs = 8000 deployed rows +// + 1000 watched images * 2 CVEs = 2000 watched rows = 10000 total rows. +func BenchmarkMemoryAtScale(b *testing.B) { + if !features.FlattenImageData.Enabled() { + b.Skip("benchmark requires FlattenImageData (image v2 model) to be enabled") + } + s := setupMemoryBench(b, 20, 100, 1000) + + s.snap.ReportStatus.ReportNotificationMethod = storage.ReportStatus_DOWNLOAD + b.Run("Transaction", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + iterCtx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + forceGC() + before := heapAllocBytes() + + err := s.rg.generateReportTransaction(iterCtx, &ReportRequest{ + ReportSnapshot: s.snap, + Collection: s.collection, + DataStartTime: time.Time{}, + }) + require.NoError(b, err) + + after := heapAllocBytes() + b.ReportMetric(float64(after-before), "heap-delta-bytes") + } + }) +} + +// BenchmarkReportDuration measures time for InMemory vs Transaction report generation. +// The built-in ns/op from testing.B captures this automatically, but this benchmark also reports +// explicit millisecond metrics for easier comparison. +// Use: go test -tags sql_integration -bench BenchmarkReportDuration -benchtime 3x -v +func BenchmarkReportDuration(b *testing.B) { + s := setupMemoryBench(b, 10, 50, 500) + if !features.FlattenImageData.Enabled() { + b.Skip("benchmark requires FlattenImageData (image v2 model) to be enabled") + } + + b.Run("InMemory", func(b *testing.B) { + for i := 0; i < b.N; i++ { + iterCtx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + + start := time.Now() + reportData, err := s.rg.getReportDataSQF(iterCtx, s.snap, s.collection, time.Time{}) + require.NoError(b, err) + + zippedCSV, err := GenerateCSV(reportData.CVEResponses, s.snap.GetName()) + require.NoError(b, err) + + err = s.rg.saveReportData(iterCtx, s.snap.GetReportConfigurationId(), s.snap.GetReportId(), zippedCSV) + require.NoError(b, err) + b.ReportMetric(float64(time.Since(start).Milliseconds()), "ms/report") + } + }) + + s.snap.ReportStatus.ReportNotificationMethod = storage.ReportStatus_DOWNLOAD + b.Run("Transaction", func(b *testing.B) { + for i := 0; i < b.N; i++ { + iterCtx := loaders.WithLoaderContext(sac.WithAllAccess(context.Background())) + + start := time.Now() + err := s.rg.generateReportTransaction(iterCtx, &ReportRequest{ + ReportSnapshot: s.snap, + Collection: s.collection, + DataStartTime: time.Time{}, + }) + require.NoError(b, err) + b.ReportMetric(float64(time.Since(start).Milliseconds()), "ms/report") + } + }) +} diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go new file mode 100644 index 0000000000000..b0b64b8c3635d --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go @@ -0,0 +1,299 @@ +//go:build sql_integration + +package reportgenerator + +import ( + "archive/zip" + "bytes" + "context" + "encoding/csv" + "io" + "time" + + blobDS "github.com/stackrox/rox/central/blob/datastore" + "github.com/stackrox/rox/central/reports/common" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/postgres" + "github.com/stackrox/rox/pkg/uuid" +) + +// CSV column indices matching csvHeader in csv_gen.go. +const ( + colCluster = 0 + colNamespace = 1 + colDeployment = 2 + colImage = 3 + colComponent = 4 + colCompVersion = 5 + colCVE = 6 + colFixable = 7 + colFixedBy = 8 + colSeverity = 9 + colCVSS = 10 + colNVDCVSS = 11 + colEPSS = 12 + colDiscoveredAt = 13 + colReference = 14 + colAdvName = 15 + colAdvLink = 16 +) + +// collectColumn extracts a single column from all rows. +func collectColumn(rows [][]string, col int) []string { + out := make([]string, len(rows)) + for i, row := range rows { + out[i] = row[col] + } + return out +} + +// setupTransactionDeps creates a multi-connection pool and the datastores +// required by generateReportTransaction. The returned reportGeneratorImpl +// is a shallow copy of s.reportGenerator with db, blobStore, and +// reportSnapshotStore replaced. +func (s *NewDataModelEnhancedReportingTestSuite) setupTransactionDeps() ( + rg *reportGeneratorImpl, + blobStore blobDS.Datastore, + configStore reportConfigDS.DataStore, +) { + s.T().Helper() + + // generateReportTransaction runs cursor reads, CVE lookups, and blob + // store writes all within a single transaction (1 connection) on a + // single goroutine, so no concurrent tx access occurs. + poolCfg := s.testDB.DB.Config().Copy() + poolCfg.Config.MaxConns = 1 + pool, err := postgres.New(context.Background(), poolCfg) + s.Require().NoError(err) + s.T().Cleanup(func() { pool.Close() }) + + blobStore = blobDS.NewTestDatastore(s.T(), pool) + snapStore := reportSnapshotDS.GetTestPostgresDataStore(s.T(), s.testDB.DB) + configStore = reportConfigDS.GetTestPostgresDataStore(s.T(), s.testDB.DB) + + rgCopy := *s.reportGenerator + rgCopy.db = pool + rgCopy.blobStore = blobStore + rgCopy.reportSnapshotStore = snapStore + return &rgCopy, blobStore, configStore +} + +// prepareSnapshot creates a report snapshot with a backing report configuration +// (FK constraint) and sets the notification method to DOWNLOAD so the streaming +// path is exercised. +func (s *NewDataModelEnhancedReportingTestSuite) prepareSnapshot( + configStore reportConfigDS.DataStore, + collectionID string, + fixability storage.VulnerabilityReportFilters_Fixability, + severities []storage.VulnerabilitySeverity, + imageTypes []storage.VulnerabilityReportFilters_ImageType, +) *storage.ReportSnapshot { + s.T().Helper() + + snap := testReportSnapshot(collectionID, fixability, severities, imageTypes, nil) + configID := uuid.NewV4().String() + snap.ReportConfigurationId = configID + snap.ReportId = uuid.NewV4().String() + snap.ReportStatus.ReportNotificationMethod = storage.ReportStatus_DOWNLOAD + + _, err := configStore.AddReportConfiguration(s.ctx, &storage.ReportConfiguration{ + Id: configID, + Name: "txn-test-config", + Type: storage.ReportConfiguration_VULNERABILITY, + }) + s.Require().NoError(err) + return snap +} + +// readBlobCSV fetches a zipped CSV from blob store, decompresses it, and returns +// the parsed CSV rows (including the header row). +func (s *NewDataModelEnhancedReportingTestSuite) readBlobCSV( + blobStore blobDS.Datastore, + configID, reportID string, +) [][]string { + s.T().Helper() + + blobPath := common.GetReportBlobPath(configID, reportID) + var buf bytes.Buffer + _, found, err := blobStore.Get(reportGenCtx, blobPath, &buf) + s.Require().NoError(err) + s.Require().True(found, "blob not found at %s", blobPath) + + zipReader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + s.Require().NoError(err) + s.Require().Len(zipReader.File, 1, "expected exactly one entry in the ZIP archive") + + rc, err := zipReader.File[0].Open() + s.Require().NoError(err) + defer func() { _ = rc.Close() }() + + csvBytes, err := io.ReadAll(rc) + s.Require().NoError(err) + + r := csv.NewReader(bytes.NewReader(csvBytes)) + r.FieldsPerRecord = -1 + rows, err := r.ReadAll() + s.Require().NoError(err) + return rows +} + +// --------------------------------------------------------------------------- +// Tests for generateReportTransaction +// --------------------------------------------------------------------------- + +// TestGenerateReportTransaction_AllDeployedImages verifies the full pipeline: +// cursor reads in a single transaction → batched CSV → ZIP → blob store. +// +// Test data: 4 deployments × 2 CVEs = 8 rows, plus header. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_AllDeployedImages() { + rg, blobStore, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_BOTH, allSeverities(), + []storage.VulnerabilityReportFilters_ImageType{storage.VulnerabilityReportFilters_DEPLOYED}) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "", "", ""), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + + rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) + s.Require().Len(rows, 9, "1 header + 8 data rows") + s.Equal(csvHeader, rows[0]) + + dataRows := rows[1:] + s.ElementsMatch([]string{ + "CVE-fixable_critical-c1_ns1_dep0_img_comp", "CVE-nonFixable_low-c1_ns1_dep0_img_comp", + "CVE-fixable_critical-c1_ns2_dep0_img_comp", "CVE-nonFixable_low-c1_ns2_dep0_img_comp", + "CVE-fixable_critical-c2_ns1_dep0_img_comp", "CVE-nonFixable_low-c2_ns1_dep0_img_comp", + "CVE-fixable_critical-c2_ns2_dep0_img_comp", "CVE-nonFixable_low-c2_ns2_dep0_img_comp", + }, collectColumn(dataRows, colCVE)) +} + +// TestGenerateReportTransaction_FixableOnlyFilter verifies that the fixable +// filter is applied through the transactional pipeline. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_FixableOnlyFilter() { + rg, blobStore, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_FIXABLE, allSeverities(), + []storage.VulnerabilityReportFilters_ImageType{storage.VulnerabilityReportFilters_DEPLOYED}) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "", "", ""), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + + rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) + s.Require().Len(rows, 5, "1 header + 4 fixable rows") + + for _, row := range rows[1:] { + s.Equal("true", row[colFixable]) + s.Equal("CRITICAL", row[colSeverity]) + } +} + +// TestGenerateReportTransaction_DeployedAndWatched verifies that both deployed +// and watched image queries are executed within the same transaction. +// +// Test data: 4 deployed CVEs + 4 watched CVEs (2 watched images × 2 CVEs) = 8 data rows. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_DeployedAndWatched() { + rg, blobStore, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_BOTH, allSeverities(), + []storage.VulnerabilityReportFilters_ImageType{ + storage.VulnerabilityReportFilters_DEPLOYED, + storage.VulnerabilityReportFilters_WATCHED, + }) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "", "", ""), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + + rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) + // 8 deployed (4 deps × 2 CVEs) + 4 watched (2 images × 2 CVEs) + 1 header = 13 + s.Require().Len(rows, 13, "1 header + 8 deployed + 4 watched") +} + +// TestGenerateReportTransaction_EmptyResult verifies that a query matching +// no data produces a valid ZIP with a CSV containing only the header row. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_EmptyResult() { + rg, blobStore, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_BOTH, allSeverities(), + []storage.VulnerabilityReportFilters_ImageType{storage.VulnerabilityReportFilters_DEPLOYED}) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "nonexistent-cluster", "", ""), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + + rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) + s.Require().Len(rows, 1, "only header row for empty result") + s.Equal(csvHeader, rows[0]) +} + +// TestGenerateReportTransaction_CSVFieldValues spot-checks individual CSV field +// values for a single known row to verify end-to-end correctness. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_CSVFieldValues() { + rg, blobStore, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_FIXABLE, + []storage.VulnerabilitySeverity{storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY}, + []storage.VulnerabilityReportFilters_ImageType{storage.VulnerabilityReportFilters_DEPLOYED}) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "c1", "ns1", "c1_ns1_dep0"), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + + rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) + s.Require().Len(rows, 2, "1 header + 1 data row") + + row := rows[1] + s.Equal("c1", row[colCluster]) + s.Equal("ns1", row[colNamespace]) + s.Equal("c1_ns1_dep0", row[colDeployment]) + s.Equal("c1_ns1_dep0_img", row[colImage]) + s.Equal("c1_ns1_dep0_img_comp", row[colComponent]) + s.Equal("1.0", row[colCompVersion]) + s.Equal("CVE-fixable_critical-c1_ns1_dep0_img_comp", row[colCVE]) + s.Equal("true", row[colFixable]) + s.Equal("1.1", row[colFixedBy]) + s.Equal("CRITICAL", row[colSeverity]) + s.Equal("9.00", row[colCVSS]) + s.Equal("10.00", row[colNVDCVSS]) + s.Equal("70.000", row[colEPSS]) + s.NotEqual("Not Available", row[colDiscoveredAt]) + s.Equal("RHSA-2025-CVE-fixable", row[colAdvName]) + s.Equal("test-rhsa-link", row[colAdvLink]) +} + +// TestGenerateReportTransaction_ReportStatusUpdated verifies that the report +// snapshot status is set to GENERATED after a successful run. +func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_ReportStatusUpdated() { + rg, _, configStore := s.setupTransactionDeps() + snap := s.prepareSnapshot(configStore, "col1", + storage.VulnerabilityReportFilters_BOTH, allSeverities(), + []storage.VulnerabilityReportFilters_ImageType{storage.VulnerabilityReportFilters_DEPLOYED}) + + err := rg.generateReportTransaction(s.ctx, &ReportRequest{ + ReportSnapshot: snap, + Collection: testCollection("col1", "", "", ""), + DataStartTime: time.Time{}, + }) + s.Require().NoError(err) + s.Equal(storage.ReportStatus_GENERATED, snap.GetReportStatus().GetRunState()) + s.NotNil(snap.GetReportStatus().GetCompletedAt()) +} diff --git a/pkg/env/postgres_query.go b/pkg/env/postgres_query.go index 3c9d1e1178d36..0805ff8d63a15 100644 --- a/pkg/env/postgres_query.go +++ b/pkg/env/postgres_query.go @@ -6,4 +6,8 @@ var ( // Values below this threshold use IN for better plan quality; values at or // above use ANY to avoid the 65535 parameter limit. PostgresParameterThreshold = RegisterIntegerSetting("ROX_POSTGRES_PARAMETER_THRESHOLD", 100) + + // PostgresDefaultCursorBatchSize sets the number of rows fetched per FETCH in server-side + // cursor queries. Lower values reduce memory usage; higher values reduce round-trips. + PostgresDefaultCursorBatchSize = RegisterIntegerSetting("ROX_POSTGRES_CURSOR_BATCH_SIZE", 1000) ) diff --git a/pkg/features/list.go b/pkg/features/list.go index 4a852f663bce6..38a10c33dbe4b 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -128,6 +128,9 @@ var ( // VulnerabilityReportsEnhancedFiltering enables filtering similar to view-based reports in scheduled vulnerability reports VulnerabilityReportsEnhancedFiltering = registerFeature("Enables filtering similar to view-based reports in scheduled vulnerability reports", "ROX_VULNERABILITY_REPORTS_ENHANCED_FILTERING", enabled) + // VulnerabilityReportStreamingDownload enables streaming downloadable vulnerability reports directly to blob storage. + VulnerabilityReportStreamingDownload = registerFeature("Enables streaming downloadable vulnerability reports to blob storage", "ROX_VULNERABILITY_REPORT_STREAMING_DOWNLOAD", enabled) + // NodeVulnerabilityReports enables interface for (future) node vulnerability reports to develop in parallel with image vulnerability reports NodeVulnerabilityReports = registerFeature("Enables interface for scheduled node vulnerability reports", "ROX_NODE_VULNERABILITY_REPORTS") diff --git a/pkg/search/postgres/common.go b/pkg/search/postgres/common.go index 3882fb1cafe5a..91dd377ca5adb 100644 --- a/pkg/search/postgres/common.go +++ b/pkg/search/postgres/common.go @@ -36,10 +36,9 @@ var ( emptyQueryErr = errox.InvalidArgs.New("empty query") cursorDefaultTimeout = env.PostgresDefaultCursorTimeout.DurationSetting() + cursorBatchSize = env.PostgresDefaultCursorBatchSize.IntegerSetting() ) -const cursorBatchSize = 1000 - type cursorSession struct { id string tx *postgres.Tx @@ -1287,7 +1286,7 @@ func RunCursorQueryForSchemaFn[T any, PT pgutils.Unmarshaler[T]](ctx context.Con } } - if rowsAffected != cursorBatchSize { + if rowsAffected != int64(cursorBatchSize) { return ctx.Err() } } diff --git a/pkg/search/postgres/select.go b/pkg/search/postgres/select.go index a038c1675a460..99cb304e7560c 100644 --- a/pkg/search/postgres/select.go +++ b/pkg/search/postgres/select.go @@ -12,9 +12,11 @@ import ( "github.com/georgysavva/scany/v2/pgxscan" "github.com/pkg/errors" v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/pkg/contextutil" "github.com/stackrox/rox/pkg/postgres" "github.com/stackrox/rox/pkg/postgres/pgutils" "github.com/stackrox/rox/pkg/postgres/walker" + "github.com/stackrox/rox/pkg/random" searchPkg "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/search/enumregistry" "github.com/stackrox/rox/pkg/search/paginated" @@ -216,6 +218,122 @@ func RunSelectRequestForSchemaFn[T any](ctx context.Context, db postgres.DB, sch }) } +// RunSelectCursorForSchemaFn executes a select request using a PostgreSQL server-side cursor. +// It fetches rows in batches of cursorBatchSize (1000), keeping only one batch in PostgreSQL +// server memory at a time. This is critical for large result sets where materializing the full +// result would exhaust server memory. +func RunSelectCursorForSchemaFn[T any](ctx context.Context, db postgres.DB, schema *walker.Schema, q *v1.Query, fn func(*T) error) (retErr error) { + var builtQuery *query + defer func() { + if r := recover(); r != nil { + if builtQuery != nil { + log.Errorf("Query issue: %s: %v", builtQuery.AsSQL(), r) + } else { + log.Errorf("Unexpected error running search request: %v", r) + } + debug.PrintStack() + retErr = fmt.Errorf("unexpected error running search request: %v", r) + } + }() + + arrayFields := getArrayFieldsFromType[T]() + + var err error + builtQuery, err = standardizeSelectQueryAndPopulatePath(ctx, q, schema, SELECT, arrayFields) + if err != nil { + return err + } + if builtQuery == nil { + return nil + } + + ctx, cancel := contextutil.ContextWithTimeoutIfNotExists(ctx, cursorDefaultTimeout) + defer cancel() + + cursor, err := pgutils.Retry2(ctx, func() (*cursorSession, error) { + return getSelectCursorSession(ctx, db, builtQuery) + }) + if err != nil { + return errors.Wrap(err, "prepare cursor") + } + if cursor == nil { + return nil + } + defer cursor.close() + + for { + rows, err := cursor.tx.Query(ctx, fmt.Sprintf("FETCH %d FROM %s", cursorBatchSize, cursor.id)) + if err != nil { + return errors.Wrap(err, "fetching from cursor") + } + + scanner := scanAPI.NewRowScanner(rows) + var batch []*T + for rows.Next() { + var row T + if err := scanner.Scan(&row); err != nil { + rows.Close() + return err + } + batch = append(batch, &row) + } + rows.Close() + if err := rows.Err(); err != nil { + return err + } + + for _, r := range batch { + if err := fn(r); err != nil { + return err + } + } + + if len(batch) < cursorBatchSize { + break + } + } + return nil +} + +// getSelectCursorSession creates a cursor session for a pre-built SELECT query, +// following the same tx-reuse pattern as retryableGetCursorSession in common.go. +func getSelectCursorSession(ctx context.Context, db postgres.DB, builtQuery *query) (*cursorSession, error) { + tx, parentTxExists := postgres.TxFromContext(ctx) + if !parentTxExists { + var err error + tx, err = db.Begin(ctx) + if err != nil { + return nil, errors.Wrap(err, "creating transaction for cursor") + } + } + + cleanupFunc := func() { + if parentTxExists { + return + } + commitCtx, commitCancel := contextutil.ContextWithTimeoutIfNotExists(context.Background(), cursorDefaultTimeout) + defer commitCancel() + if err := tx.Commit(commitCtx); err != nil { + log.Errorf("error committing cursor transaction: %v", err) + } + } + + cursorID := fmt.Sprintf("select_%s", random.GenerateString(16, random.CaseInsensitiveAlpha)) + queryStr := builtQuery.AsSQL() + + _, err := tx.Exec(ctx, fmt.Sprintf("DECLARE %s CURSOR FOR %s", cursorID, queryStr), builtQuery.Data...) + if err != nil { + cleanupFunc() + return nil, errors.Wrap(err, "declaring cursor") + } + + return &cursorSession{ + id: cursorID, + tx: tx, + close: cleanupFunc, + }, nil +} + func standardizeSelectQueryAndPopulatePath(ctx context.Context, q *v1.Query, schema *walker.Schema, queryType QueryType, arrayFields map[string]bool) (*query, error) { nowForQuery := time.Now() diff --git a/pkg/search/postgres/select_cursor_test.go b/pkg/search/postgres/select_cursor_test.go new file mode 100644 index 0000000000000..490e722625a94 --- /dev/null +++ b/pkg/search/postgres/select_cursor_test.go @@ -0,0 +1,266 @@ +//go:build sql_integration + +package postgres_test + +import ( + "context" + "errors" + "testing" + + "github.com/stackrox/rox/pkg/postgres/pgtest" + "github.com/stackrox/rox/pkg/postgres/schema" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/search" + pgSearch "github.com/stackrox/rox/pkg/search/postgres" + "github.com/stackrox/rox/pkg/search/postgres/aggregatefunc" + "github.com/stackrox/rox/tools/generate-helpers/pg-table-bindings/multitest/postgres" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestRunSelectCursorForSchemaFn verifies that RunSelectCursorForSchemaFn +// produces the same results as RunSelectRequestForSchemaFn. The cursor +// path uses DECLARE CURSOR / FETCH N under the hood, so equivalence with +// the non-cursor path is the key property. +func TestRunSelectCursorForSchemaFn(t *testing.T) { + ctx := sac.WithAllAccess(context.Background()) + testDB := pgtest.ForT(t) + + store := postgres.New(testDB.DB) + for _, s := range getTestStructs() { + require.NoError(t, store.Upsert(ctx, s)) + } + + t.Run("select single field matches non-cursor path", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + ProtoQuery() + + var expected []*Struct1 + err := pgSearch.RunSelectRequestForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + require.NotEmpty(t, expected) + + var actual []*Struct1 + err = pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.ElementsMatch(t, expected, actual) + }) + + t.Run("select with where clause", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + AddExactMatches(search.TestString, "acs"). + ProtoQuery() + + var expected []*Struct1 + err := pgSearch.RunSelectRequestForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + + var actual []*Struct1 + err = pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + + t.Run("select multiple fields from child table", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields( + search.NewQuerySelect(search.TestNestedString), + search.NewQuerySelect(search.TestNestedBool), + ). + AddExactMatches(search.TestNestedString, "nested_acs"). + ProtoQuery() + + var expected []*Struct2 + err := pgSearch.RunSelectRequestForSchemaFn[Struct2](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct2) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + + var actual []*Struct2 + err = pgSearch.RunSelectCursorForSchemaFn[Struct2](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct2) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + + t.Run("select with pagination preserves order", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields( + search.NewQuerySelect(search.TestString), + search.NewQuerySelect(search.TestNestedString), + ). + WithPagination( + search.NewPagination(). + AddSortOption(search.NewSortOption(search.TestString)). + AddSortOption(search.NewSortOption(search.TestNestedString)), + ). + ProtoQuery() + + var expected []*Struct3 + err := pgSearch.RunSelectRequestForSchemaFn[Struct3](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct3) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + + var actual []*Struct3 + err = pgSearch.RunSelectCursorForSchemaFn[Struct3](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct3) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + + t.Run("select with derived fields and group by", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields( + search.NewQuerySelect(search.TestNestedString).AggrFunc(aggregatefunc.Count), + ). + AddGroupBy(search.TestNestedString). + ProtoQuery() + + var expected []*DerivedStruct5 + err := pgSearch.RunSelectRequestForSchemaFn[DerivedStruct5](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *DerivedStruct5) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + + var actual []*DerivedStruct5 + err = pgSearch.RunSelectCursorForSchemaFn[DerivedStruct5](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *DerivedStruct5) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.ElementsMatch(t, expected, actual) + }) + + t.Run("nil query returns no results", func(t *testing.T) { + called := false + err := pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, nil, func(r *Struct1) error { + called = true + return nil + }) + require.NoError(t, err) + assert.False(t, called) + }) + + t.Run("empty result set", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + AddExactMatches(search.TestString, "nonexistent_value_xyz"). + ProtoQuery() + + var actual []*Struct1 + err := pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.Empty(t, actual) + }) + + t.Run("callback error propagates and stops iteration", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + ProtoQuery() + + expectedErr := errors.New("callback failed") + callCount := 0 + err := pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + callCount++ + return expectedErr + }) + require.Error(t, err) + assert.ErrorIs(t, err, expectedErr) + assert.Equal(t, 1, callCount) + }) + + t.Run("callback error after multiple rows", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + ProtoQuery() + + expectedErr := errors.New("fail on third") + callCount := 0 + err := pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + callCount++ + if callCount == 3 { + return expectedErr + } + return nil + }) + require.Error(t, err) + assert.ErrorIs(t, err, expectedErr) + assert.Equal(t, 3, callCount) + }) + + t.Run("select null timestamp field", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields( + search.NewQuerySelect(search.TestString), + search.NewQuerySelect(search.TestTimestamp), + ). + ProtoQuery() + + var expected []*Struct5 + err := pgSearch.RunSelectRequestForSchemaFn[Struct5](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct5) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + + var actual []*Struct5 + err = pgSearch.RunSelectCursorForSchemaFn[Struct5](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct5) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.ElementsMatch(t, expected, actual) + }) + + t.Run("select with limit", func(t *testing.T) { + q := search.NewQueryBuilder(). + AddSelectFields(search.NewQuerySelect(search.TestString)). + WithPagination( + search.NewPagination(). + AddSortOption(search.NewSortOption(search.TestString)). + Limit(2), + ). + ProtoQuery() + + var expected []*Struct1 + err := pgSearch.RunSelectRequestForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + expected = append(expected, r) + return nil + }) + require.NoError(t, err) + require.Len(t, expected, 2) + + var actual []*Struct1 + err = pgSearch.RunSelectCursorForSchemaFn[Struct1](ctx, testDB.DB, schema.TestStructsSchema, q, func(r *Struct1) error { + actual = append(actual, r) + return nil + }) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) +} From faddd5bcf8ac940c8d2626abadc5bff11418c883 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:09:47 +0200 Subject: [PATCH 146/638] chore(ci): Separate JUnit results in summary (#22509) --- .github/workflows/e2e-byodb-tests.yaml | 10 +++++-- .../e2e-db-backup-restore-tests.yaml | 2 +- .github/workflows/e2e-gke-upgrade-tests.yaml | 30 +++++++++++++++---- .github/workflows/e2e-nongroovy-tests.yaml | 10 +++++-- .github/workflows/e2e-qa-tests.yaml | 10 +++++-- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index ce3fba26ffa7f..cad5a34347a3a 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -187,12 +187,18 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for failed tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "fail" + - name: Publish summary for passed and skipped tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) diff --git a/.github/workflows/e2e-db-backup-restore-tests.yaml b/.github/workflows/e2e-db-backup-restore-tests.yaml index a78f2692d8fb9..eff0bfcb79fda 100644 --- a/.github/workflows/e2e-db-backup-restore-tests.yaml +++ b/.github/workflows/e2e-db-backup-restore-tests.yaml @@ -147,7 +147,7 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for all tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index b9c25c7ae4950..3d000345db4a7 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -155,12 +155,18 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for failed tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "fail" + - name: Publish summary for passed and skipped tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) @@ -299,12 +305,18 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for failed tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "fail" + - name: Publish summary for passed and skipped tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) @@ -430,12 +442,18 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for failed tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "fail" + - name: Publish summary for passed and skipped tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index e95b1990c531d..e68926ead76db 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -199,12 +199,18 @@ jobs: exit 6 fi - - name: Publish test summary + - name: Publish summary for failed tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "fail" + - name: Publish summary for passed and skipped tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index 71b052df595d8..6ba54bab9cead 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -325,12 +325,18 @@ jobs: from post_tests import FinalPost FinalPost(store_qa_tests_data=True).run() - - name: Publish test summary + - name: Publish summary for failed tests uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 if: always() with: paths: ${{ env.ARTIFACT_DIR }}/**/*.xml - show: all + show: "fail" + - name: Publish summary for passed and skipped tests + uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # ratchet:test-summary/action@v2 + if: always() + with: + paths: ${{ env.ARTIFACT_DIR }}/**/*.xml + show: "pass, skip" - name: Report junit failures in jira if: (!cancelled()) From 9f61a79999f357321c23e49ed032ca802083f92d Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:30:47 -0700 Subject: [PATCH 147/638] chore(be): Cleanup/deprecate usages of unused adm cntrl settings (#22427) --- generated/api/v1/cluster_service.swagger.json | 6 +- .../internalapi/sensor/image_iservice.pb.go | 16 ++-- generated/storage/cluster.pb.go | 26 +++--- proto/internalapi/sensor/image_iservice.proto | 2 +- proto/storage/cluster.proto | 4 +- proto/storage/proto.lock | 16 +++- .../orchestratormanager/Kubernetes.groovy | 80 +++++++++++++++---- .../AdmissionControllerNoImageScanTest.groovy | 43 +--------- .../test/groovy/AttemptedAlertsTest.groovy | 3 +- sensor/admission-control/errors/msg.go | 13 --- .../manager/evaluate_deploytime.go | 5 -- .../manager/fast_path_detection_bench_test.go | 1 - .../image_coalescing_caching_bench_test.go | 6 +- sensor/admission-control/manager/images.go | 24 +++--- .../admission-control/manager/images_test.go | 5 -- .../admission-control/manager/manager_impl.go | 33 +------- .../admission-control/service/service_test.go | 3 +- sensor/common/image/service_impl.go | 4 +- sensor/common/image/service_impl_test.go | 19 +++-- 19 files changed, 139 insertions(+), 170 deletions(-) delete mode 100644 sensor/admission-control/errors/msg.go diff --git a/generated/api/v1/cluster_service.swagger.json b/generated/api/v1/cluster_service.swagger.json index 1f2a7a4fcdee1..e7cb5f337dafc 100644 --- a/generated/api/v1/cluster_service.swagger.json +++ b/generated/api/v1/cluster_service.swagger.json @@ -479,13 +479,15 @@ "format": "int32" }, "scanInline": { - "type": "boolean" + "type": "boolean", + "title": "always true since 4.9" }, "disableBypass": { "type": "boolean" }, "enforceOnUpdates": { - "type": "boolean" + "type": "boolean", + "title": "same as enabled since 4.9" } } }, diff --git a/generated/internalapi/sensor/image_iservice.pb.go b/generated/internalapi/sensor/image_iservice.pb.go index 59839e0540733..21c13768dfd81 100644 --- a/generated/internalapi/sensor/image_iservice.pb.go +++ b/generated/internalapi/sensor/image_iservice.pb.go @@ -23,10 +23,11 @@ const ( ) type GetImageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Image *storage.ContainerImage `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"` - Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` - ScanInline bool `protobuf:"varint,2,opt,name=scan_inline,json=scanInline,proto3" json:"scan_inline,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Image *storage.ContainerImage `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"` + Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Deprecated: Marked as deprecated in internalapi/sensor/image_iservice.proto. + ScanInline bool `protobuf:"varint,2,opt,name=scan_inline,json=scanInline,proto3" json:"scan_inline,omitempty"` // always true; AC is the only client unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -75,6 +76,7 @@ func (x *GetImageRequest) GetNamespace() string { return "" } +// Deprecated: Marked as deprecated in internalapi/sensor/image_iservice.proto. func (x *GetImageRequest) GetScanInline() bool { if x != nil { return x.ScanInline @@ -130,11 +132,11 @@ var File_internalapi_sensor_image_iservice_proto protoreflect.FileDescriptor const file_internalapi_sensor_image_iservice_proto_rawDesc = "" + "\n" + - "'internalapi/sensor/image_iservice.proto\x12\x06sensor\x1a\x18storage/deployment.proto\x1a\x13storage/image.proto\"\x7f\n" + + "'internalapi/sensor/image_iservice.proto\x12\x06sensor\x1a\x18storage/deployment.proto\x1a\x13storage/image.proto\"\x83\x01\n" + "\x0fGetImageRequest\x12-\n" + "\x05image\x18\x01 \x01(\v2\x17.storage.ContainerImageR\x05image\x12\x1c\n" + - "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12\x1f\n" + - "\vscan_inline\x18\x02 \x01(\bR\n" + + "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12#\n" + + "\vscan_inline\x18\x02 \x01(\bB\x02\x18\x01R\n" + "scanInline\"8\n" + "\x10GetImageResponse\x12$\n" + "\x05image\x18\x01 \x01(\v2\x0e.storage.ImageR\x05image2M\n" + diff --git a/generated/storage/cluster.pb.go b/generated/storage/cluster.pb.go index 3a93715df58b2..b032df461b173 100644 --- a/generated/storage/cluster.pb.go +++ b/generated/storage/cluster.pb.go @@ -998,12 +998,14 @@ type OrchestratorMetadata_OpenshiftVersion struct { func (*OrchestratorMetadata_OpenshiftVersion) isOrchestratorMetadata_IsOpenshift() {} type AdmissionControllerConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - TimeoutSeconds int32 `protobuf:"varint,2,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` - ScanInline bool `protobuf:"varint,3,opt,name=scan_inline,json=scanInline,proto3" json:"scan_inline,omitempty"` - DisableBypass bool `protobuf:"varint,4,opt,name=disable_bypass,json=disableBypass,proto3" json:"disable_bypass,omitempty"` - EnforceOnUpdates bool `protobuf:"varint,5,opt,name=enforce_on_updates,json=enforceOnUpdates,proto3" json:"enforce_on_updates,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + TimeoutSeconds int32 `protobuf:"varint,2,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` + // Deprecated: Marked as deprecated in storage/cluster.proto. + ScanInline bool `protobuf:"varint,3,opt,name=scan_inline,json=scanInline,proto3" json:"scan_inline,omitempty"` // always true since 4.9 + DisableBypass bool `protobuf:"varint,4,opt,name=disable_bypass,json=disableBypass,proto3" json:"disable_bypass,omitempty"` + // Deprecated: Marked as deprecated in storage/cluster.proto. + EnforceOnUpdates bool `protobuf:"varint,5,opt,name=enforce_on_updates,json=enforceOnUpdates,proto3" json:"enforce_on_updates,omitempty"` // same as enabled since 4.9 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1052,6 +1054,7 @@ func (x *AdmissionControllerConfig) GetTimeoutSeconds() int32 { return 0 } +// Deprecated: Marked as deprecated in storage/cluster.proto. func (x *AdmissionControllerConfig) GetScanInline() bool { if x != nil { return x.ScanInline @@ -1066,6 +1069,7 @@ func (x *AdmissionControllerConfig) GetDisableBypass() bool { return false } +// Deprecated: Marked as deprecated in storage/cluster.proto. func (x *AdmissionControllerConfig) GetEnforceOnUpdates() bool { if x != nil { return x.EnforceOnUpdates @@ -2859,14 +2863,14 @@ const file_storage_cluster_proto_rawDesc = "" + "\n" + "build_date\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tbuildDate\x12!\n" + "\fapi_versions\x18\x03 \x03(\tR\vapiVersionsB\x0e\n" + - "\fis_openshift\"\xd4\x01\n" + + "\fis_openshift\"\xdc\x01\n" + "\x19AdmissionControllerConfig\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12'\n" + - "\x0ftimeout_seconds\x18\x02 \x01(\x05R\x0etimeoutSeconds\x12\x1f\n" + - "\vscan_inline\x18\x03 \x01(\bR\n" + + "\x0ftimeout_seconds\x18\x02 \x01(\x05R\x0etimeoutSeconds\x12#\n" + + "\vscan_inline\x18\x03 \x01(\bB\x02\x18\x01R\n" + "scanInline\x12%\n" + - "\x0edisable_bypass\x18\x04 \x01(\bR\rdisableBypass\x12,\n" + - "\x12enforce_on_updates\x18\x05 \x01(\bR\x10enforceOnUpdates\"/\n" + + "\x0edisable_bypass\x18\x04 \x01(\bR\rdisableBypass\x120\n" + + "\x12enforce_on_updates\x18\x05 \x01(\bB\x02\x18\x01R\x10enforceOnUpdates\"/\n" + "\x11TolerationsConfig\x12\x1a\n" + "\bdisabled\x18\x01 \x01(\bR\bdisabled\"\xf4\x04\n" + "\x13StaticClusterConfig\x12(\n" + diff --git a/proto/internalapi/sensor/image_iservice.proto b/proto/internalapi/sensor/image_iservice.proto index 39d6961c9077b..96ef2f6bc6df8 100644 --- a/proto/internalapi/sensor/image_iservice.proto +++ b/proto/internalapi/sensor/image_iservice.proto @@ -10,7 +10,7 @@ option go_package = "./internalapi/sensor;sensor"; message GetImageRequest { storage.ContainerImage image = 1; string namespace = 3; - bool scan_inline = 2; + bool scan_inline = 2 [deprecated = true]; // always true; AC is the only client } message GetImageResponse { diff --git a/proto/storage/cluster.proto b/proto/storage/cluster.proto index 8702523bcc846..060d9e3a71240 100644 --- a/proto/storage/cluster.proto +++ b/proto/storage/cluster.proto @@ -88,10 +88,10 @@ enum CollectionMethod { message AdmissionControllerConfig { bool enabled = 1; int32 timeout_seconds = 2; - bool scan_inline = 3; + bool scan_inline = 3 [deprecated = true]; // always true since 4.9 bool disable_bypass = 4; - bool enforce_on_updates = 5; + bool enforce_on_updates = 5 [deprecated = true]; // same as enabled since 4.9 } message TolerationsConfig { diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index a335c54587da6..3837f78d1497e 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -2241,7 +2241,13 @@ { "id": 3, "name": "scan_inline", - "type": "bool" + "type": "bool", + "options": [ + { + "name": "deprecated", + "value": "true" + } + ] }, { "id": 4, @@ -2251,7 +2257,13 @@ { "id": 5, "name": "enforce_on_updates", - "type": "bool" + "type": "bool", + "options": [ + { + "name": "deprecated", + "value": "true" + } + ] } ] }, diff --git a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy index 0a34e502cb40c..3ff93c2e6c171 100644 --- a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy +++ b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy @@ -71,6 +71,7 @@ import io.fabric8.kubernetes.api.model.apps.DaemonSetBuilder import io.fabric8.kubernetes.api.model.apps.DaemonSetList import io.fabric8.kubernetes.api.model.apps.DaemonSetSpec import io.fabric8.kubernetes.api.model.apps.Deployment as K8sDeployment +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder import io.fabric8.kubernetes.api.model.apps.DeploymentList import io.fabric8.kubernetes.api.model.apps.DeploymentSpec import io.fabric8.kubernetes.api.model.apps.StatefulSet as K8sStatefulSet @@ -210,15 +211,58 @@ class Kubernetes { waitForDeploymentAndPopulateInfo(deployment) } + @CompileDynamic boolean updateDeploymentNoWait(Deployment deployment, int maxRetries = 0) { - K8sDeployment k8sdeployment = deployments.inNamespace(deployment.namespace).withName(deployment.name).get() - if (k8sdeployment) { - log.debug "Deployment ${deployment.name} with version ${k8sdeployment.metadata.resourceVersion} " + - "found in namespace ${deployment.namespace}. Updating..." - } else { + deployment.getNamespace() != null ?: deployment.setNamespace(this.namespace) + K8sDeployment existing = deployments.inNamespace(deployment.namespace).withName(deployment.name).get() + if (!existing) { log.debug "Deployment ${deployment.name} NOT found in namespace ${deployment.namespace}. Creating..." + return createDeploymentNoWait(deployment, maxRetries) + } + log.debug "Deployment ${deployment.name} with version ${existing.metadata.resourceVersion} " + + "found in namespace ${deployment.namespace}. Updating..." + // Edit the live object rather than createOrReplace (CREATE then REPLACE). + // createOrReplace is recorded as CREATE enforcement. + // Always retry 409s a few times: the deployment controller often updates + // status right after create, so a PATCH with a stale resourceVersion + // conflicts. maxRetries=0 still uses that floor; a larger maxRetries + // raises it. Do not retry 403; admission denials must fail immediately. + K8sDeployment desired = toK8sDeployment(deployment) + int maxAttempts = Math.max(maxRetries, 5) + for (int i = 0; i < maxAttempts; i++) { + try { + this.deployments.inNamespace(deployment.namespace) + .withName(deployment.name) + .edit { K8sDeployment current -> + new DeploymentBuilder(current) + .editMetadata() + .withLabels(desired.metadata.labels) + .withAnnotations(desired.metadata.annotations) + .endMetadata() + .editSpec() + .withReplicas(desired.spec.replicas) + .withMinReadySeconds(desired.spec.minReadySeconds) + .withTemplate(desired.spec.template) + .endSpec() + .build() + } + log.debug "Told the orchestrator to update " + deployment.name + return true + } catch (KubernetesClientException e) { + if (e.code == 409 && i < maxAttempts - 1) { + log.debug "Conflict updating ${deployment.name}, retrying " + + "(attempt ${i + 1} of ${maxAttempts})" + sleep 1000 + continue + } + log.warn("Error updating k8s deployment: ", e) + return false + } catch (Exception e) { + log.warn("Error updating k8s deployment: ", e) + return false + } } - return createDeploymentNoWait(deployment, maxRetries) + return false } void updateDeployment(Deployment deployment) { @@ -2089,15 +2133,8 @@ class Kubernetes { */ @CompileDynamic - boolean createDeploymentNoWait(Deployment deployment, int maxNumRetries = 0) { - deployment.getNamespace() != null ?: deployment.setNamespace(this.namespace) - - // Create service if needed - if (deployment.exposeAsService) { - createService(deployment) - } - - K8sDeployment d = new K8sDeployment( + private K8sDeployment toK8sDeployment(Deployment deployment) { + return new K8sDeployment( metadata: new ObjectMeta( name: deployment.name, namespace: deployment.namespace, @@ -2116,9 +2153,20 @@ class Kubernetes { ), spec: generatePodSpec(deployment) ) - ) ) + } + + @CompileDynamic + boolean createDeploymentNoWait(Deployment deployment, int maxNumRetries = 0) { + deployment.getNamespace() != null ?: deployment.setNamespace(this.namespace) + + // Create service if needed + if (deployment.exposeAsService) { + createService(deployment) + } + + K8sDeployment d = toK8sDeployment(deployment) try { withK8sClientRetry(maxNumRetries, 1) { diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerNoImageScanTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerNoImageScanTest.groovy index 6d562584c284e..4780d500306c1 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerNoImageScanTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerNoImageScanTest.groovy @@ -75,30 +75,12 @@ OqxYbK0Iro6GzSmOzxkn+N2AKawLyXi84WSwJQBK//psATakCgAQKkNTAA== def "Verify Admission Controller Behavior for No Image Scans Policy"() { String gcrId - // Note: This test is intentionally not using @Unroll in order to depend on the order of - // operations. - when: + "Enable admission controller enforcement" def ac = AdmissionControllerConfig.newBuilder() .setEnabled(true) - .setScanInline(false) .setTimeoutSeconds(30) - assert ClusterService.updateAdmissionController(ac) - // Maximum time to wait for propagation to sensor - sleep 5000 - - then: - "Create deployment with a non-scannable image and inline scans disabled" - assert launchDeploymentWithImage(NON_EXISTENT_IMAGE) - - and: - "Create deployment with a scannable image and inline scans disabled" - assert launchDeploymentWithImage(IMAGE_WITH_SCANS) - - when: - "Enable inline scans" - ac.scanInline = true assert ClusterService.updateAdmissionController(ac) sleep 5000 @@ -113,32 +95,13 @@ OqxYbK0Iro6GzSmOzxkn+N2AKawLyXi84WSwJQBK//psATakCgAQKkNTAA== sleep 5000 then: - "Create deployment with a scannable image and inline scans enabled (w/ long timeout)" + "Create deployment with a scannable image" assert launchDeploymentWithImage(IMAGE_WITH_SCANS) and: - "Create deployment with a non-scannable image and inline scans enabled (w/ long timeout)" + "Create deployment with a non-scannable image" assert !launchDeploymentWithImage(NON_EXISTENT_IMAGE) - when: - "Disable inline scans again" - ac.scanInline = false - assert ClusterService.updateAdmissionController(ac) - sleep 5000 - - and: - "Enable image signature policy" - updateImageSignaturePolicy(true) - sleep 5000 - - then: - "Create deployment with a non-scannable image and inline scans disabled" - assert launchDeploymentWithImage(NON_EXISTENT_IMAGE) - - and: - "Create deployment with a scannable image and inline scans disabled" - assert launchDeploymentWithImage(IMAGE_WITH_SCANS) - cleanup: if (gcrId) { ImageIntegrationService.deleteImageIntegration(gcrId) diff --git a/qa-tests-backend/src/test/groovy/AttemptedAlertsTest.groovy b/qa-tests-backend/src/test/groovy/AttemptedAlertsTest.groovy index 5f7eafb30ca55..e6d55c642efeb 100644 --- a/qa-tests-backend/src/test/groovy/AttemptedAlertsTest.groovy +++ b/qa-tests-backend/src/test/groovy/AttemptedAlertsTest.groovy @@ -189,8 +189,7 @@ class AttemptedAlertsTest extends BaseSpecification { and: "Set admission controller settings to enforce on updates to #enforce" AdmissionControllerConfig ac = AdmissionControllerConfig.newBuilder() - .setEnabled(false) - .setEnforceOnUpdates(enforce) + .setEnabled(enforce) .setTimeoutSeconds(3) .build() diff --git a/sensor/admission-control/errors/msg.go b/sensor/admission-control/errors/msg.go deleted file mode 100644 index 50346aad031ef..0000000000000 --- a/sensor/admission-control/errors/msg.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import ( - "fmt" - - "github.com/stackrox/rox/generated/storage" -) - -// ImageScanUnavailableMsg return message indicating inability to handle policies requiring image scans. -func ImageScanUnavailableMsg(policy *storage.Policy) string { - return fmt.Sprintf("Policy %q (%s) depends on the existence of image scans. To enforce this policy "+ - "please enable the 'Contact image scanners' option", policy.GetName(), policy.GetId()) -} diff --git a/sensor/admission-control/manager/evaluate_deploytime.go b/sensor/admission-control/manager/evaluate_deploytime.go index 91ace291194d7..9c35ffe8acef3 100644 --- a/sensor/admission-control/manager/evaluate_deploytime.go +++ b/sensor/admission-control/manager/evaluate_deploytime.go @@ -26,11 +26,6 @@ const ( ) func (m *manager) shouldBypass(s *state, req *admission.AdmissionRequest) bool { - if !s.activeForOperation(req.Operation) { - log.Debugf("Not enforcing on operation, bypassing %s request on %s/%s [%s]", req.Operation, req.Namespace, req.Name, req.Kind) - return true - } - // Do not enforce on StackRox and system namespaces if req.Namespace == namespaces.StackRox || req.Namespace == m.ownNamespace || kubernetes.IsSystemNamespace(req.Namespace) { log.Debugf("Action affects system namespace, bypassing %s request on %s/%s [%s]", req.Operation, req.Namespace, req.Name, req.Kind) diff --git a/sensor/admission-control/manager/fast_path_detection_bench_test.go b/sensor/admission-control/manager/fast_path_detection_bench_test.go index f9cc072d3fd63..4730eeecf233c 100644 --- a/sensor/admission-control/manager/fast_path_detection_bench_test.go +++ b/sensor/admission-control/manager/fast_path_detection_bench_test.go @@ -313,7 +313,6 @@ func benchSettings(fastPolicies, slowPolicies []*storage.Policy) *sensor.Admissi ClusterConfig: &storage.DynamicClusterConfig{ AdmissionControllerConfig: &storage.AdmissionControllerConfig{ Enabled: true, - ScanInline: true, TimeoutSeconds: 10, }, }, diff --git a/sensor/admission-control/manager/image_coalescing_caching_bench_test.go b/sensor/admission-control/manager/image_coalescing_caching_bench_test.go index e683609667a8d..bea93a06f1c0c 100644 --- a/sensor/admission-control/manager/image_coalescing_caching_bench_test.go +++ b/sensor/admission-control/manager/image_coalescing_caching_bench_test.go @@ -238,10 +238,8 @@ func cachingBenchSettings() *sensor.AdmissionControlSettings { ClusterId: uuid.NewDummy().String(), ClusterConfig: &storage.DynamicClusterConfig{ AdmissionControllerConfig: &storage.AdmissionControllerConfig{ - Enabled: true, - EnforceOnUpdates: false, - TimeoutSeconds: 10, - ScanInline: true, + Enabled: true, + TimeoutSeconds: 10, }, }, EnforcedDeployTimePolicies: &storage.PolicyList{Policies: []*storage.Policy{ diff --git a/sensor/admission-control/manager/images.go b/sensor/admission-control/manager/images.go index a0be3b67a0b2c..a3b9bb721fc2e 100644 --- a/sensor/admission-control/manager/images.go +++ b/sensor/admission-control/manager/images.go @@ -127,7 +127,7 @@ func (m *manager) getImageFromSensorOrCentral(ctx context.Context, s *state, img start := time.Now() resp, err := v1.NewImageServiceClient(s.centralConn).ScanImageInternalForAdmission(ctx, &v1.ScanImageInternalRequest{ Image: img, - CachedOnly: !s.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline(), + CachedOnly: false, }) observeImageFetch(fetchSourceCentral, time.Since(start), err) if err != nil { @@ -139,7 +139,7 @@ func (m *manager) getImageFromSensorOrCentral(ctx context.Context, s *state, img start := time.Now() resp, err := m.client.GetImage(ctx, &sensor.GetImageRequest{ Image: img, - ScanInline: s.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline(), + ScanInline: true, Namespace: deployment.GetNamespace(), }) observeImageFetch(fetchSourceSensor, time.Since(start), err) @@ -220,20 +220,16 @@ func (m *manager) getAvailableImagesAndKickOffScans(ctx context.Context, shouldF pendingCount.Store(1) fetchCount := 0 - scanInline := s.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline() - for idx, container := range deployment.GetContainers() { image := container.GetImage() - if image.GetId() != "" || scanInline { - cachedImage := m.getCachedImage(image, s, true) - if cachedImage != nil { - images[idx] = cachedImage - } - if shouldFetch && (cachedImage == nil || (scanInline && cachedImage.GetScan() == nil)) { - pendingCount.Add(1) - fetchCount++ - go m.fetchImage(ctx, s, imgChan, &pendingCount, idx, image, deployment) - } + cachedImage := m.getCachedImage(image, s, true) + if cachedImage != nil { + images[idx] = cachedImage + } + if shouldFetch && (cachedImage == nil || cachedImage.GetScan() == nil) { + pendingCount.Add(1) + fetchCount++ + go m.fetchImage(ctx, s, imgChan, &pendingCount, idx, image, deployment) } if images[idx] == nil { images[idx] = types.ToImage(container.GetImage()) diff --git a/sensor/admission-control/manager/images_test.go b/sensor/admission-control/manager/images_test.go index 7e87d0ce9b995..cc24b56f5457e 100644 --- a/sensor/admission-control/manager/images_test.go +++ b/sensor/admission-control/manager/images_test.go @@ -425,11 +425,6 @@ func (s *ImageCacheTestSuite) TestGetAvailableImagesInitContainerHandling() { for name, tc := range cases { s.Run(name, func() { st := createTestState(false) - st.ClusterConfig = &storage.DynamicClusterConfig{ - AdmissionControllerConfig: &storage.AdmissionControllerConfig{ - ScanInline: true, - }, - } s.manager.state.Store(st) deployment := &storage.Deployment{ diff --git a/sensor/admission-control/manager/manager_impl.go b/sensor/admission-control/manager/manager_impl.go index dc200e1afa000..90db67b4026a2 100644 --- a/sensor/admission-control/manager/manager_impl.go +++ b/sensor/admission-control/manager/manager_impl.go @@ -11,7 +11,6 @@ import ( "github.com/stackrox/rox/generated/internalapi/sensor" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/booleanpolicy" - "github.com/stackrox/rox/pkg/booleanpolicy/policyfields" "github.com/stackrox/rox/pkg/clientconn" "github.com/stackrox/rox/pkg/coalescer" "github.com/stackrox/rox/pkg/concurrency" @@ -27,7 +26,6 @@ import ( "github.com/stackrox/rox/pkg/sizeboundedcache" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/pkg/utils" - "github.com/stackrox/rox/sensor/admission-control/errors" "github.com/stackrox/rox/sensor/admission-control/resources" "google.golang.org/grpc" admission "k8s.io/api/admission/v1" @@ -64,7 +62,6 @@ type state struct { eventOnlyK8sDetector runtime.Detector bypassForUsers, bypassForGroups set.FrozenStringSet - enforcedOps map[admission.Operation]struct{} centralConn *grpc.ClientConn } @@ -82,11 +79,6 @@ func (s *state) admissionTimeoutCtx() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) } -func (s *state) activeForOperation(op admission.Operation) bool { - _, active := s.enforcedOps[op] - return active -} - const ( // imageNameCacheSize is the maximum number of entries in the image name-to-cache-key // LRU. Each entry maps a full image name (e.g. "docker.io/library/nginx:1.25") to its @@ -379,11 +371,6 @@ func (m *manager) ProcessNewSettings(newSettings *sensor.AdmissionControlSetting allK8sEventPolicies := detection.NewPolicySet(m, m) deployFieldK8sEventPolicies, k8sEventOnlyPolicies := detection.NewPolicySet(m, m), detection.NewPolicySet(m, m) for _, policy := range newSettings.GetRuntimePolicies().GetPolicies() { - if policyfields.AlertsOnMissingEnrichment(policy) && !newSettings.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline() { - log.Warn(errors.ImageScanUnavailableMsg(policy)) - continue - } - if err := allK8sEventPolicies.UpsertPolicy(policy); err != nil { log.Errorf("Unable to upsert policy %q (%s), will not be able to detect", policy.GetName(), policy.GetId()) } @@ -400,19 +387,13 @@ func (m *manager) ProcessNewSettings(newSettings *sensor.AdmissionControlSetting log.Debugf("Upserted policy %q (%s)", policy.GetName(), policy.GetId()) } - enforceOnCreates := newSettings.GetClusterConfig().GetAdmissionControllerConfig().GetEnabled() - enforceOnUpdates := newSettings.GetClusterConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates() + enforce := newSettings.GetClusterConfig().GetAdmissionControllerConfig().GetEnabled() // Manager implements both ClusterLabelProvider and NamespaceLabelProvider interfaces. specOnlyPolicies := detection.NewPolicySet(m, m) enrichmentRequiredPolicies := detection.NewPolicySet(m, m) - if enforceOnCreates || enforceOnUpdates { + if enforce { for _, policy := range newSettings.GetEnforcedDeployTimePolicies().GetPolicies() { - if policyfields.AlertsOnMissingEnrichment(policy) && - !newSettings.GetClusterConfig().GetAdmissionControllerConfig().GetScanInline() { - log.Warn(errors.ImageScanUnavailableMsg(policy)) - continue - } compiled, err := detection.CompilePolicy(policy, m, m) if err != nil { log.Errorf("Unable to compile policy %q (%s): %v", policy.GetName(), policy.GetId(), err) @@ -426,15 +407,6 @@ func (m *manager) ProcessNewSettings(newSettings *sensor.AdmissionControlSetting } } - enforcedOperations := make(map[admission.Operation]struct{}) - if enforceOnCreates { - enforcedOperations[admission.Create] = struct{}{} - } - - if enforceOnUpdates { - enforcedOperations[admission.Update] = struct{}{} - } - oldState := m.currentState() newState := &state{ AdmissionControlSettings: newSettings, @@ -445,7 +417,6 @@ func (m *manager) ProcessNewSettings(newSettings *sensor.AdmissionControlSetting eventOnlyK8sDetector: runtime.NewDetector(k8sEventOnlyPolicies), bypassForUsers: allowAlwaysUsers, bypassForGroups: allowAlwaysGroups, - enforcedOps: enforcedOperations, } if oldState != nil && newSettings.GetCentralEndpoint() == oldState.GetCentralEndpoint() { diff --git a/sensor/admission-control/service/service_test.go b/sensor/admission-control/service/service_test.go index 7e896ea79da3c..28a34dc26cbae 100644 --- a/sensor/admission-control/service/service_test.go +++ b/sensor/admission-control/service/service_test.go @@ -88,8 +88,7 @@ func TestLatestTagPolicyAdmissionReview(t *testing.T) { ClusterId: uuid.NewDummy().String(), ClusterConfig: &storage.DynamicClusterConfig{ AdmissionControllerConfig: &storage.AdmissionControllerConfig{ - EnforceOnUpdates: true, - Enabled: true, + Enabled: true, }, }, }, diff --git a/sensor/common/image/service_impl.go b/sensor/common/image/service_impl.go index b6c3cc5eeefe8..ca56e8cc1af44 100644 --- a/sensor/common/image/service_impl.go +++ b/sensor/common/image/service_impl.go @@ -84,7 +84,7 @@ func (s *serviceImpl) GetImage(ctx context.Context, req *sensor.GetImageRequest) v, ok := s.imageCache.Get(cache.GetKey(req.GetImage())) if id != "" && ok { img := v.GetIfDone() - if img != nil && (!req.GetScanInline() || img.GetScan() != nil) { + if img != nil && img.GetScan() != nil { return &sensor.GetImageResponse{ Image: img, }, nil @@ -112,7 +112,7 @@ func (s *serviceImpl) GetImage(ctx context.Context, req *sensor.GetImageRequest) if !req.GetImage().GetIsClusterLocal() { scanResp, err := s.centralClient.ScanImageInternalForAdmission(ctx, &v1.ScanImageInternalRequest{ Image: req.GetImage(), - CachedOnly: !req.GetScanInline(), + CachedOnly: false, }) if err != nil { return nil, errors.Wrap(err, "scanning image via central") diff --git a/sensor/common/image/service_impl_test.go b/sensor/common/image/service_impl_test.go index b7664f6d5369d..db7f80ef61eca 100644 --- a/sensor/common/image/service_impl_test.go +++ b/sensor/common/image/service_impl_test.go @@ -72,28 +72,28 @@ func (s *imageServiceSuite) TestGetImage() { expectedResponse *sensor.GetImageResponse }{ "Cache hit and central is unreachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventOfflineMode, expectCache: expectCacheHelper(s.mockCache, 1, createScannedImage(imageName, imageID)), expectedError: nil, expectedResponse: createImageResponse(imageName, imageID), }, "Cache hit and central is reachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventCentralReachable, expectCache: expectCacheHelper(s.mockCache, 1, createScannedImage(imageName, imageID)), expectedError: nil, expectedResponse: createImageResponse(imageName, imageID), }, "Cache miss and central is unreachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventOfflineMode, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectedError: errCentralNoReachable, expectedResponse: nil, }, "Cache miss and central is reachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventCentralReachable, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectRegistry: expectRegistryHelper(s.mockRegistryStore, 1, false), @@ -102,7 +102,7 @@ func (s *imageServiceSuite) TestGetImage() { expectedResponse: createImageResponse(imageName, imageID), }, "Cache miss, central is reachable and returns error": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventCentralReachable, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectRegistry: expectRegistryHelper(s.mockRegistryStore, 1, false), @@ -111,14 +111,14 @@ func (s *imageServiceSuite) TestGetImage() { expectedResponse: nil, }, "Cache miss, local scan, central is unreachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventOfflineMode, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectedError: errCentralNoReachable, expectedResponse: nil, }, "Cache miss, local scan, central is reachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventCentralReachable, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectRegistry: expectRegistryHelper(s.mockRegistryStore, 1, true), @@ -127,7 +127,7 @@ func (s *imageServiceSuite) TestGetImage() { expectedResponse: createImageResponse(imageName, imageID), }, "Cache miss, local scan returns error, central is reachable": { - request: createImageRequest(imageName, imageID, false), + request: createImageRequest(imageName, imageID), notify: common.SensorComponentEventCentralReachable, expectCache: expectCacheHelper(s.mockCache, 1, nil), expectRegistry: expectRegistryHelper(s.mockRegistryStore, 1, true), @@ -212,9 +212,8 @@ func createScannedImage(name, id string) cache.Value { }} } -func createImageRequest(name, id string, scanInline bool) *sensor.GetImageRequest { +func createImageRequest(name, id string) *sensor.GetImageRequest { return &sensor.GetImageRequest{ - ScanInline: scanInline, Image: &storage.ContainerImage{ Id: id, Name: &storage.ImageName{ From 1a705372cc46da3e70ff03f50de9b690d544d4bb Mon Sep 17 00:00:00 2001 From: David Vail Date: Tue, 1 Sep 2026 15:17:09 -0400 Subject: [PATCH 148/638] ROX-36378: Add Lightspeed AI integration form (#22356) --- .../LightspeedIntegrationForm.test.ts | 44 ++++++ .../LightspeedIntegrationForm.tsx | 128 ++++++++++++++++++ .../IntegrationForm/IntegrationForm.tsx | 6 + .../Integrations/utils/integrationUtils.ts | 4 + .../src/services/AiIntegrationsService.ts | 4 + .../src/services/IntegrationsService.ts | 3 + 6 files changed, 189 insertions(+) create mode 100644 ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.test.ts create mode 100644 ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.tsx diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.test.ts b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.test.ts new file mode 100644 index 0000000000000..494e0e35acd88 --- /dev/null +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.test.ts @@ -0,0 +1,44 @@ +import { defaultValues, validationSchema } from './LightspeedIntegrationForm'; +import type { AiIntegrationFormValues } from './LightspeedIntegrationForm'; + +function makeIntegration( + overrides: Partial = {} +): AiIntegrationFormValues { + return { + id: '', + name: 'OpenShift Lightspeed', + type: 'AI_INTEGRATION_TYPE_OLS', + serviceUrl: 'https://lightspeed.example.com', + ...overrides, + }; +} + +describe('LightspeedIntegrationForm validationSchema', () => { + it('passes with a name and a service URL', () => { + expect(validationSchema.isValidSync(makeIntegration())).toBe(true); + }); + + it('passes when the service URL is empty because it is optional', () => { + expect(validationSchema.isValidSync(makeIntegration({ serviceUrl: '' }))).toBe(true); + }); + + it('fails when the name is missing', () => { + expect(validationSchema.isValidSync(makeIntegration({ name: '' }))).toBe(false); + }); + + it('fails when the name is only whitespace', () => { + expect(validationSchema.isValidSync(makeIntegration({ name: ' ' }))).toBe(false); + }); + + it('fails when the type is not the OLS type', () => { + expect( + validationSchema.isValidSync( + makeIntegration({ type: 'AI_INTEGRATION_TYPE_UNSPECIFIED' }) + ) + ).toBe(false); + }); + + it('rejects the empty default values because a name is required', () => { + expect(validationSchema.isValidSync(defaultValues)).toBe(false); + }); +}); diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.tsx new file mode 100644 index 0000000000000..240f512787f1e --- /dev/null +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/Forms/AiIntegrations/LightspeedIntegrationForm.tsx @@ -0,0 +1,128 @@ +import type { ReactElement } from 'react'; +import * as yup from 'yup'; +import { Form, PageSection, TextInput } from '@patternfly/react-core'; +import merge from 'lodash/merge'; + +import FormMessage from 'Components/PatternFly/FormMessage'; +import FormSaveButton from 'Components/PatternFly/FormSaveButton'; +import FormCancelButton from 'Components/PatternFly/FormCancelButton'; +import FormTestButton from 'Components/PatternFly/FormTestButton'; +import type { AiIntegration } from 'services/AiIntegrationsService'; + +import FormLabelGroup from '../../FormLabelGroup'; +import IntegrationFormActions from '../../IntegrationFormActions'; +import useIntegrationForm from '../../useIntegrationForm'; +import type { IntegrationFormProps } from '../../integrationFormTypes'; + +export const validationSchema = yup.object().shape({ + name: yup.string().trim().required('Integration name is required'), + type: yup.string().oneOf(['AI_INTEGRATION_TYPE_OLS']).required(), + serviceUrl: yup.string().trim(), +}); + +export type AiIntegrationFormValues = AiIntegration; + +export const defaultValues: AiIntegrationFormValues = { + id: '', + name: '', + type: 'AI_INTEGRATION_TYPE_OLS', + serviceUrl: '', +}; + +function LightspeedIntegrationForm({ + initialValues = null, + isEditable = false, +}: IntegrationFormProps): ReactElement { + const formInitialValues = structuredClone(defaultValues); + if (initialValues) { + merge(formInitialValues, initialValues); + } + + const { + values, + touched, + errors, + dirty, + isValid, + setFieldValue, + handleBlur, + isSubmitting, + isTesting, + onSave, + onTest, + onCancel, + message, + } = useIntegrationForm({ + initialValues: formInitialValues, + validationSchema, + }); + + function onChange(value, event) { + return setFieldValue(event.target.id, value); + } + + return ( + <> + + +
    + + onChange(value, event)} + onBlur={handleBlur} + isDisabled={!isEditable} + /> + + + onChange(value, event)} + onBlur={handleBlur} + isDisabled={!isEditable} + /> + + +
    + {isEditable && ( + + + Save + + + Test + + Cancel + + )} + + ); +} + +export default LightspeedIntegrationForm; diff --git a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx index 00ca8a2b668eb..d493a0aeb763a 100644 --- a/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx +++ b/ui/apps/platform/src/Containers/Integrations/IntegrationForm/IntegrationForm.tsx @@ -51,6 +51,9 @@ import SignatureIntegrationForm from './Forms/SignatureIntegrationForm'; import OcmIntegrationForm from './Forms/CloudSourceIntegrations/OcmIntegrationForm'; import PaladinCloudIntegrationForm from './Forms/CloudSourceIntegrations/PaladinCloudIntegrationForm'; +// AI integrations +import LightspeedIntegrationForm from './Forms/AiIntegrations/LightspeedIntegrationForm'; + import './IntegrationForm.css'; type IntegrationFormProps = { @@ -75,6 +78,9 @@ const ComponentFormMap = { s3: S3IntegrationForm, s3compatible: S3CompatibleIntegrationForm, }, + aiIntegrations: { + lightspeed: LightspeedIntegrationForm, + }, cloudSources: { ocm: OcmIntegrationForm, paladinCloud: PaladinCloudIntegrationForm, diff --git a/ui/apps/platform/src/Containers/Integrations/utils/integrationUtils.ts b/ui/apps/platform/src/Containers/Integrations/utils/integrationUtils.ts index 19e3fc0e120c0..66cec5537806e 100644 --- a/ui/apps/platform/src/Containers/Integrations/utils/integrationUtils.ts +++ b/ui/apps/platform/src/Containers/Integrations/utils/integrationUtils.ts @@ -52,6 +52,10 @@ export function getIsCloudSource(source: IntegrationSource): boolean { return source === 'cloudSources'; } +export function getIsAiIntegration(source: IntegrationSource): boolean { + return source === 'aiIntegrations'; +} + export function getGoogleCredentialsPlaceholder( useWorkloadId: boolean, updatePassword: boolean diff --git a/ui/apps/platform/src/services/AiIntegrationsService.ts b/ui/apps/platform/src/services/AiIntegrationsService.ts index c01add7599062..0e63df4d66a5d 100644 --- a/ui/apps/platform/src/services/AiIntegrationsService.ts +++ b/ui/apps/platform/src/services/AiIntegrationsService.ts @@ -40,6 +40,10 @@ export function deleteAiIntegration(id: string): Promise { return axios.delete(`${aiIntegrationsUrl}/${id}`).then((response) => response.data); } +export function deleteAiIntegrations(ids: string[]): Promise { + return Promise.all(ids.map(deleteAiIntegration)); +} + export function testAiIntegration(data: AiIntegration): Promise { return axios.post(`${aiIntegrationsUrl}/test`, data).then((response) => response.data); } diff --git a/ui/apps/platform/src/services/IntegrationsService.ts b/ui/apps/platform/src/services/IntegrationsService.ts index 02a9ce9f05485..a7eb4ec88feb1 100644 --- a/ui/apps/platform/src/services/IntegrationsService.ts +++ b/ui/apps/platform/src/services/IntegrationsService.ts @@ -12,6 +12,7 @@ export const serviceIntegrationSources = [ 'notifiers', 'signatureIntegrations', 'cloudSources', + 'aiIntegrations', ] as const; export type IntegrationSource = (typeof serviceIntegrationSources)[number]; @@ -34,6 +35,8 @@ function getPath(source: IntegrationSource): string { return '/v1/auth/m2m'; case 'cloudSources': return '/v1/cloud-sources'; + case 'aiIntegrations': + return '/v2/ai-integrations'; default: return ''; } From 22e0f49bfcfecc90b6617413e6c238b7305f7b8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:18:21 +0200 Subject: [PATCH 149/638] chore(deps): bump github.com/RoaringBitmap/roaring/v2 from 2.24.0 to 2.26.0 (#22527) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 350b4a0939024..08557dc4e1cd7 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/PagerDuty/go-pagerduty v1.8.0 - github.com/RoaringBitmap/roaring/v2 v2.24.0 + github.com/RoaringBitmap/roaring/v2 v2.26.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 github.com/adhocore/gronx v1.20.3 diff --git a/go.sum b/go.sum index e6ed4e53a4e1d..d4adb3a119820 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/RaduBerinde/axisds v0.1.0 h1:YItk/RmU5nvlsv/awo2Fjx97Mfpt4JfgtEVAGPrL github.com/RaduBerinde/axisds v0.1.0/go.mod h1:UHGJonU9z4YYGKJxSaC6/TNcLOBptpmM5m2Cksbnw0Y= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 h1:bsU8Tzxr/PNz75ayvCnxKZWEYdLMPDkUgticP4a4Bvk= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54/go.mod h1:0tr7FllbE9gJkHq7CVeeDDFAFKQVy5RnCSSNBOvdqbc= -github.com/RoaringBitmap/roaring/v2 v2.24.0 h1:zQkkBZtG3WRP4j+P3A5DO221SvL1Br88TJkhyqEQRZo= -github.com/RoaringBitmap/roaring/v2 v2.24.0/go.mod h1:SfT3of9nYh3vis1dIbCj4Yw6KQGujTN+f345nrN/0JA= +github.com/RoaringBitmap/roaring/v2 v2.26.0 h1:K30ZxF4vZcIKvJsbmgfiep2K64f+dILJqkYGoj4xnwU= +github.com/RoaringBitmap/roaring/v2 v2.26.0/go.mod h1:BZufmFbox589n3j5eOmyTaLSGXbRLc2LmQvjKjzSEGU= github.com/Shopify/toxiproxy/v2 v2.12.0 h1:d1x++lYZg/zijXPPcv7PH0MvHMzEI5aX/YuUi/Sw+yg= github.com/Shopify/toxiproxy/v2 v2.12.0/go.mod h1:R9Z38Pw6k2cGZWXHe7tbxjGW9azmY1KbDQJ1kd+h7Tk= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= From 3bea1372d1e66e67613a22696ecc3d9c24ddf46e Mon Sep 17 00:00:00 2001 From: Brad Lugo Date: Tue, 1 Sep 2026 13:48:29 -0700 Subject: [PATCH 150/638] ROX-36547: Enable SBOM Scanning by default (#22457) --- central/image/service/sbom_scan_http_handler_test.go | 8 +++----- pkg/features/list.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/central/image/service/sbom_scan_http_handler_test.go b/central/image/service/sbom_scan_http_handler_test.go index f895204ff9a36..869d9c7b2815b 100644 --- a/central/image/service/sbom_scan_http_handler_test.go +++ b/central/image/service/sbom_scan_http_handler_test.go @@ -42,6 +42,7 @@ func TestScanSBOMHttpHandler_ServeHTTP(t *testing.T) { t.Run("invalid request method", func(t *testing.T) { testutils.MustUpdateFeature(t, features.ScannerV4, true) + testutils.MustUpdateFeature(t, features.SBOMScanning, true) req := httptest.NewRequest(http.MethodGet, "/sbom", nil) recorder := httptest.NewRecorder() @@ -50,12 +51,9 @@ func TestScanSBOMHttpHandler_ServeHTTP(t *testing.T) { handler.ServeHTTP(recorder, req) res := recorder.Result() - body, err := io.ReadAll(res.Body) - require.NoError(t, err) - err = res.Body.Close() + err := res.Body.Close() assert.NoError(t, err) - assert.Equal(t, http.StatusNotImplemented, res.StatusCode) - assert.Contains(t, string(body), "SBOM Scanning is disabled") + assert.Equal(t, http.StatusMethodNotAllowed, res.StatusCode) }) t.Run("invalid media type", func(t *testing.T) { diff --git a/pkg/features/list.go b/pkg/features/list.go index 38a10c33dbe4b..33f5eb478939a 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -206,7 +206,7 @@ var ( ScannerV4StoreExternalIndexReports = registerFeature("Enables storing index reports from delegated scans to Central's Scanner V4 Indexer", "ROX_SCANNER_V4_STORE_EXTERNAL_INDEX_REPORTS", enabled) // SBOMScanning enables matching vulnerabilities to components found in Red Hat produced SBOMs. - SBOMScanning = registerFeature("Enables matching vulnerabilities to components found in Red Hat produced SBOMs", "ROX_SBOM_SCANNING") + SBOMScanning = registerFeature("Enables matching vulnerabilities to components found in Red Hat produced SBOMs", "ROX_SBOM_SCANNING", enabled) // SensorInformerWatchdog enables periodic logging of sensor informers that are stuck or have not synced. SensorInformerWatchdog = registerFeature("Sensor informer watchdog logging", "ROX_SENSOR_INFORMER_WATCHDOG", enabled) From 54f255f14d2de8d197d55708cdf502b6a3f315a2 Mon Sep 17 00:00:00 2001 From: Brad Lugo Date: Tue, 1 Sep 2026 13:49:39 -0700 Subject: [PATCH 151/638] ROX-36548: Update sbom scan CLI command description (#22547) --- roxctl/sbom/scan/scan.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/roxctl/sbom/scan/scan.go b/roxctl/sbom/scan/scan.go index fcaf6de295f73..93e70b782c8c1 100644 --- a/roxctl/sbom/scan/scan.go +++ b/roxctl/sbom/scan/scan.go @@ -54,8 +54,11 @@ func Command(cliEnvironment environment.Environment) *cobra.Command { c := &cobra.Command{ Use: "scan", - Short: "[DEV PREVIEW] Scan the specified SBOM and return scan results", - Long: "[DEV PREVIEW] Scan the specified SBOM and return scan results. You must have write permissions to the `Image` resource. Currently supports SPDX 2.3 JSON documents with content types: [`application/spdx+json`, `text/spdx+json`].", + Short: "(Technology Preview) Scan the specified SBOM and return scan results", + Long: "(Technology Preview) Scan the specified SBOM and return scan results. " + + "You must have write permissions to the `Image` resource. Currently supports SPDX 2.3 JSON documents with content types: " + + "[`application/spdx+json`, `text/spdx+json`]." + + common.TechPreviewLongText, RunE: util.RunENoArgs(func(c *cobra.Command) error { if err := sbomScanCmd.Construct(nil, c, objectPrinterFactory); err != nil { return err From 6eed1ab6b88e251b93d3d954d641167e9e43d399 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 2 Sep 2026 00:34:29 +0200 Subject: [PATCH 152/638] ROX-36682: Stop calling Scanner V2 node-inventory (#22560) Co-authored-by: Marcin Owsiany --- CHANGELOG.md | 2 ++ central/clusters/zip/render_test.go | 11 +++-------- compliance/cmd/compliance/main.go | 4 +++- .../templates/collector.yaml.htpl | 2 -- pkg/env/node_scan.go | 4 ++-- .../helmtest/collector-compliance.test.yaml | 19 +++++++++++++++++++ 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa0353dfec23..7c53f530ec870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Removed Features +- Compliance container no longer collects Scanner V2 node inventories. Node scanning continues via Scanner V4 index reports, as long as Scanner V4 is enabled. + ### Deprecated Features - ROX-26281: block creation of new GCR integrations. Users are directed to use Google Artifact Registry instead. diff --git a/central/clusters/zip/render_test.go b/central/clusters/zip/render_test.go index 45a5f34ddbe4c..97774b9b343cc 100644 --- a/central/clusters/zip/render_test.go +++ b/central/clusters/zip/render_test.go @@ -86,6 +86,9 @@ func doTestRenderOpenshif(t *testing.T, clusterType storage.ClusterType) { assert.True(t, foundMain) assert.Equal(t, "compliance", complianceCont.Name) + _, exists := getEnvVarValue(complianceCont.Env, env.NodeInventoryContainerEnabled.EnvVar()) + assert.False(t, exists, "compliance should not set %s; Go default is false", env.NodeInventoryContainerEnabled.EnvVar()) + if clusterType == storage.ClusterType_OPENSHIFT4_CLUSTER { nInvCont, found := findContainer(ds.Spec.Template.Spec.Containers, "node-inventory") assert.True(t, found, "node-inventory container should exist under collector DS") @@ -93,17 +96,9 @@ func doTestRenderOpenshif(t *testing.T, clusterType storage.ClusterType) { expectedScannerParts := strings.Split(strings.ReplaceAll(complianceCont.Image, "/main:", "/scanner-slim:"), ":") assert.Truef(t, strings.HasPrefix(nInvCont.Image, expectedScannerParts[0]), "scanner-slim image (%q) should be from the same registry as main (%q)", nInvCont.Image, complianceCont.Image) - - value, exists := getEnvVarValue(complianceCont.Env, env.NodeInventoryContainerEnabled.EnvVar()) - assert.True(t, exists) - assert.Equal(t, "true", value, "compliance should have %s=true", env.NodeInventoryContainerEnabled.EnvVar()) } else { _, foundNInv := findContainer(ds.Spec.Template.Spec.Containers, "node-inventory") assert.False(t, foundNInv, "node-inventory container must not exist under collector DS") - - value, exists := getEnvVarValue(complianceCont.Env, env.NodeInventoryContainerEnabled.EnvVar()) - assert.True(t, exists) - assert.Equalf(t, "false", value, "compliance should have %s=false", env.NodeInventoryContainerEnabled.EnvVar()) } } diff --git a/compliance/cmd/compliance/main.go b/compliance/cmd/compliance/main.go index 64180efb53a13..5f9811ac51c5a 100644 --- a/compliance/cmd/compliance/main.go +++ b/compliance/cmd/compliance/main.go @@ -31,7 +31,9 @@ func main() { cfg := index.DefaultNodeIndexerConfig() scanner := inventory.NewNodeInventoryComponentScanner(np) - scanner.Connect(env.NodeScanningEndpoint.Setting()) + if env.NodeInventoryContainerEnabled.BooleanSetting() { + scanner.Connect(env.NodeScanningEndpoint.Setting()) + } cachedNodeIndexer := index.NewCachingNodeIndexer(cfg, env.NodeIndexCacheDuration.DurationSetting(), env.NodeIndexCachePath.Setting()) ctx, cancel := context.WithCancel(context.Background()) diff --git a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl index 552f3929366dd..e86617992a14f 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl @@ -175,8 +175,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: ROX_CALL_NODE_INVENTORY_ENABLED - value: {{ if eq ._rox.env.openshift 4 }}"true"{{ else }}"false"{{ end }} {{- if eq ._rox.env.openshift 4 }} - name: ROX_NODE_INDEX_HOST_PATH value: "/hostindex" diff --git a/pkg/env/node_scan.go b/pkg/env/node_scan.go index d2c30b8fb1c91..582bcfd03c69d 100644 --- a/pkg/env/node_scan.go +++ b/pkg/env/node_scan.go @@ -18,8 +18,8 @@ var ( // scanning wait time. NodeScanningMaxInitialWait = registerDurationSetting("ROX_NODE_SCANNING_MAX_INITIAL_WAIT", 5*time.Minute) - // NodeInventoryContainerEnabled is used to tell compliance whether a connection to the node-inventory container should be attempted - NodeInventoryContainerEnabled = RegisterBooleanSetting("ROX_CALL_NODE_INVENTORY_ENABLED", true) + // NodeInventoryContainerEnabled gates whether compliance connects to the node-inventory container. + NodeInventoryContainerEnabled = RegisterBooleanSetting("ROX_CALL_NODE_INVENTORY_ENABLED", false) // NodeAnalysisDeadline is a time in which node-inventory component should reply to compliance NodeAnalysisDeadline = registerDurationSetting("ROX_NODE_SCANNING_DEADLINE", 30*time.Second) diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml index 754730bfcf9b5..e7b9c7577dd11 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml @@ -6,6 +6,25 @@ values: imagePullSecrets: allowNone: true tests: +- name: "does not set Scanner V2 node inventory env (Go default is off)" + expect: | + envVars(.daemonsets.collector; "compliance") | assertThat(has("ROX_CALL_NODE_INVENTORY_ENABLED") == false) + tests: + - name: "on kubernetes" + - name: "on OpenShift 4" + server: + visibleSchemas: + - openshift-4.12 + availableSchemas: + - openshift-4.12 +- name: "customize.envVars can re-enable Scanner V2 node inventory calls" + values: + customize: + envVars: + ROX_CALL_NODE_INVENTORY_ENABLED: "true" + expect: | + envVars(.daemonsets.collector; "compliance")["ROX_CALL_NODE_INVENTORY_ENABLED"] | assertThat(. == "true") + [container(.daemonsets.collector; "compliance") | .env[] | select(.name == "ROX_CALL_NODE_INVENTORY_ENABLED")] | length | assertThat(. == 1) - name: "test securityContext" tests: - name: "by default seLinuxOptions are enabled and use container_runtime_t as container runtime" From 33bc85d561f5eaa413e33428c5314ac12932c535 Mon Sep 17 00:00:00 2001 From: Gustavo Alkmim Date: Tue, 1 Sep 2026 15:35:38 -0700 Subject: [PATCH 153/638] ROX-36481: [UI] Add Top prefix to aggregate CVSS/CVE Severity columns (#22517) "CVSS" and "CVE severity" column headings changed to "Top CVSS" and "Top CVE Severity" on pages where the values are aggregated. --- .../vulnerabilities/nodeCves/cveDetailPage.test.ts | 8 ++++---- .../vulnerabilities/nodeCves/nodeDetailPage.test.ts | 12 ++++++------ .../virtualMachineCves/virtualMachinePage.test.ts | 4 ++-- .../workloadCves/deploymentSingle.test.ts | 2 +- .../Vulnerabilities/NodeCves/Node/CVEsTable.tsx | 8 ++++---- .../NodeCves/NodeCve/AffectedNodesTable.cy.jsx | 4 ++-- .../NodeCves/NodeCve/AffectedNodesTable.tsx | 8 ++++---- .../VirtualMachinePageVulnerabilities.tsx | 8 ++++---- .../VirtualMachineVulnerabilitiesTable.tsx | 12 ++++++------ .../AffectedVirtualMachinesTable.tsx | 8 ++++---- .../WorkloadCves/Tables/AffectedImagesTable.tsx | 12 ++++++------ .../Tables/DeploymentVulnerabilitiesTable.tsx | 6 +++--- .../Tables/ImageVulnerabilitiesTable.tsx | 12 ++++++------ 13 files changed, 52 insertions(+), 52 deletions(-) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/cveDetailPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/cveDetailPage.test.ts index 0122708d27e83..5eddf49637827 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/cveDetailPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/cveDetailPage.test.ts @@ -144,11 +144,11 @@ describe('Node CVEs - CVE Detail Page', () => { ); // check sorting of CVE Severity column - sortByTableHeader('CVE severity'); + sortByTableHeader('Top CVE severity'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'Severity', reversed: true }) ); - sortByTableHeader('CVE severity'); + sortByTableHeader('Top CVE severity'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'Severity', reversed: false }) ); @@ -164,11 +164,11 @@ describe('Node CVEs - CVE Detail Page', () => { ); // check sorting of CVSS column - sortByTableHeader('CVSS'); + sortByTableHeader('Top CVSS'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'CVSS', reversed: true }) ); - sortByTableHeader('CVSS'); + sortByTableHeader('Top CVSS'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'CVSS', reversed: false }) ); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts index 9a818546db0d9..687e12b993fce 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts @@ -122,11 +122,11 @@ describe('Node CVEs - Node Detail Page', () => { ); // check sorting of Top Severity column - sortByTableHeader('Top severity'); + sortByTableHeader('Top CVE severity'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'Severity', reversed: true }) ); - sortByTableHeader('Top severity'); + sortByTableHeader('Top CVE severity'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'Severity', reversed: false }) ); @@ -142,11 +142,11 @@ describe('Node CVEs - Node Detail Page', () => { ); // check sorting of CVSS column - sortByTableHeader('CVSS'); + sortByTableHeader('Top CVSS'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'CVSS', reversed: true }) ); - sortByTableHeader('CVSS'); + sortByTableHeader('Top CVSS'); waitAndYieldRequestBodyVariables().then( expectRequestedSort({ field: 'CVSS', reversed: false }) ); @@ -177,7 +177,7 @@ describe('Node CVEs - Node Detail Page', () => { waitAndYieldRequestBodyVariables().then( expectRequestedQuery('Severity:LOW_VULNERABILITY_SEVERITY') ); - assertOnEachRowForColumn('Top severity', (_, cell) => { + assertOnEachRowForColumn('Top CVE severity', (_, cell) => { expect(cell.innerText).to.contain('Low'); }); filterHelpers.clearFilters(); @@ -195,7 +195,7 @@ describe('Node CVEs - Node Detail Page', () => { // filtering by CVSS should only display rows with a CVSS in range filterHelpers.addNumericFilter('CVE', 'CVSS', 'Is less than', 8); waitAndYieldRequestBodyVariables().then(expectRequestedQuery('CVSS:<8')); - assertOnEachRowForColumn('CVSS', (_, cell) => { + assertOnEachRowForColumn('Top CVSS', (_, cell) => { const cvss = parseFloat(cell.innerText.replace(/[^0-9.]/g, '')); expect(cvss).to.be.lessThan(8); }); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts index 370893afdc89f..67daad1772c3e 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts @@ -74,9 +74,9 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { .eq(0) .within(() => { cy.get('td[data-label="CVE"]').contains('CVE-2024-0001'); - cy.get('td[data-label="CVE severity"]').contains('Critical'); + cy.get('td[data-label="Top CVE severity"]').contains('Critical'); cy.get('td[data-label="CVE status"]').contains('Fixable'); - cy.get('td[data-label="CVSS"]').contains('9.8'); + cy.get('td[data-label="Top CVSS"]').contains('9.8'); cy.get('td[data-label="EPSS probability"]').contains('85.000%'); cy.get('td[data-label="Affected components"]').contains('1 component'); }); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/deploymentSingle.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/deploymentSingle.test.ts index 778cc8a43e973..28ee7a461ba97 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/deploymentSingle.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/deploymentSingle.test.ts @@ -73,7 +73,7 @@ describe('Workload CVE Deployment Single page', () => { cy.get(vulnSelectors.summaryCard('CVEs by status')); // Check table exists with the correct headers cy.get('table thead tr th').contains('CVE'); - cy.get('table thead tr th').contains('CVE severity'); + cy.get('table thead tr th').contains('Top CVE severity'); cy.get('table thead tr th').contains('CVE status'); cy.get('table thead tr th').contains('Affected components'); cy.get('table thead tr th').contains('First discovered'); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Node/CVEsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Node/CVEsTable.tsx index cc9e80449e16b..4f2488c8c442d 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Node/CVEsTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Node/CVEsTable.tsx @@ -79,9 +79,9 @@ function CVEsTable({ tableState, getSortParams, onClearFilters }: CVEsTableProps
    - + - + @@ -114,13 +114,13 @@ function CVEsTable({ tableState, getSortParams, onClearFilters }: CVEsTableProps - - - + - + @@ -152,13 +152,13 @@ function AffectedNodesTable({ - - - + - + @@ -144,7 +144,7 @@ function VirtualMachinePageVulnerabilities({ - - - - + - + @@ -92,7 +92,7 @@ function AffectedVirtualMachinesTable({ /> - - - + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx index 7c903f4efd429..052979790da06 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx @@ -76,7 +76,7 @@ export const defaultColumns = { isUntoggleAble: true, }, cveSeverity: { - title: 'CVE severity', + title: 'Top CVE severity', isShownByDefault: true, }, cveStatus: { @@ -84,7 +84,7 @@ export const defaultColumns = { isShownByDefault: true, }, cvss: { - title: 'CVSS', + title: 'Top CVSS', isShownByDefault: true, }, nvdCvss: { @@ -214,14 +214,14 @@ function ImageVulnerabilitiesTable({ className={getVisibilityClass('cveSeverity')} sort={getSortParams('Severity')} > - CVE severity + Top CVE severity From 9938e76e9dcd415d926ae1eedd03d5f20f2b6a11 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:25:05 +0200 Subject: [PATCH 154/638] fix(sensor): Improve VM scraper debug logging (#22552) --- .../common/virtualmachine/vmscraper/scraper.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index bab9d23190070..8b2de094f5cf9 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -292,24 +292,25 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { } due := s.dueKeys() + nDue := len(due) nTracked := concurrency.WithLock1(&s.mu, func() int { n := len(s.vmState) metrics.PullTrackedVMs.Set(float64(n)) return n }) - metrics.PullDueVMs.Set(float64(len(due))) + metrics.PullDueVMs.Set(float64(nDue)) capN := min(s.concurrency, startBudget(nTracked, s.tickInterval, newVMIndexReportWindow(s.interval))) - if capN < len(due) { + if capN < nDue { due = due[:capN] } - log.Debugf("VMScraper: tick: %d due VMs %v (concurrency=%d, reconcile=%v)", len(due), due, s.concurrency, reconcile) - metrics.PullTicksTotal.Inc() - - // A tick launches every due VM, so this is the start count. started := len(due) + // Idle ticks stay silent so a 10s scheduler step does not look like an empty cluster. if started > 0 { + log.Debugf("VMScraper: tick: %d due of %d tracked, starting %d %v (concurrency=%d, reconcile=%v)", + nDue, nTracked, started, due, s.concurrency, reconcile) metrics.PullStartsPerTick.Observe(float64(started)) } + metrics.PullTicksTotal.Inc() var successCount atomic.Int32 g, gCtx := errgroup.WithContext(ctx) @@ -326,7 +327,10 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { _ = g.Wait() elapsed := s.now().Sub(tickStart) - log.Debugf("VMScraper: tick done: %d/%d due VMs succeeded in %s", successCount.Load(), len(due), elapsed.Truncate(time.Millisecond)) + if started > 0 { + log.Debugf("VMScraper: tick done: %d/%d started of %d tracked succeeded in %s", + successCount.Load(), started, nTracked, elapsed.Truncate(time.Millisecond)) + } metrics.PullTickDurationSeconds.Observe(elapsed.Seconds()) } From 5f0e8eaec378a8a990fd09a8397f75b2d701b55b Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:41:45 +0000 Subject: [PATCH 155/638] Update Konflux references (#22545) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 20 ++++++++++---------- .tekton/checks.yaml | 4 ++-- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/main-pipeline.yaml | 20 ++++++++++---------- .tekton/operator-bundle-pipeline.yaml | 18 +++++++++--------- .tekton/operator-index-build.yaml | 6 +++--- .tekton/retag-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 20 ++++++++++---------- 8 files changed, 46 insertions(+), 46 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 18b43265a8679..645008c3cd8c5 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -251,7 +251,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 - name: kind value: task resolver: bundles @@ -310,7 +310,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 - name: kind value: task resolver: bundles @@ -350,7 +350,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 - name: kind value: task resolver: bundles @@ -395,7 +395,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:97e2b2cdca9110fdc8a93ba585a1a1a743f989f2fd85f4073f6e5ea9ad2ce828 - name: kind value: task resolver: bundles @@ -418,7 +418,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:b961f8b259483fac39efc9371faca88faea528d2572ae30b79a9d3b56db8364b + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 - name: kind value: task resolver: bundles @@ -521,7 +521,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.3@sha256:9df272d2180bd67e6a3e04d5cd30ffa7ba27af4e599a1922f7069f5c89888e5b - name: kind value: task resolver: bundles @@ -567,7 +567,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index d60a34aa5f9a1..ffcfa9c4d3f40 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -146,7 +146,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:cf32941c77e413818c4ef6b78c2a88d6e6ddc21729767d8f4d316cd293e4a15d + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:7d15b5066a8bedc8926286078baa5fec5d42cc7c06054823581c62ccc7a93e86 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 4a45aa3efb368..ad62ef02172fe 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -201,7 +201,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 1a7f3e4cd92bb..2e3ce58efe46f 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -169,7 +169,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c - name: kind value: task resolver: bundles @@ -193,7 +193,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 - name: kind value: task resolver: bundles @@ -331,7 +331,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 - name: kind value: task resolver: bundles @@ -351,7 +351,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 - name: kind value: task resolver: bundles @@ -372,7 +372,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 - name: kind value: task resolver: bundles @@ -417,7 +417,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:97e2b2cdca9110fdc8a93ba585a1a1a743f989f2fd85f4073f6e5ea9ad2ce828 - name: kind value: task resolver: bundles @@ -440,7 +440,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:b961f8b259483fac39efc9371faca88faea528d2572ae30b79a9d3b56db8364b + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 - name: kind value: task resolver: bundles @@ -543,7 +543,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.3@sha256:9df272d2180bd67e6a3e04d5cd30ffa7ba27af4e599a1922f7069f5c89888e5b - name: kind value: task resolver: bundles @@ -589,7 +589,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 49e24b95e1857..9814d2a889b7c 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -265,7 +265,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c - name: kind value: task resolver: bundles @@ -289,7 +289,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -348,7 +348,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 - name: kind value: task resolver: bundles @@ -517,7 +517,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.0@sha256:cf32941c77e413818c4ef6b78c2a88d6e6ddc21729767d8f4d316cd293e4a15d + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:7d15b5066a8bedc8926286078baa5fec5d42cc7c06054823581c62ccc7a93e86 - name: kind value: task resolver: bundles @@ -536,7 +536,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3@sha256:2dae3c4beaf1070db565c536bc997e93925ae8c0c45dd8da9b4ae5293fd8c592 + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3@sha256:4a4dfe9d15f165d346eae60a67571672eeaa1d4d4789f036a732f8f28254f870 - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 - name: kind value: task resolver: bundles @@ -596,7 +596,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:97e2b2cdca9110fdc8a93ba585a1a1a743f989f2fd85f4073f6e5ea9ad2ce828 - name: kind value: task resolver: bundles @@ -716,7 +716,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.3@sha256:9df272d2180bd67e6a3e04d5cd30ffa7ba27af4e599a1922f7069f5c89888e5b - name: kind value: task resolver: bundles @@ -762,7 +762,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 069afbaa200cc..eecb5a3d19b23 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -195,7 +195,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -414,7 +414,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 - name: kind value: task resolver: bundles @@ -433,7 +433,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 6e3bb864b6aa0..205ac1fd3c30c 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 9ac9392fddbc0..a58378f6e645f 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:5f687152ab0661f84830877de93a8ec45cb0b756f71b24ac55133d03a4007936 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:3bcd4c39a346d29617013f4c47d245d38f020cdbf814cf3ce095911c528f5003 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c - name: kind value: task resolver: bundles @@ -271,7 +271,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.1@sha256:c09c1c3ced67fb8740b2925a7de09c0810d4e8c8f148d41c21ffe37810d85c62 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 - name: kind value: task resolver: bundles @@ -330,7 +330,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.0@sha256:44c1ca2e823f006ee94e066231b033b191f0f8cee51add1e908138335eba9b30 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 - name: kind value: task resolver: bundles @@ -349,7 +349,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:c2cda69e681c976dd5b39caf6682d1c8f8c5e5a53a67a22a5b06d4cde773bd10 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 - name: kind value: task resolver: bundles @@ -370,7 +370,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6bb2697f8f194a6a644b8b861489ce8b1c5ba16a7d55dbd8e66a0c98d06dfc1c + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 - name: kind value: task resolver: bundles @@ -415,7 +415,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:76ed85aa05ce42d31341269837f104c3b766508313a68439085dcf9cc89d03f4 + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:97e2b2cdca9110fdc8a93ba585a1a1a743f989f2fd85f4073f6e5ea9ad2ce828 - name: kind value: task resolver: bundles @@ -438,7 +438,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:b961f8b259483fac39efc9371faca88faea528d2572ae30b79a9d3b56db8364b + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 - name: kind value: task resolver: bundles @@ -541,7 +541,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.2@sha256:90be6a6537578b35687ae3639970be0eb273dfc73dabe4039bdcb307c555c086 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.3@sha256:9df272d2180bd67e6a3e04d5cd30ffa7ba27af4e599a1922f7069f5c89888e5b - name: kind value: task resolver: bundles @@ -587,7 +587,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:393b4d0b530c2657b3ff6fec714781f4938b95b699dea2b369442b7664e1cce2 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf - name: kind value: task resolver: bundles From e717d7175bc4bb7009f142a08b3b3d93df005297 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:49:48 +0000 Subject: [PATCH 156/638] Update all dependencies (#22521) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .konflux/konflux.check-go-builder-version.Dockerfile | 2 +- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 4 ++-- image/rhel/konflux.Dockerfile | 6 +++--- image/roxctl/konflux.Dockerfile | 4 ++-- operator/konflux.Dockerfile | 4 ++-- operator/konflux.bundle.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.konflux/konflux.check-go-builder-version.Dockerfile b/.konflux/konflux.check-go-builder-version.Dockerfile index 262af6002fa6a..f9b99226d5379 100644 --- a/.konflux/konflux.check-go-builder-version.Dockerfile +++ b/.konflux/konflux.check-go-builder-version.Dockerfile @@ -1,4 +1,4 @@ -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 WORKDIR /workspace diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index ad62ef02172fe..78d0e87519cba 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:580752f96d36c4132bffd30f9c34865bf4bd87f6aa161c969d117f21732e50f7 + image: registry.redhat.io/ubi9-minimal:latest@sha256:7fbeae18dc9476399f565e68255f602a3374ea8614ba3d14843565131a13ff93 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index eecb5a3d19b23..9f4194cc2308a 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:580752f96d36c4132bffd30f9c34865bf4bd87f6aa161c969d117f21732e50f7 + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:7fbeae18dc9476399f565e68255f602a3374ea8614ba3d14843565131a13ff93 imagePullPolicy: IfNotPresent script: | #!/bin/bash @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1787774815@sha256:1a755651ffe1a438f137418d183f18ebad527ec929206c17804f93490a97869e + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1788245275@sha256:8cf89835994846ca0dffb9078e3a5638c57ec6175750f0af02fbe9c9942696d3 imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index bad5909d7b109..05832d6b6ca37 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -1,7 +1,7 @@ ARG PG_VERSION=15 -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 AS go-builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS go-builder RUN dnf -y install --allowerasing jq @@ -37,7 +37,7 @@ RUN mkdir -p image/rhel/docs/api/v1 && \ RUN make copy-go-binaries-to-image-dir -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:57ad03c5db9382cd266af665819f60e5b1490fc27ee589320159df59f9eb453b as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:7679e533a1b91b206351b2b0b574f23de0697de57e98244cafbd30ed6879a336 as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -61,7 +61,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index c2cc8245055c9..ddccfec721b47 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -4,7 +4,7 @@ # - https://issues.redhat.com/browse/RHTAPBUGS-864 - deprecated-base-image-check behaves incorrectly. # - https://issues.redhat.com/browse/RHTAPBUGS-865 - openshift-golang-builder is not considered to be a valid base image. # -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 AS builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index d54bfa3772553..5f8cadaaca0fb 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 AS builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.bundle.Dockerfile b/operator/konflux.bundle.Dockerfile index c121f5fd68352..49cbd4ade5bb6 100644 --- a/operator/konflux.bundle.Dockerfile +++ b/operator/konflux.bundle.Dockerfile @@ -1,4 +1,4 @@ -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 AS builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS builder COPY . /stackrox WORKDIR /stackrox/operator diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index c57f45f10b9ea..dc3384a800b4f 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:3ca2c34c83e6563fa574b7c7ecda92a5fdae2ec7a5f37a408e5e383947fb1957 AS builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS builder ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:b8c53f907b7ea8934d6bb23b319ca7b5ab567e61a0806ffc80170631cabc7563 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:25a147defd01e19674714f55d17538c8dbe55d8c305fa157ecc3f9c8977b05b6 AS package_installer COPY --from=ubi-micro-base / /out/ From 58b275019bd6dc600250031c27a2acd3b3b8613b Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Wed, 2 Sep 2026 05:39:58 -0400 Subject: [PATCH 157/638] fix(vm): vm4vm bug bash fixes (#22512) Co-authored-by: bradr5 Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Piotr Rygielski <114479+vikin91@users.noreply.github.com> --- .../convert/v1tov2storage/vm_scan_parts.go | 9 + central/views/vmcve/db_response.go | 25 +++ central/views/vmcve/mocks/types.go | 160 +++++++++++++++++- central/views/vmcve/types.go | 16 +- central/views/vmcve/view_impl.go | 44 ++++- .../virtualmachine/v2/service/service_impl.go | 65 ++++++- .../v2/service/service_impl_test.go | 48 +++++- 7 files changed, 347 insertions(+), 20 deletions(-) diff --git a/central/convert/v1tov2storage/vm_scan_parts.go b/central/convert/v1tov2storage/vm_scan_parts.go index c14d9d9bb0cb3..aa9710f7964d6 100644 --- a/central/convert/v1tov2storage/vm_scan_parts.go +++ b/central/convert/v1tov2storage/vm_scan_parts.go @@ -126,6 +126,14 @@ func convertCVEBaseInfo(info *storage.VirtualMachineCVEInfo) *storage.CVEInfo { }) } + var epss *storage.EPSS + if e := info.GetEpss(); e != nil { + epss = &storage.EPSS{ + EpssProbability: e.GetEpssProbability(), + EpssPercentile: e.GetEpssPercentile(), + } + } + return &storage.CVEInfo{ Cve: info.GetCve(), Summary: info.GetSummary(), @@ -135,6 +143,7 @@ func convertCVEBaseInfo(info *storage.VirtualMachineCVEInfo) *storage.CVEInfo { LastModified: info.GetLastModified(), CvssMetrics: info.GetCvssMetrics(), References: refs, + Epss: epss, } } diff --git a/central/views/vmcve/db_response.go b/central/views/vmcve/db_response.go index 76ebcf0f9bd7c..60570311ab830 100644 --- a/central/views/vmcve/db_response.go +++ b/central/views/vmcve/db_response.go @@ -121,6 +121,31 @@ func (r *resourceCountByFixability) GetFixable() int { return r.fixable } +type cveForVMResponse struct { + CVE string `db:"cve"` + MaxSeverity int32 `db:"severity_max"` + FixableCount int `db:"fixable_count"` + MaxCVSS float32 `db:"cvss_max"` + MaxNVDCVSS float32 `db:"nvd_cvss_max"` + EPSSProbabilityMax *float32 `db:"epss_probability_max"` + AffectedComponentCount int `db:"component_id_count"` + Published *time.Time `db:"cve_published_on_min"` +} + +func (r *cveForVMResponse) GetCVE() string { return r.CVE } +func (r *cveForVMResponse) GetMaxSeverity() int32 { return r.MaxSeverity } +func (r *cveForVMResponse) GetIsFixable() bool { return r.FixableCount > 0 } +func (r *cveForVMResponse) GetMaxCVSS() float32 { return r.MaxCVSS } +func (r *cveForVMResponse) GetMaxNVDCVSS() float32 { return r.MaxNVDCVSS } +func (r *cveForVMResponse) GetAffectedComponentCount() int { return r.AffectedComponentCount } +func (r *cveForVMResponse) GetPublishDate() *time.Time { return r.Published } +func (r *cveForVMResponse) GetEPSSProbability() float32 { + if r.EPSSProbabilityMax == nil { + return 0 + } + return *r.EPSSProbabilityMax +} + type cveComponentResponse struct { ComponentName string `db:"component"` ComponentVersion string `db:"component_version"` diff --git a/central/views/vmcve/mocks/types.go b/central/views/vmcve/mocks/types.go index 035a11df50a7c..f370a8f81b349 100644 --- a/central/views/vmcve/mocks/types.go +++ b/central/views/vmcve/mocks/types.go @@ -17,6 +17,7 @@ import ( common "github.com/stackrox/rox/central/views/common" vmcve "github.com/stackrox/rox/central/views/vmcve" v1 "github.com/stackrox/rox/generated/api/v1" + search "github.com/stackrox/rox/pkg/search" gomock "go.uber.org/mock/gomock" ) @@ -305,18 +306,18 @@ func (mr *MockCveViewMockRecorder) CountAffectedVMs(ctx, q any) *gomock.Call { } // CountBySeverity mocks base method. -func (m *MockCveView) CountBySeverity(ctx context.Context, q *v1.Query) (common.ResourceCountByCVESeverity, error) { +func (m *MockCveView) CountBySeverity(ctx context.Context, q *v1.Query, countOn search.FieldLabel) (common.ResourceCountByCVESeverity, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CountBySeverity", ctx, q) + ret := m.ctrl.Call(m, "CountBySeverity", ctx, q, countOn) ret0, _ := ret[0].(common.ResourceCountByCVESeverity) ret1, _ := ret[1].(error) return ret0, ret1 } // CountBySeverity indicates an expected call of CountBySeverity. -func (mr *MockCveViewMockRecorder) CountBySeverity(ctx, q any) *gomock.Call { +func (mr *MockCveViewMockRecorder) CountBySeverity(ctx, q, countOn any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountBySeverity", reflect.TypeOf((*MockCveView)(nil).CountBySeverity), ctx, q) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountBySeverity", reflect.TypeOf((*MockCveView)(nil).CountBySeverity), ctx, q, countOn) } // CountBySeverityPerVM mocks base method. @@ -379,6 +380,21 @@ func (mr *MockCveViewMockRecorder) GetCVEComponents(ctx, q any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCVEComponents", reflect.TypeOf((*MockCveView)(nil).GetCVEComponents), ctx, q) } +// GetCVEsForVM mocks base method. +func (m *MockCveView) GetCVEsForVM(ctx context.Context, q *v1.Query) ([]vmcve.CVEForVMCore, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCVEsForVM", ctx, q) + ret0, _ := ret[0].([]vmcve.CVEForVMCore) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCVEsForVM indicates an expected call of GetCVEsForVM. +func (mr *MockCveViewMockRecorder) GetCVEsForVM(ctx, q any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCVEsForVM", reflect.TypeOf((*MockCveView)(nil).GetCVEsForVM), ctx, q) +} + // GetVMIDs mocks base method. func (m *MockCveView) GetVMIDs(ctx context.Context, q *v1.Query) ([]string, error) { m.ctrl.T.Helper() @@ -394,6 +410,142 @@ func (mr *MockCveViewMockRecorder) GetVMIDs(ctx, q any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVMIDs", reflect.TypeOf((*MockCveView)(nil).GetVMIDs), ctx, q) } +// MockCVEForVMCore is a mock of CVEForVMCore interface. +type MockCVEForVMCore struct { + ctrl *gomock.Controller + recorder *MockCVEForVMCoreMockRecorder + isgomock struct{} +} + +// MockCVEForVMCoreMockRecorder is the mock recorder for MockCVEForVMCore. +type MockCVEForVMCoreMockRecorder struct { + mock *MockCVEForVMCore +} + +// NewMockCVEForVMCore creates a new mock instance. +func NewMockCVEForVMCore(ctrl *gomock.Controller) *MockCVEForVMCore { + mock := &MockCVEForVMCore{ctrl: ctrl} + mock.recorder = &MockCVEForVMCoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCVEForVMCore) EXPECT() *MockCVEForVMCoreMockRecorder { + return m.recorder +} + +// GetAffectedComponentCount mocks base method. +func (m *MockCVEForVMCore) GetAffectedComponentCount() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAffectedComponentCount") + ret0, _ := ret[0].(int) + return ret0 +} + +// GetAffectedComponentCount indicates an expected call of GetAffectedComponentCount. +func (mr *MockCVEForVMCoreMockRecorder) GetAffectedComponentCount() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAffectedComponentCount", reflect.TypeOf((*MockCVEForVMCore)(nil).GetAffectedComponentCount)) +} + +// GetCVE mocks base method. +func (m *MockCVEForVMCore) GetCVE() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCVE") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetCVE indicates an expected call of GetCVE. +func (mr *MockCVEForVMCoreMockRecorder) GetCVE() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCVE", reflect.TypeOf((*MockCVEForVMCore)(nil).GetCVE)) +} + +// GetEPSSProbability mocks base method. +func (m *MockCVEForVMCore) GetEPSSProbability() float32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEPSSProbability") + ret0, _ := ret[0].(float32) + return ret0 +} + +// GetEPSSProbability indicates an expected call of GetEPSSProbability. +func (mr *MockCVEForVMCoreMockRecorder) GetEPSSProbability() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEPSSProbability", reflect.TypeOf((*MockCVEForVMCore)(nil).GetEPSSProbability)) +} + +// GetIsFixable mocks base method. +func (m *MockCVEForVMCore) GetIsFixable() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetIsFixable") + ret0, _ := ret[0].(bool) + return ret0 +} + +// GetIsFixable indicates an expected call of GetIsFixable. +func (mr *MockCVEForVMCoreMockRecorder) GetIsFixable() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIsFixable", reflect.TypeOf((*MockCVEForVMCore)(nil).GetIsFixable)) +} + +// GetMaxCVSS mocks base method. +func (m *MockCVEForVMCore) GetMaxCVSS() float32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxCVSS") + ret0, _ := ret[0].(float32) + return ret0 +} + +// GetMaxCVSS indicates an expected call of GetMaxCVSS. +func (mr *MockCVEForVMCoreMockRecorder) GetMaxCVSS() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxCVSS", reflect.TypeOf((*MockCVEForVMCore)(nil).GetMaxCVSS)) +} + +// GetMaxNVDCVSS mocks base method. +func (m *MockCVEForVMCore) GetMaxNVDCVSS() float32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxNVDCVSS") + ret0, _ := ret[0].(float32) + return ret0 +} + +// GetMaxNVDCVSS indicates an expected call of GetMaxNVDCVSS. +func (mr *MockCVEForVMCoreMockRecorder) GetMaxNVDCVSS() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxNVDCVSS", reflect.TypeOf((*MockCVEForVMCore)(nil).GetMaxNVDCVSS)) +} + +// GetMaxSeverity mocks base method. +func (m *MockCVEForVMCore) GetMaxSeverity() int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxSeverity") + ret0, _ := ret[0].(int32) + return ret0 +} + +// GetMaxSeverity indicates an expected call of GetMaxSeverity. +func (mr *MockCVEForVMCoreMockRecorder) GetMaxSeverity() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxSeverity", reflect.TypeOf((*MockCVEForVMCore)(nil).GetMaxSeverity)) +} + +// GetPublishDate mocks base method. +func (m *MockCVEForVMCore) GetPublishDate() *time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPublishDate") + ret0, _ := ret[0].(*time.Time) + return ret0 +} + +// GetPublishDate indicates an expected call of GetPublishDate. +func (mr *MockCVEForVMCoreMockRecorder) GetPublishDate() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublishDate", reflect.TypeOf((*MockCVEForVMCore)(nil).GetPublishDate)) +} + // MockVMSeverityCounts is a mock of VMSeverityCounts interface. type MockVMSeverityCounts struct { ctrl *gomock.Controller diff --git a/central/views/vmcve/types.go b/central/views/vmcve/types.go index 9df0b1e249b57..a789ffed3d4bb 100644 --- a/central/views/vmcve/types.go +++ b/central/views/vmcve/types.go @@ -6,6 +6,7 @@ import ( "github.com/stackrox/rox/central/views/common" v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/pkg/search" ) // CveCore is an interface to get VM CVE properties. @@ -40,13 +41,26 @@ type CVEComponentCore interface { //go:generate mockgen-wrapper type CveView interface { Count(ctx context.Context, q *v1.Query) (int, error) - CountBySeverity(ctx context.Context, q *v1.Query) (common.ResourceCountByCVESeverity, error) + CountBySeverity(ctx context.Context, q *v1.Query, countOn search.FieldLabel) (common.ResourceCountByCVESeverity, error) Get(ctx context.Context, q *v1.Query) ([]CveCore, error) GetVMIDs(ctx context.Context, q *v1.Query) ([]string, error) GetCVEComponents(ctx context.Context, q *v1.Query) ([]CVEComponentCore, error) CountBySeverityPerVM(ctx context.Context, q *v1.Query) ([]VMSeverityCounts, error) GetAffectedVMs(ctx context.Context, q *v1.Query) ([]AffectedVMCore, error) CountAffectedVMs(ctx context.Context, q *v1.Query) (int, error) + GetCVEsForVM(ctx context.Context, q *v1.Query) ([]CVEForVMCore, error) +} + +// CVEForVMCore provides per-CVE aggregation scoped to a single VM. +type CVEForVMCore interface { + GetCVE() string + GetMaxSeverity() int32 + GetIsFixable() bool + GetMaxCVSS() float32 + GetMaxNVDCVSS() float32 + GetEPSSProbability() float32 + GetAffectedComponentCount() int + GetPublishDate() *time.Time } // VMSeverityCounts provides per-VM severity counts. diff --git a/central/views/vmcve/view_impl.go b/central/views/vmcve/view_impl.go index 9a7bb24521a9f..b159fcafcd330 100644 --- a/central/views/vmcve/view_impl.go +++ b/central/views/vmcve/view_impl.go @@ -35,7 +35,7 @@ func (v *vmCVECoreViewImpl) Count(ctx context.Context, q *v1.Query) (int, error) return pgSearch.RunDistinctCountForSchema(queryCtx, v.db, v.schema, q, search.CVE) } -func (v *vmCVECoreViewImpl) CountBySeverity(ctx context.Context, q *v1.Query) (common.ResourceCountByCVESeverity, error) { +func (v *vmCVECoreViewImpl) CountBySeverity(ctx context.Context, q *v1.Query, countOn search.FieldLabel) (common.ResourceCountByCVESeverity, error) { if err := common.ValidateQuery(q); err != nil { return nil, err } @@ -43,7 +43,7 @@ func (v *vmCVECoreViewImpl) CountBySeverity(ctx context.Context, q *v1.Query) (c queryCtx, cancel := contextutil.ContextWithTimeoutIfNotExists(ctx, queryTimeout) defer cancel() - result, err := pgSearch.RunSelectOneForSchema[resourceCountByVMCVESeverity](queryCtx, v.db, v.schema, common.WithCountBySeverityAndFixabilityQuery(q, search.VirtualMachineID)) + result, err := pgSearch.RunSelectOneForSchema[resourceCountByVMCVESeverity](queryCtx, v.db, v.schema, common.WithCountBySeverityAndFixabilityQuery(q, countOn)) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func (v *vmCVECoreViewImpl) CountBySeverityPerVM(ctx context.Context, q *v1.Quer return nil, err } - cloned := common.WithCountBySeverityAndFixabilityQuery(q, search.VirtualMachineID) + cloned := common.WithCountBySeverityAndFixabilityQuery(q, search.CVE) cloned.Selects = append([]*v1.QuerySelect{ search.NewQuerySelect(search.VirtualMachineID).Proto(), }, cloned.GetSelects()...) @@ -192,6 +192,44 @@ func (v *vmCVECoreViewImpl) GetAffectedVMs(ctx context.Context, q *v1.Query) ([] return ret, nil } +func (v *vmCVECoreViewImpl) GetCVEsForVM(ctx context.Context, q *v1.Query) ([]CVEForVMCore, error) { + if err := common.ValidateQuery(q); err != nil { + return nil, err + } + + cloned := q.CloneVT() + cloned = common.UpdateSortAggs(cloned) + cloned.Selects = []*v1.QuerySelect{ + search.NewQuerySelect(search.CVE).Proto(), + search.NewQuerySelect(search.Severity).AggrFunc(aggregatefunc.Max).Proto(), + search.NewQuerySelect(search.Fixable).AggrFunc(aggregatefunc.Count). + Filter("fixable_count", + search.NewQueryBuilder().AddBools(search.Fixable, true).ProtoQuery(), + ).Proto(), + search.NewQuerySelect(search.CVSS).AggrFunc(aggregatefunc.Max).Proto(), + search.NewQuerySelect(search.NVDCVSS).AggrFunc(aggregatefunc.Max).Proto(), + search.NewQuerySelect(search.EPSSProbablity).AggrFunc(aggregatefunc.Max).Proto(), + search.NewQuerySelect(search.ComponentID).AggrFunc(aggregatefunc.Count).Distinct().Proto(), + search.NewQuerySelect(search.CVEPublishedOn).AggrFunc(aggregatefunc.Min).Proto(), + } + cloned.GroupBy = &v1.QueryGroupBy{ + Fields: []string{search.CVE.String()}, + } + + queryCtx, cancel := contextutil.ContextWithTimeoutIfNotExists(ctx, queryTimeout) + defer cancel() + + var ret []CVEForVMCore + err := pgSearch.RunSelectRequestForSchemaFn[cveForVMResponse](queryCtx, v.db, v.schema, cloned, func(r *cveForVMResponse) error { + ret = append(ret, r) + return nil + }) + if err != nil { + return nil, err + } + return ret, nil +} + func (v *vmCVECoreViewImpl) GetCVEComponents(ctx context.Context, q *v1.Query) ([]CVEComponentCore, error) { cloned := q.CloneVT() cloned.Selects = []*v1.QuerySelect{ diff --git a/central/virtualmachine/v2/service/service_impl.go b/central/virtualmachine/v2/service/service_impl.go index d52945ecde6e8..e895b56e82859 100644 --- a/central/virtualmachine/v2/service/service_impl.go +++ b/central/virtualmachine/v2/service/service_impl.go @@ -303,7 +303,7 @@ func (s *serviceImpl) GetVMVulnSummary(ctx context.Context, request *v2.GetVMVul vmFilter = search.ConjunctionQuery(vmFilter, additionalQuery) } - severityCounts, err := s.cveView.CountBySeverity(ctx, vmFilter) + severityCounts, err := s.cveView.CountBySeverity(ctx, vmFilter, search.CVE) if err != nil { return nil, err } @@ -339,19 +339,50 @@ func (s *serviceImpl) ListVMCVEsByVM(ctx context.Context, request *v2.ListVMCVEs countQuery := searchQuery.CloneVT() countQuery.Pagination = nil - totalCount, err := s.cveDS.Count(ctx, countQuery) + totalCount, err := s.cveView.Count(ctx, countQuery) if err != nil { return nil, err } - cves, err := s.cveDS.SearchRawVMCVEs(ctx, searchQuery) + cves, err := s.cveView.GetCVEsForVM(ctx, searchQuery) + if err != nil { + return nil, err + } + + // Fetch one raw storage row per CVE for metadata not available through + // the view (summary, link). These are stored in the serialized blob. + cveIDs := make([]string, 0, len(cves)) + for _, cve := range cves { + cveIDs = append(cveIDs, cve.GetCVE()) + } + metadataByID, err := s.fetchCVEMetadata(ctx, request.GetVmId(), cveIDs) if err != nil { return nil, err } items := make([]*v2.VMCVERow, 0, len(cves)) for _, cve := range cves { - items = append(items, storagetov2.VirtualMachineCVEV2ToRow(cve)) + row := &v2.VMCVERow{ + Cve: cve.GetCVE(), + Severity: v2.VulnerabilitySeverity(cve.GetMaxSeverity()), + IsFixable: cve.GetIsFixable(), + Cvss: cve.GetMaxCVSS(), + NvdCvss: cve.GetMaxNVDCVSS(), + EpssProbability: cve.GetEPSSProbability(), + AffectedComponentCount: int32(cve.GetAffectedComponentCount()), + PublishedOn: protocompat.ConvertTimeToTimestampOrNil(cve.GetPublishDate()), + } + if meta, ok := metadataByID[cve.GetCVE()]; ok { + row.Summary = meta.GetCveBaseInfo().GetSummary() + row.Link = meta.GetCveBaseInfo().GetLink() + if adv := meta.GetAdvisory(); adv != nil && adv.GetName() != "" { + row.Advisory = &v2.Advisory{ + Name: adv.GetName(), + Link: adv.GetLink(), + } + } + } + items = append(items, row) } return &v2.ListVMCVEsByVMResponse{ @@ -360,6 +391,30 @@ func (s *serviceImpl) ListVMCVEsByVM(ctx context.Context, request *v2.ListVMCVEs }, nil } +// fetchCVEMetadata fetches one raw storage row per CVE for metadata fields +// (summary, link) that aren't available through the SQL view. +func (s *serviceImpl) fetchCVEMetadata(ctx context.Context, vmID string, cveIDs []string) (map[string]*storage.VirtualMachineCVEV2, error) { + if len(cveIDs) == 0 { + return nil, nil + } + q := search.ConjunctionQuery( + search.NewQueryBuilder().AddExactMatches(search.VirtualMachineID, vmID).ProtoQuery(), + search.NewQueryBuilder().AddExactMatches(search.CVE, cveIDs...).ProtoQuery(), + ) + rows, err := s.cveDS.SearchRawVMCVEs(ctx, q) + if err != nil { + return nil, err + } + result := make(map[string]*storage.VirtualMachineCVEV2, len(cveIDs)) + for _, row := range rows { + cveID := row.GetCveBaseInfo().GetCve() + if _, exists := result[cveID]; !exists { + result[cveID] = row + } + } + return result, nil +} + // GetVMCVEComponents returns components affected by a specific CVE on a specific VM. func (s *serviceImpl) GetVMCVEComponents(ctx context.Context, request *v2.GetVMCVEComponentsRequest) (*v2.GetVMCVEComponentsResponse, error) { if request.GetVmId() == "" || request.GetCveId() == "" { @@ -486,7 +541,7 @@ func (s *serviceImpl) GetVMCVEDetail(ctx context.Context, request *v2.GetVMCVEDe viewFilter = search.ConjunctionQuery(viewFilter, additionalQuery) } - severityCounts, err := s.cveView.CountBySeverity(ctx, viewFilter) + severityCounts, err := s.cveView.CountBySeverity(ctx, viewFilter, search.VirtualMachineID) if err != nil { return nil, err } diff --git a/central/virtualmachine/v2/service/service_impl_test.go b/central/virtualmachine/v2/service/service_impl_test.go index 593de8bf1f0a4..383443a1ce712 100644 --- a/central/virtualmachine/v2/service/service_impl_test.go +++ b/central/virtualmachine/v2/service/service_impl_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/stackrox/rox/central/convert/storagetov2" commonViews "github.com/stackrox/rox/central/views/common" @@ -18,11 +19,32 @@ import ( "github.com/stackrox/rox/pkg/fixtures/fixtureconsts" "github.com/stackrox/rox/pkg/grpc/testutils" "github.com/stackrox/rox/pkg/protoassert" + "github.com/stackrox/rox/pkg/search" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) +type testCVEForVM struct { + cve string + maxSeverity int32 + isFixable bool + maxCVSS float32 + maxNVDCVSS float32 + epssProbability float32 + componentCount int + publishDate *time.Time +} + +func (t *testCVEForVM) GetCVE() string { return t.cve } +func (t *testCVEForVM) GetMaxSeverity() int32 { return t.maxSeverity } +func (t *testCVEForVM) GetIsFixable() bool { return t.isFixable } +func (t *testCVEForVM) GetMaxCVSS() float32 { return t.maxCVSS } +func (t *testCVEForVM) GetMaxNVDCVSS() float32 { return t.maxNVDCVSS } +func (t *testCVEForVM) GetEPSSProbability() float32 { return t.epssProbability } +func (t *testCVEForVM) GetAffectedComponentCount() int { return t.componentCount } +func (t *testCVEForVM) GetPublishDate() *time.Time { return t.publishDate } + func TestAuthz(t *testing.T) { testutils.AssertAuthzWorks(t, &serviceImpl{}) } @@ -134,7 +156,7 @@ func TestGetVMVulnSummary(t *testing.T) { }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { mockVM.EXPECT().CountVirtualMachines(ctx, gomock.Any()).Return(1, nil) - mockView.EXPECT().CountBySeverity(ctx, gomock.Any()).Return(&commonViews.ResourceCountByImageCVESeverity{ + mockView.EXPECT().CountBySeverity(ctx, gomock.Any(), search.CVE).Return(&commonViews.ResourceCountByImageCVESeverity{ CriticalSeverityCount: 2, FixableCriticalSeverityCount: 1, ImportantSeverityCount: 3, @@ -189,7 +211,7 @@ func TestGetVMVulnSummary(t *testing.T) { func TestListVMCVEsByVM(t *testing.T) { ctx := context.Background() - cve1 := &storage.VirtualMachineCVEV2{ + cve1Raw := &storage.VirtualMachineCVEV2{ Id: "cve-uuid-1", VmV2Id: "vm-1", VmComponentId: "comp-1", @@ -204,6 +226,14 @@ func TestListVMCVEsByVM(t *testing.T) { EpssProbability: 0.5, } + cve1View := &testCVEForVM{ + cve: "CVE-2024-1234", + maxSeverity: int32(storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY), + isFixable: true, + maxCVSS: 7.5, + componentCount: 3, + } + tests := map[string]struct { request *v2.ListVMCVEsByVMRequest setupMock func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) @@ -231,8 +261,9 @@ func TestListVMCVEsByVM(t *testing.T) { VmId: fixtureconsts.VirtualMachine1, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockCVE.EXPECT().Count(ctx, gomock.Any()).Return(1, nil) - mockCVE.EXPECT().SearchRawVMCVEs(ctx, gomock.Any()).Return([]*storage.VirtualMachineCVEV2{cve1}, nil) + mockView.EXPECT().Count(ctx, gomock.Any()).Return(1, nil) + mockView.EXPECT().GetCVEsForVM(ctx, gomock.Any()).Return([]vmcve.CVEForVMCore{cve1View}, nil) + mockCVE.EXPECT().SearchRawVMCVEs(ctx, gomock.Any()).Return([]*storage.VirtualMachineCVEV2{cve1Raw}, nil) }, expectedCount: 1, }, @@ -274,6 +305,9 @@ func TestListVMCVEsByVM(t *testing.T) { assert.Equal(t, v2.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, row.GetSeverity()) assert.True(t, row.GetIsFixable()) assert.Equal(t, float32(7.5), row.GetCvss()) + assert.Equal(t, int32(3), row.GetAffectedComponentCount()) + assert.Equal(t, "test vuln 1", row.GetSummary()) + assert.Equal(t, "https://example.com/1", row.GetLink()) } } }) @@ -541,7 +575,7 @@ func TestGetVMCVEDetail(t *testing.T) { }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { mockCVE.EXPECT().SearchRawVMCVEs(ctx, gomock.Any()).Return([]*storage.VirtualMachineCVEV2{cve1}, nil) - mockView.EXPECT().CountBySeverity(ctx, gomock.Any()).Return(&commonViews.ResourceCountByImageCVESeverity{ + mockView.EXPECT().CountBySeverity(ctx, gomock.Any(), search.VirtualMachineID).Return(&commonViews.ResourceCountByImageCVESeverity{ CriticalSeverityCount: 2, }, nil) mockView.EXPECT().GetVMIDs(ctx, gomock.Any()).Return([]string{"vm-1"}, nil) @@ -558,7 +592,7 @@ func TestGetVMCVEDetail(t *testing.T) { }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { mockCVE.EXPECT().SearchRawVMCVEs(ctx, gomock.Any()).Return([]*storage.VirtualMachineCVEV2{cve1}, nil) - mockView.EXPECT().CountBySeverity(ctx, gomock.Any()).Return(&commonViews.ResourceCountByImageCVESeverity{ + mockView.EXPECT().CountBySeverity(ctx, gomock.Any(), search.VirtualMachineID).Return(&commonViews.ResourceCountByImageCVESeverity{ CriticalSeverityCount: 2, }, nil) mockView.EXPECT().GetVMIDs(ctx, gomock.Any()).Return([]string{"vm-1"}, nil) @@ -583,7 +617,7 @@ func TestGetVMCVEDetail(t *testing.T) { }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { mockCVE.EXPECT().SearchRawVMCVEs(ctx, gomock.Any()).Return([]*storage.VirtualMachineCVEV2{cve1}, nil) - mockView.EXPECT().CountBySeverity(ctx, gomock.Any()).Return(&commonViews.ResourceCountByImageCVESeverity{}, nil) + mockView.EXPECT().CountBySeverity(ctx, gomock.Any(), search.VirtualMachineID).Return(&commonViews.ResourceCountByImageCVESeverity{}, nil) mockView.EXPECT().GetVMIDs(ctx, gomock.Any()).Return(nil, nil) mockVM.EXPECT().CountVirtualMachines(ctx, gomock.Any()).Return(5, nil) }, From b228af63a1ad617da31059ba75db4de055498f5a Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Wed, 2 Sep 2026 10:23:29 +0000 Subject: [PATCH 158/638] chore(deps): bump go to 1.26.7 (#22535) Signed-off-by: Tomasz Janiszewski --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 08557dc4e1cd7..67dab160c2516 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/stackrox/rox -go 1.26.5 +go 1.26.7 require ( cloud.google.com/go/artifactregistry v1.26.0 From 017f2ffe372fe722b09cca8ced509e8d76f2d22b Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 2 Sep 2026 12:32:40 +0200 Subject: [PATCH 159/638] chore(triage): delegate CI triage to the investigator subagent (#22500) Co-authored-by: Claude Opus 4.8 (1M context) --- .claude/commands/triage.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.claude/commands/triage.md b/.claude/commands/triage.md index 188a69fb49de9..5b50228e254a2 100644 --- a/.claude/commands/triage.md +++ b/.claude/commands/triage.md @@ -1 +1,12 @@ -Analyze this CI failure and find a root cause and permanent solution: +Investigate this StackRox CI failure by launching the `stackrox-ci-failure-investigator` +subagent (via the Agent tool). Pass it the input below — a Prow build ID or URL, a failing +job name, a ROX-XXXXX ticket, or pasted logs — and have it download and read the CI +artifacts (Prow step logs plus the collected k8s service logs, pod descriptions, events, and +node capacity) before forming a hypothesis. + +Do not guess at a cause or attribute the failure to flakiness or a version being behind until +the artifacts rule everything else out. Follow the causal chain to its origin — a failed +check is usually a downstream symptom. Do not write code until the root cause is confirmed +with the user. + +$ARGUMENTS From 3ec5b1c5fa1bf950a652616f9d00205ba71542f5 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:53:05 +0200 Subject: [PATCH 160/638] ROX-35685: cache repo-to-CPE mapping in Sensor (#22143) Co-authored-by: Cursor --- .../virtualmachines/roxagent/cmd/serve.go | 10 +- sensor/common/scannerdefinitions/repo2cpe.go | 282 +++++++++++++ .../scannerdefinitions/repo2cpe_test.go | 378 ++++++++++++++++++ sensor/common/sensor/sensor.go | 6 +- 4 files changed, 665 insertions(+), 11 deletions(-) create mode 100644 sensor/common/scannerdefinitions/repo2cpe.go create mode 100644 sensor/common/scannerdefinitions/repo2cpe_test.go diff --git a/compliance/virtualmachines/roxagent/cmd/serve.go b/compliance/virtualmachines/roxagent/cmd/serve.go index 9921bd5221612..6bc282581f28a 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve.go +++ b/compliance/virtualmachines/roxagent/cmd/serve.go @@ -167,6 +167,7 @@ func runServe(ctx context.Context, cfg serveConfig) error { if err := urlUpdater.Start(serveCtx); err != nil { return fmt.Errorf("starting repository-to-CPE mapping downloader: %w", err) } + defer urlUpdater.Stop() } var wg sync.WaitGroup @@ -178,21 +179,14 @@ func runServe(ctx context.Context, cfg serveConfig) error { // Exiting, as systemd would kill this service anyway after TimeoutStartSec. cancelServe() wg.Wait() - if urlUpdater != nil { - urlUpdater.Stop() - } return err } <-serveCtx.Done() // Wait for Serve's graceful drain (in-flight connections) and the // rescan loop to finish before returning, so the process doesn't exit - // mid-drain or mid-scan. urlUpdater.Stop waits for its own background - // refresh loop the same way, once serveCtx is already done. + // mid-drain or mid-scan. wg.Wait() - if urlUpdater != nil { - urlUpdater.Stop() - } return nil } diff --git a/sensor/common/scannerdefinitions/repo2cpe.go b/sensor/common/scannerdefinitions/repo2cpe.go new file mode 100644 index 0000000000000..825c3e37fd41b --- /dev/null +++ b/sensor/common/scannerdefinitions/repo2cpe.go @@ -0,0 +1,282 @@ +package scannerdefinitions + +import ( + "context" + "crypto/x509" + "io" + "net/http" + "net/url" + "slices" + "sync/atomic" + "time" + + "github.com/pkg/errors" + "github.com/stackrox/rox/pkg/centralsensor" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/utils" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" + "github.com/stackrox/rox/sensor/common" + "github.com/stackrox/rox/sensor/common/centralclient" + "github.com/stackrox/rox/sensor/common/message" + "github.com/stackrox/rox/sensor/common/unimplemented" +) + +const ( + // repo2CPEFileParam matches sensorMappingsFile in compliance/node/index/indexer.go. + repo2CPEFileParam = "repo2cpe" + + // repo2CPERefreshInterval is the steady-state cadence other repo2cpe consumers use. + repo2CPERefreshInterval = 4 * time.Hour + // repo2CPERetryInterval is short so a cold cache reaches its first success quickly. + repo2CPERetryInterval = time.Minute + repo2CPEFetchTimeout = 30 * time.Second + + etagHeader = "ETag" + ifNoneMatchHeader = "If-None-Match" + lastModifiedHeader = "Last-Modified" + ifModifiedSinceHeader = "If-Modified-Since" +) + +var ( + _ common.SensorComponent = (*Repo2CPE)(nil) + + errStartMoreThanOnce = errors.New("repo-to-CPE refresher already started") +) + +// Repo2CPE caches Central's repo-to-CPE mapping and refreshes it in the +// background. Sensor starts and stops it like any other component. +type Repo2CPE struct { + unimplemented.Receiver + + centralClient *http.Client + centralReachable atomic.Bool + started atomic.Bool + cancel context.CancelFunc + + timerMu sync.Mutex + timer *time.Timer + + cacheMu sync.Mutex + cache repo2CPECache +} + +// repo2CPECache is Sensor's in-process copy of Central's repo-to-CPE mapping, +// independent of Handler's per-request proxying, plus enough state to issue +// a conditional GET and to tell "never fetched" apart from "stale". +type repo2CPECache struct { + mapping []byte + hash string + etag string + lastModified string + lastSuccess time.Time +} + +// NewRepo2CPE creates a refresher that fetches Central's repo-to-CPE mapping. +func NewRepo2CPE(centralEndpoint string, centralCertificates []*x509.Certificate) (*Repo2CPE, error) { + client, err := centralclient.AuthenticatedCentralHTTPClient(centralEndpoint, centralCertificates) + if err != nil { + return nil, errors.Wrap(err, "instantiating central HTTP transport") + } + return newRepo2CPE(client), nil +} + +func newRepo2CPE(client *http.Client) *Repo2CPE { + return &Repo2CPE{centralClient: client} +} + +func (r *Repo2CPE) Name() string { return "scannerdefinitions.Repo2CPE" } + +func (r *Repo2CPE) Capabilities() []centralsensor.SensorCapability { return nil } + +func (r *Repo2CPE) ResponsesC() <-chan *message.ExpiringMessage { return nil } + +func (r *Repo2CPE) Start() error { + if r.started.Swap(true) { + return errStartMoreThanOnce + } + ctx, cancel := context.WithCancel(context.Background()) + timer := time.NewTimer(0) + concurrency.WithLock(&r.timerMu, func() { + r.cancel = cancel + r.timer = timer + }) + go r.run(ctx, timer.C) + return nil +} + +func (r *Repo2CPE) Stop() { + concurrency.WithLock(&r.timerMu, func() { + if r.cancel != nil { + r.cancel() + } + if r.timer != nil { + r.timer.Stop() + } + }) +} + +// Notify kicks an immediate fetch when Central becomes reachable, so a cold +// cache does not wait out the current timer. +func (r *Repo2CPE) Notify(e common.SensorComponentEvent) { + log.Info(common.LogSensorComponentEvent(e, r.Name())) + switch e { + case common.SensorComponentEventCentralReachable: + r.centralReachable.Store(true) + r.resetTimer(0) + case common.SensorComponentEventOfflineMode: + r.centralReachable.Store(false) + } +} + +func (r *Repo2CPE) resetTimer(d time.Duration) { + concurrency.WithLock(&r.timerMu, func() { + if r.timer != nil { + r.timer.Reset(d) + } + }) +} + +// run fetches on each receive from ticks, then rearms the component timer. +func (r *Repo2CPE) run(ctx context.Context, ticks <-chan time.Time) { + for { + select { + case <-ctx.Done(): + return + case <-ticks: + if ctx.Err() != nil { + return + } + if !r.centralReachable.Load() { + r.resetTimer(repo2CPERetryInterval) + continue + } + fetchCtx, cancel := context.WithTimeout(ctx, repo2CPEFetchTimeout) + ok := r.attemptRepo2CPERefresh(fetchCtx) + cancel() + if ctx.Err() != nil { + return + } + delay := repo2CPERetryInterval + if ok { + delay = repo2CPERefreshInterval + } + r.resetTimer(delay) + } + } +} + +// FetchRepo2CPE returns a copy of the cached mapping. ok is false only +// when Central has never been fetched successfully; unreachable Central +// still yields the last-good copy. +func (r *Repo2CPE) FetchRepo2CPE(_ context.Context) (mapping []byte, hash string, ok bool) { + return concurrency.WithLock3(&r.cacheMu, func() ([]byte, string, bool) { + if r.cache.lastSuccess.IsZero() { + return nil, "", false + } + return slices.Clone(r.cache.mapping), r.cache.hash, true + }) +} + +// attemptRepo2CPERefresh issues one conditional GET for the repo-to-CPE +// mapping and updates the cache on success or "unchanged". It reports +// whether the attempt succeeded, so the caller can pick the next delay. +func (r *Repo2CPE) attemptRepo2CPERefresh(ctx context.Context) bool { + etag, lastModified := concurrency.WithLock2(&r.cacheMu, func() (string, string) { + return r.cache.etag, r.cache.lastModified + }) + + req, err := r.newRepo2CPERequest(ctx, etag, lastModified) + if err != nil { + log.Warnf("Failed to build repo-to-CPE mapping request: %v", err) + return false + } + + resp, err := r.centralClient.Do(req) + if err != nil { + log.Warnf("Failed to fetch repo-to-CPE mapping from central: %v", err) + return false + } + defer utils.IgnoreError(resp.Body.Close) + + switch resp.StatusCode { + case http.StatusNotModified: + r.recordRepo2CPEUnchanged(resp) + return true + case http.StatusOK: + body, err := io.ReadAll(io.LimitReader(resp.Body, cpemapping.MaxMappingBytes+1)) + if err != nil { + log.Warnf("Failed to read repo-to-CPE mapping response: %v", err) + return false + } + if err := cpemapping.ValidateMapping(body); err != nil { + log.Warnf("Repo-to-CPE mapping failed validation, keeping last-good: %v", err) + return false + } + r.recordRepo2CPESuccess(body, resp) + return true + default: + log.Warnf("Unexpected status %d fetching repo-to-CPE mapping from central", resp.StatusCode) + return false + } +} + +func (r *Repo2CPE) newRepo2CPERequest(ctx context.Context, etag, lastModified string) (*http.Request, error) { + centralURL := url.URL{ + Path: scannerDefsPath, + RawQuery: "file=" + repo2CPEFileParam, + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, centralURL.String(), nil) + if err != nil { + return nil, errors.Wrap(err, "creating request") + } + if etag != "" { + req.Header.Set(ifNoneMatchHeader, etag) + } + if lastModified != "" { + req.Header.Set(ifModifiedSinceHeader, lastModified) + } + return req, nil +} + +func (r *Repo2CPE) recordRepo2CPEUnchanged(resp *http.Response) { + concurrency.WithLock(&r.cacheMu, func() { + r.cache.lastSuccess = time.Now() + r.mergeRepo2CPEValidatorsNoLock(resp) + }) + log.Debug("Repo-to-CPE mapping unchanged (304)") +} + +// recordRepo2CPESuccess keeps the existing cached bytes when the new content +// hashes the same as what's cached, treating a same-hash 200 like a 304. +func (r *Repo2CPE) recordRepo2CPESuccess(body []byte, resp *http.Response) { + hash := cpemapping.HashMapping(body) + concurrency.WithLock(&r.cacheMu, func() { + r.cache.lastSuccess = time.Now() + if hash != r.cache.hash { + r.cache.mapping = body + r.cache.hash = hash + } + r.replaceRepo2CPEValidatorsNoLock(resp) + }) + log.Debugf("Fetched repo-to-CPE mapping from central, hash=%s", hash) +} + +// replaceRepo2CPEValidatorsNoLock copies validators from a 200, including +// empty values, so the next conditional GET cannot reuse a prior mapping's +// ETag or Last-Modified. +func (r *Repo2CPE) replaceRepo2CPEValidatorsNoLock(resp *http.Response) { + r.cache.etag = resp.Header.Get(etagHeader) + r.cache.lastModified = resp.Header.Get(lastModifiedHeader) +} + +// mergeRepo2CPEValidatorsNoLock applies non-empty validators from a 304, +// which may omit them. +func (r *Repo2CPE) mergeRepo2CPEValidatorsNoLock(resp *http.Response) { + if etag := resp.Header.Get(etagHeader); etag != "" { + r.cache.etag = etag + } + if lm := resp.Header.Get(lastModifiedHeader); lm != "" { + r.cache.lastModified = lm + } +} diff --git a/sensor/common/scannerdefinitions/repo2cpe_test.go b/sensor/common/scannerdefinitions/repo2cpe_test.go new file mode 100644 index 0000000000000..458d47f34577f --- /dev/null +++ b/sensor/common/scannerdefinitions/repo2cpe_test.go @@ -0,0 +1,378 @@ +package scannerdefinitions + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "net/url" + "slices" + "sync/atomic" + "testing" + "testing/synctest" + "time" + + "github.com/stackrox/rox/pkg/httputil" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" + "github.com/stackrox/rox/sensor/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testRepo2CPEMapping = `{"data":{"foo":{"cpes":["cpe:/o:foo"]}}}` + +// newTestCentralClient spins up a real httptest.Server and returns a client +// whose transport rewrites relative URLs to point at it, mirroring how the +// production Central transport fills in scheme/host for requests built with +// only a path (see newRepo2CPERequest). +func newTestCentralClient(t *testing.T, handler http.HandlerFunc) *http.Client { + t.Helper() + server := httptest.NewServer(handler) + t.Cleanup(server.Close) + + base, err := url.Parse(server.URL) + require.NoError(t, err) + + client := server.Client() + client.Transport = httputil.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { + req = req.Clone(req.Context()) + req.URL.Scheme = base.Scheme + req.URL.Host = base.Host + return http.DefaultTransport.RoundTrip(req) + }) + return client +} + +func countingMappingClient(fetches *atomic.Int32, mapping string) *http.Client { + return &http.Client{ + Transport: httputil.RoundTripperFunc(func(_ *http.Request) (*http.Response, error) { + fetches.Add(1) + return &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + Body: io.NopCloser(bytes.NewBufferString(mapping)), + }, nil + }), + } +} + +func TestAttemptRepo2CPERefresh(t *testing.T) { + const mappingV1 = `{"data":{"foo":{"cpes":["cpe:/o:foo"]}}}` + const mappingV2 = `{"data":{"bar":{"cpes":["cpe:/o:bar"]}}}` + hashV1 := cpemapping.HashMapping([]byte(mappingV1)) + hashV2 := cpemapping.HashMapping([]byte(mappingV2)) + seededSuccess := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC) + + tests := map[string]struct { + seedCache repo2CPECache + serverHandler http.HandlerFunc + networkErr bool + wantOK bool + wantMapping string + wantHash string + wantEtag string + wantLastModified string + }{ + "first fetch succeeds and populates an empty cache": { + serverHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, repo2CPEFileParam, r.URL.Query().Get("file")) + w.Header().Set(etagHeader, `"v1"`) + _, _ = w.Write([]byte(mappingV1)) + }, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + wantEtag: `"v1"`, + }, + "304 with matching validators keeps the cached mapping": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, etag: `"v1"`, lastModified: "old", lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, `"v1"`, r.Header.Get(ifNoneMatchHeader)) + w.WriteHeader(http.StatusNotModified) + }, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + wantEtag: `"v1"`, + wantLastModified: "old", + }, + "200 without validators replaces previously cached validators": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, etag: `"v1"`, lastModified: "old", lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(mappingV2)) + }, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + }, + "200 with a same-hash body is treated as unchanged": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(mappingV1)) + }, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + }, + "200 with a different hash replaces the cached mapping": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(mappingV2)) + }, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + }, + "an unexpected status leaves the cache untouched": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + }, + "a network error leaves the cache untouched": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + networkErr: true, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + }, + "an oversized response leaves the cache untouched": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write(bytes.Repeat([]byte("a"), cpemapping.MaxMappingBytes+1)) + }, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + }, + "an invalid mapping body leaves the cache untouched": { + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + serverHandler: func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`{"not":"a mapping"}`)) + }, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + r := &Repo2CPE{cache: tt.seedCache} + if tt.networkErr { + r.centralClient = &http.Client{ + Transport: httputil.RoundTripperFunc(func(_ *http.Request) (*http.Response, error) { + return nil, errors.New("connection refused") + }), + } + } else { + r.centralClient = newTestCentralClient(t, tt.serverHandler) + } + + ok := r.attemptRepo2CPERefresh(t.Context()) + + assert.Equal(t, tt.wantOK, ok) + assert.Equal(t, tt.wantMapping, string(r.cache.mapping)) + assert.Equal(t, tt.wantHash, r.cache.hash) + assert.Equal(t, tt.wantEtag, r.cache.etag) + assert.Equal(t, tt.wantLastModified, r.cache.lastModified) + if tt.wantOK { + assert.False(t, r.cache.lastSuccess.IsZero()) + } else { + assert.Equal(t, tt.seedCache.lastSuccess, r.cache.lastSuccess) + } + }) + } +} + +// TestAttemptRepo2CPERefresh_RetryRecoversWithoutLosingCache calls the +// refresh helper directly (no timers) to verify a failed attempt keeps +// serving the last good mapping and a later success can recover. +func TestAttemptRepo2CPERefresh_RetryRecoversWithoutLosingCache(t *testing.T) { + const mapping = `{"data":{"foo":{"cpes":["cpe:/o:foo"]}}}` + hash := cpemapping.HashMapping([]byte(mapping)) + + var fail atomic.Bool + r := newRepo2CPE(newTestCentralClient(t, func(w http.ResponseWriter, _ *http.Request) { + if fail.Load() { + w.WriteHeader(http.StatusInternalServerError) + return + } + _, _ = w.Write([]byte(mapping)) + })) + + require.True(t, r.attemptRepo2CPERefresh(t.Context())) + assert.Equal(t, hash, r.cache.hash) + require.False(t, r.cache.lastSuccess.IsZero()) + lastGood := r.cache.lastSuccess + + fail.Store(true) + require.False(t, r.attemptRepo2CPERefresh(t.Context())) + assert.Equal(t, hash, r.cache.hash, "a failed refresh must not drop the last good mapping") + assert.Equal(t, lastGood, r.cache.lastSuccess) + + fail.Store(false) + require.True(t, r.attemptRepo2CPERefresh(t.Context())) + assert.Equal(t, hash, r.cache.hash) +} + +func TestFetchRepo2CPE(t *testing.T) { + mapping := []byte(`{"data":{"foo":{"cpes":["cpe:/o:foo"]}}}`) + hash := cpemapping.HashMapping(mapping) + + t.Run("never fetched returns ok=false", func(t *testing.T) { + r := newRepo2CPE(&http.Client{}) + r.centralReachable.Store(false) + + gotMapping, gotHash, ok := r.FetchRepo2CPE(t.Context()) + + assert.False(t, ok) + assert.Nil(t, gotMapping) + assert.Empty(t, gotHash) + }) + + t.Run("offline serves the last cached mapping with ok=true", func(t *testing.T) { + r := newRepo2CPE(&http.Client{}) + r.cache = repo2CPECache{mapping: mapping, hash: hash, lastSuccess: time.Now()} + r.centralReachable.Store(false) + + gotMapping, gotHash, ok := r.FetchRepo2CPE(t.Context()) + + assert.True(t, ok) + assert.Equal(t, mapping, gotMapping) + assert.Equal(t, hash, gotHash) + }) + + t.Run("returned mapping is a copy of the cache", func(t *testing.T) { + r := newRepo2CPE(&http.Client{}) + r.cache = repo2CPECache{mapping: slices.Clone(mapping), hash: hash, lastSuccess: time.Now()} + r.centralReachable.Store(false) + + gotMapping, _, ok := r.FetchRepo2CPE(t.Context()) + require.True(t, ok) + gotMapping[0] = 'X' + assert.Equal(t, mapping, r.cache.mapping) + }) +} + +func TestRepo2CPERun(t *testing.T) { + tests := map[string]func(t *testing.T){ + "should fetch when a tick arrives and central is reachable": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + r.centralReachable.Store(true) + + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + ticks := make(chan time.Time) + go r.run(ctx, ticks) + synctest.Wait() + assert.Equal(t, int32(0), fetches.Load()) + + ticks <- time.Time{} + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + }, + "should skip the fetch when central is unreachable": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + r.centralReachable.Store(false) + + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + ticks := make(chan time.Time) + go r.run(ctx, ticks) + synctest.Wait() + + ticks <- time.Time{} + synctest.Wait() + assert.Equal(t, int32(0), fetches.Load()) + }, + } + for name, fn := range tests { + t.Run(name, func(t *testing.T) { + synctest.Test(t, fn) + }) + } +} + +func TestRepo2CPEStart(t *testing.T) { + tests := map[string]func(t *testing.T){ + "should fetch immediately on Start when central is reachable": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + t.Cleanup(r.Stop) + r.centralReachable.Store(true) + + require.NoError(t, r.Start()) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + }, + "should return an error on a second Start": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + t.Cleanup(r.Stop) + r.centralReachable.Store(true) + + require.NoError(t, r.Start()) + synctest.Wait() + assert.Equal(t, errStartMoreThanOnce, r.Start()) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + }, + "should stop without further fetches": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + t.Cleanup(r.Stop) + r.centralReachable.Store(true) + + require.NoError(t, r.Start()) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + + r.Stop() + synctest.Wait() + time.Sleep(repo2CPERefreshInterval) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + }, + "should refetch after the success interval": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + t.Cleanup(r.Stop) + r.centralReachable.Store(true) + + require.NoError(t, r.Start()) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + + time.Sleep(repo2CPERefreshInterval) + synctest.Wait() + assert.Equal(t, int32(2), fetches.Load()) + }, + "should refetch immediately when Notify kicks a running loop": func(t *testing.T) { + var fetches atomic.Int32 + r := newRepo2CPE(countingMappingClient(&fetches, testRepo2CPEMapping)) + t.Cleanup(r.Stop) + r.centralReachable.Store(true) + + require.NoError(t, r.Start()) + synctest.Wait() + assert.Equal(t, int32(1), fetches.Load()) + + r.Notify(common.SensorComponentEventCentralReachable) + synctest.Wait() + assert.Equal(t, int32(2), fetches.Load()) + }, + } + for name, fn := range tests { + t.Run(name, func(t *testing.T) { + synctest.Test(t, fn) + }) + } +} diff --git a/sensor/common/sensor/sensor.go b/sensor/common/sensor/sensor.go index 4be110bce8ce6..edc398271a91b 100644 --- a/sensor/common/sensor/sensor.go +++ b/sensor/common/sensor/sensor.go @@ -241,10 +241,10 @@ func (s *Sensor) Start() { route, err := s.newScannerDefinitionsRoute(s.centralEndpoint, centralCertificates) if err != nil { utils.Should(errors.Wrap(err, "Failed to create scanner definition route")) + } else { + customRoutes = append(customRoutes, *route) + s.AddNotifiable(scannerclient.ResetNotifiable()) } - customRoutes = append(customRoutes, *route) - - s.AddNotifiable(scannerclient.ResetNotifiable()) } // Enable proxy endpoint for forwarding requests to Central on OpenShift. From 86c8ca4e1616f1690c88d8514db6281eb72f1677 Mon Sep 17 00:00:00 2001 From: David Vail Date: Wed, 2 Sep 2026 07:30:43 -0400 Subject: [PATCH 161/638] ROX-30368: Add AI risk summary to Risk details page (#22453) --- .../AiRiskSummary/AiRiskSummaryCard.cy.jsx | 76 +++++++++++ .../Risk/AiRiskSummary/AiRiskSummaryCard.tsx | 120 ++++++++++++++++++ .../AiRiskSummary/riskSummaryCache.test.ts | 93 ++++++++++++++ .../Risk/AiRiskSummary/riskSummaryCache.ts | 46 +++++++ .../Risk/AiRiskSummary/useAiRiskSummary.ts | 52 ++++++++ .../src/Containers/Risk/RiskDetailsPage.tsx | 64 ++++++++-- ui/apps/platform/src/images/aiExperience.svg | 6 + 7 files changed, 444 insertions(+), 13 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.cy.jsx create mode 100644 ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.tsx create mode 100644 ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.test.ts create mode 100644 ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.ts create mode 100644 ui/apps/platform/src/Containers/Risk/AiRiskSummary/useAiRiskSummary.ts create mode 100644 ui/apps/platform/src/images/aiExperience.svg diff --git a/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.cy.jsx b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.cy.jsx new file mode 100644 index 0000000000000..805198e0dac47 --- /dev/null +++ b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.cy.jsx @@ -0,0 +1,76 @@ +import AiRiskSummaryCard from './AiRiskSummaryCard'; + +const summaryText = 'sync-worker scores 67/100 (Important).\nMain drivers: suspicious processes.'; + +function setup(props = {}) { + cy.mount( + {}} + onRetry={() => {}} + {...props} + /> + ); +} + +describe(Cypress.spec.relative, () => { + it('should show a spinner while the summary is loading', () => { + setup({ isLoading: true }); + + cy.get('[aria-label="Generating AI risk summary"]').should('exist'); + cy.contains('Always review AI-generated content prior to use.').should('not.exist'); + }); + + it('should render an error alert with a retry action when the request fails', () => { + const onRetry = cy.stub().as('onRetry'); + setup({ error: new Error('something went wrong'), onRetry }); + + cy.contains('Unable to generate AI risk summary').should('exist'); + cy.contains('something went wrong').should('exist'); + cy.contains('Always review AI-generated content prior to use.').should('not.exist'); + + cy.contains('button', 'Try again').click(); + cy.get('@onRetry').should('have.been.calledOnce'); + }); + + it('should render the summary with the review disclaimer on success', () => { + setup({ summary: summaryText }); + + cy.contains('AI risk briefing').should('exist'); + cy.contains('Always review AI-generated content prior to use.').should('exist'); + cy.contains('sync-worker scores 67/100 (Important).').should('exist'); + cy.get('[aria-label="Generating AI risk summary"]').should('not.exist'); + }); + + it('should copy the summary to the clipboard', () => { + setup({ summary: summaryText }); + + cy.get('button[aria-label="Copy AI summary to clipboard"]').click(); + + cy.window().then((window) => { + window.navigator.clipboard.readText().then((text) => { + expect(text).to.eq(summaryText); + }); + }); + }); + + it('should invoke onExpand when the collapse toggle is clicked', () => { + const onExpand = cy.stub().as('onExpand'); + setup({ summary: summaryText, onExpand }); + + cy.get('button[aria-label="Collapse AI risk briefing"]').click(); + cy.get('@onExpand').should('have.been.calledOnce'); + }); + + it('should hide the summary body when collapsed', () => { + setup({ summary: summaryText, isExpanded: false }); + + // The title remains, but the collapsed card removes its body from the DOM. + cy.contains('AI risk briefing').should('exist'); + cy.contains('sync-worker scores 67/100 (Important).').should('not.exist'); + cy.get('button[aria-label="Expand AI risk briefing"]').should('exist'); + }); +}); diff --git a/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.tsx b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.tsx new file mode 100644 index 0000000000000..1ccc2ebbc0097 --- /dev/null +++ b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/AiRiskSummaryCard.tsx @@ -0,0 +1,120 @@ +import type { ReactElement } from 'react'; +import { + Alert, + AlertActionLink, + Bullseye, + Button, + Card, + CardBody, + CardExpandableContent, + CardHeader, + CardTitle, + Content, + Flex, + FlexItem, + Spinner, +} from '@patternfly/react-core'; +import { CopyIcon } from '@patternfly/react-icons'; + +import useClipboardCopy from 'hooks/useClipboardCopy'; +import AiExperienceIcon from 'images/aiExperience.svg?react'; +import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; + +export type AiRiskSummaryCardProps = { + summary: string | undefined; + isLoading: boolean; + error: unknown; + isExpanded: boolean; + onExpand: () => void; + onRetry: () => void; +}; + +function AiRiskSummaryCard({ + summary, + isLoading, + error, + isExpanded, + onExpand, + onRetry, +}: AiRiskSummaryCardProps): ReactElement { + const { wasCopied, copyToClipboard } = useClipboardCopy(); + + return ( + + } + aria-label={ + wasCopied ? 'Copied AI summary' : 'Copy AI summary to clipboard' + } + isDisabled={!summary} + onClick={() => summary && copyToClipboard(summary)} + /> + ), + }} + > + + + + AI risk briefing + + + + + + {isLoading && ( + + + + )} + {!isLoading && Boolean(error) && ( + Try again + } + > + {getAxiosErrorMessage(error)} + + )} + {!isLoading && !error && ( + + + + {summary} + + + )} + + + + ); +} + +export default AiRiskSummaryCard; diff --git a/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.test.ts b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.test.ts new file mode 100644 index 0000000000000..9bf846e3f1c53 --- /dev/null +++ b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.test.ts @@ -0,0 +1,93 @@ +import type { DeploymentRiskSummary } from 'services/DeploymentsService'; + +const mockFetch = vi.hoisted(() => vi.fn()); +vi.mock('services/DeploymentsService', () => ({ + fetchDeploymentRiskSummary: mockFetch, +})); + +// The cache keeps state at module scope, so load a fresh copy for each test to +// keep them isolated (rather than adding a test-only reset to the module). +async function loadCache() { + vi.resetModules(); + return import('./riskSummaryCache'); +} + +describe('riskSummaryCache', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('fetches and returns the summary for a deployment', async () => { + mockFetch.mockResolvedValue({ summary: 'risk briefing' }); + const { fetchCachedDeploymentRiskSummary } = await loadCache(); + + await expect(fetchCachedDeploymentRiskSummary('dep-1')).resolves.toEqual({ + summary: 'risk briefing', + }); + expect(mockFetch).toHaveBeenCalledExactlyOnceWith('dep-1'); + }); + + it('caches the result and does not fetch again on a subsequent call', async () => { + mockFetch.mockResolvedValue({ summary: 'cached' }); + const { fetchCachedDeploymentRiskSummary } = await loadCache(); + + await fetchCachedDeploymentRiskSummary('dep-1'); + await fetchCachedDeploymentRiskSummary('dep-1'); + + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it('dedupes concurrent in-flight requests for the same deployment', async () => { + let resolveFetch: (value: DeploymentRiskSummary) => void = () => {}; + mockFetch.mockImplementation( + () => + new Promise((resolve) => { + resolveFetch = resolve; + }) + ); + const { fetchCachedDeploymentRiskSummary } = await loadCache(); + + const first = fetchCachedDeploymentRiskSummary('dep-1'); + const second = fetchCachedDeploymentRiskSummary('dep-1'); + expect(mockFetch).toHaveBeenCalledTimes(1); + + resolveFetch({ summary: 'shared' }); + await expect(first).resolves.toEqual({ summary: 'shared' }); + await expect(second).resolves.toEqual({ summary: 'shared' }); + }); + + it('caches per deployment id', async () => { + mockFetch.mockImplementation((id: string) => Promise.resolve({ summary: `summary ${id}` })); + const { fetchCachedDeploymentRiskSummary, peekCachedDeploymentRiskSummary } = + await loadCache(); + + await fetchCachedDeploymentRiskSummary('dep-1'); + await fetchCachedDeploymentRiskSummary('dep-2'); + + expect(mockFetch).toHaveBeenCalledTimes(2); + expect(peekCachedDeploymentRiskSummary('dep-1')).toEqual({ summary: 'summary dep-1' }); + expect(peekCachedDeploymentRiskSummary('dep-2')).toEqual({ summary: 'summary dep-2' }); + }); + + it('does not cache failures and retries on the next attempt', async () => { + mockFetch + .mockRejectedValueOnce(new Error('boom')) + .mockResolvedValueOnce({ summary: 'recovered' }); + const { fetchCachedDeploymentRiskSummary, peekCachedDeploymentRiskSummary } = + await loadCache(); + + await expect(fetchCachedDeploymentRiskSummary('dep-1')).rejects.toThrow('boom'); + expect(peekCachedDeploymentRiskSummary('dep-1')).toBeUndefined(); + + await expect(fetchCachedDeploymentRiskSummary('dep-1')).resolves.toEqual({ + summary: 'recovered', + }); + expect(mockFetch).toHaveBeenCalledTimes(2); + expect(peekCachedDeploymentRiskSummary('dep-1')).toEqual({ summary: 'recovered' }); + }); + + it('peek returns undefined before a summary has been fetched', async () => { + const { peekCachedDeploymentRiskSummary } = await loadCache(); + expect(peekCachedDeploymentRiskSummary('dep-1')).toBeUndefined(); + }); +}); diff --git a/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.ts b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.ts new file mode 100644 index 0000000000000..a056626a85c5d --- /dev/null +++ b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/riskSummaryCache.ts @@ -0,0 +1,46 @@ +import { fetchDeploymentRiskSummary } from 'services/DeploymentsService'; +import type { DeploymentRiskSummary } from 'services/DeploymentsService'; + +// Session-scoped cache for AI risk summaries, keyed by deployment id. Generating a +// summary is expensive and the result should be relatively stable for a deployment within a browser +// session, so resolved summaries are cached and in-flight requests are deduped at +// module scope. The cache therefore persists across page navigations (until a full +// reload) - the behavior a query library would provide, +// without the dependency. Failures are not cached, so a later attempt retries. + +const resolved = new Map(); +const inFlight = new Map>(); + +/** Fetches the summary, returning the cached value or an in-flight request when available. */ +export function fetchCachedDeploymentRiskSummary( + deploymentId: string +): Promise { + const cached = resolved.get(deploymentId); + if (cached) { + return Promise.resolve(cached); + } + const pending = inFlight.get(deploymentId); + if (pending) { + return pending; + } + const request = fetchDeploymentRiskSummary(deploymentId) + .then((summary) => { + resolved.set(deploymentId, summary); + return summary; + }) + .finally(() => { + inFlight.delete(deploymentId); + }); + inFlight.set(deploymentId, request); + return request; +} + +/** + * Synchronous read of an already-cached summary, if any. Lets a freshly mounted + * component render a previously fetched summary immediately without re-investigating. + */ +export function peekCachedDeploymentRiskSummary( + deploymentId: string +): DeploymentRiskSummary | undefined { + return resolved.get(deploymentId); +} diff --git a/ui/apps/platform/src/Containers/Risk/AiRiskSummary/useAiRiskSummary.ts b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/useAiRiskSummary.ts new file mode 100644 index 0000000000000..ecb4bf8d588d0 --- /dev/null +++ b/ui/apps/platform/src/Containers/Risk/AiRiskSummary/useAiRiskSummary.ts @@ -0,0 +1,52 @@ +import { useState } from 'react'; + +import useRestMutation from 'hooks/useRestMutation'; +import type { DeploymentRiskSummary } from 'services/DeploymentsService'; + +import { + fetchCachedDeploymentRiskSummary, + peekCachedDeploymentRiskSummary, +} from './riskSummaryCache'; + +export type UseAiRiskSummaryReturn = { + isPresent: boolean; + isExpanded: boolean; + summary: DeploymentRiskSummary | undefined; + isLoading: boolean; + error: unknown; + investigate: () => void; + setExpanded: (isExpanded: boolean) => void; +}; + +export default function useAiRiskSummary(deploymentId: string): UseAiRiskSummaryReturn { + const [isExpanded, setIsExpanded] = useState(false); + const { isLoading, error, mutate } = useRestMutation(fetchCachedDeploymentRiskSummary); + + // Read the summary from the session cache if it exists + const summary = peekCachedDeploymentRiskSummary(deploymentId); + + // The card is present (collapsed or expanded) whenever there is a cached result to + // surface, a request in flight, an error to report, or the user has expanded it this + // session. A cached result on a fresh mount renders the card collapsed, so navigating + // back to a deployment shows that recent results exist without re-spending tokens. + const isPresent = Boolean(summary) || isLoading || Boolean(error) || isExpanded; + + function investigate() { + setIsExpanded(true); + // Already cached this session, or a request is in flight - nothing to fetch. + if (summary || isLoading) { + return; + } + mutate(deploymentId); + } + + return { + isPresent, + isExpanded, + summary, + isLoading, + error, + investigate, + setExpanded: setIsExpanded, + }; +} diff --git a/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx b/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx index 232e4bc577baf..48582eb14236f 100644 --- a/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx @@ -25,10 +25,14 @@ import { riskPlatformViewPath, riskUserWorkloadsViewPath, } from 'routePaths'; +import useFeatureFlags from 'hooks/useFeatureFlags'; import useIsRouteEnabled from 'hooks/useIsRouteEnabled'; +import AiExperienceIcon from 'images/aiExperience.svg?react'; import RiskDetailTabs from './RiskDetailTabs'; import useDeploymentWithRisk from './useDeploymentWithRisk'; +import AiRiskSummaryCard from './AiRiskSummary/AiRiskSummaryCard'; +import useAiRiskSummary from './AiRiskSummary/useAiRiskSummary'; function getRiskBreadcrumb(filteredWorkflowView: FilteredWorkflowView) { // Note: We cannot exhaustively check for all possible values of filteredWorkflowView because @@ -52,6 +56,10 @@ function RiskDetailsPage(): ReactElement { const { filteredWorkflowView } = useFilteredWorkflowViewURLState(); + const { isFeatureFlagEnabled } = useFeatureFlags(); + const isRiskSummaryEnabled = isFeatureFlagEnabled('ROX_LIGHTSPEED_RISK_SUMMARY'); + const aiRiskSummary = useAiRiskSummary(deploymentId); + const isRouteEnabled = useIsRouteEnabled(); const isRouteEnabledForNetworkGraph = isRouteEnabled('network-graph'); @@ -76,22 +84,52 @@ function RiskDetailsPage(): ReactElement { )} - {isRouteEnabledForNetworkGraph && data && ( - - )} + + {isRouteEnabledForNetworkGraph && data && ( + + + + )} + {isRiskSummaryEnabled && data && !aiRiskSummary.isPresent && ( + + + + )} + + {isRiskSummaryEnabled && aiRiskSummary.isPresent && data && ( + + aiRiskSummary.setExpanded(!aiRiskSummary.isExpanded)} + onRetry={aiRiskSummary.investigate} + /> + + )} {error && ( + From 08607dddf95c20b74ea44bae3a9f4c6c290f90a1 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Wed, 2 Sep 2026 14:13:07 +0200 Subject: [PATCH 162/638] ROX-36432: add node scan_time diagnostic logging (#22504) Co-authored-by: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 --- .../datastore/datastore_impl_postgres_test.go | 97 +++++++++++++++++ .../node/datastore/store/postgres/store.go | 11 ++ .../datastore/store/postgres/store_test.go | 81 ++++++++++++++ .../service/pipeline/nodeindex/pipeline.go | 30 ++++++ .../pipeline/nodeindex/pipeline_test.go | 102 ++++++++++++++++++ pkg/nodes/enricher/enricher_impl.go | 17 +++ pkg/nodes/enricher/enricher_impl_test.go | 78 +++++++++++++- 7 files changed, 415 insertions(+), 1 deletion(-) diff --git a/central/node/datastore/datastore_impl_postgres_test.go b/central/node/datastore/datastore_impl_postgres_test.go index 37c4fed5b63dc..34c364b82a20d 100644 --- a/central/node/datastore/datastore_impl_postgres_test.go +++ b/central/node/datastore/datastore_impl_postgres_test.go @@ -4,8 +4,11 @@ package datastore import ( "context" + "fmt" "sort" + "sync/atomic" "testing" + "time" nodeCVEDS "github.com/stackrox/rox/central/cve/node/datastore" nodeCVEPostgres "github.com/stackrox/rox/central/cve/node/datastore/store/postgres" @@ -31,6 +34,7 @@ import ( pkgSearch "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/search/scoped" "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/sync" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" ) @@ -78,6 +82,99 @@ func (suite *NodePostgresDataStoreTestSuite) TearDownTest() { suite.db.Close() } +// TestConcurrentUpsertNode_KeyedMutexPreventsRegression is the datastore-layer counterpart of +// store_test.go's TestStore_ConcurrentUpsert_BypassingOuterLock. That test proves the store layer alone has +// a check-then-act race in isUpdated(): the read-old-scan-and-decide step isn't covered by the store's own +// keyFence lock. This test drives the same workload through UpsertNode, the production call path, which +// wraps the entire read-decide-write sequence in a per-node-ID keyedMutex (see UpsertNode in +// datastore_impl.go). Every caller in this codebase reaches the store exclusively through UpsertNode, so +// this path determines whether concurrent upserts can regress scan_time; the test asserts scan_time never +// regresses even under heavy concurrency. +func (suite *NodePostgresDataStoreTestSuite) TestConcurrentUpsertNode_KeyedMutexPreventsRegression() { + allowAllCtx := sac.WithAllAccess(context.Background()) + + node := getTestNodeForPostgres(fixtureconsts.Node1, "concurrent-node") + baseTime := time.Now().Add(-24 * time.Hour) + node.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&baseTime) + suite.NoError(suite.datastore.UpsertNode(allowAllCtx, node)) + + const iterations = 500 + const freshWriters = 4 + const metaWriters = 4 + var wg sync.WaitGroup + // A writer that fails leaves the intended workload incomplete, so a zero regression count would be + // meaningless. Track upsert failures and assert none happened. + var upsertFailures atomic.Int64 + + // Simulates the nodeindex pipeline: always a fresh, monotonically increasing ScanTime. + for w := range freshWriters { + wg.Go(func() { + for i := range iterations { + fresh := node.CloneVT() + t := baseTime.Add(time.Duration(w*iterations+i+1) * time.Millisecond) + fresh.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&t) + if err := suite.datastore.UpsertNode(allowAllCtx, fresh); err != nil { + upsertFailures.Add(1) + } + } + }) + } + + // Simulates the nodes pipeline: frequent metadata-only churn, Scan explicitly nil. + for w := range metaWriters { + wg.Go(func() { + for i := range iterations { + metaOnly := node.CloneVT() + metaOnly.Scan = nil + metaOnly.Labels = map[string]string{"writer": fmt.Sprintf("%d-%d", w, i)} + if err := suite.datastore.UpsertNode(allowAllCtx, metaOnly); err != nil { + upsertFailures.Add(1) + } + } + }) + } + + var regressions atomic.Int64 + stop := make(chan struct{}) + var pollWG sync.WaitGroup + for range 2 { + pollWG.Go(func() { + lastSeen := baseTime + for { + select { + case <-stop: + return + default: + } + current, exists, err := suite.datastore.GetNode(allowAllCtx, node.GetId()) + if err == nil && exists { + st := current.GetScan().GetScanTime().AsTime() + if st.Before(lastSeen) { + regressions.Add(1) + } else { + lastSeen = st + } + } + // Yield between reads so the pollers don't hammer the DB in a tight busy-loop. + time.Sleep(time.Millisecond) + } + }) + } + + wg.Wait() + close(stop) + pollWG.Wait() + + suite.Zero(upsertFailures.Load(), + "every concurrent UpsertNode must succeed; a failure means the intended workload did not complete, "+ + "which would make the regression assertion below meaningless") + suite.Zero(regressions.Load(), + "scan_time must never regress through UpsertNode, since every real caller (nodeindex and nodes "+ + "pipelines alike) goes through the per-node-ID keyedMutex that wraps the entire read-decide-write "+ + "sequence; a non-zero count here would mean the keyedMutex itself has a bug, not just the store "+ + "layer's already-known gap") +} + func (suite *NodePostgresDataStoreTestSuite) TestBasicOps() { node := getTestNodeForPostgres(fixtureconsts.Node1, "name1") allowAllCtx := sac.WithAllAccess(context.Background()) diff --git a/central/node/datastore/store/postgres/store.go b/central/node/datastore/store/postgres/store.go index 1ecfdf88f7131..9f8a4ddbf51f3 100644 --- a/central/node/datastore/store/postgres/store.go +++ b/central/node/datastore/store/postgres/store.go @@ -529,6 +529,17 @@ func (s *storeImpl) isUpdated(ctx context.Context, node *storage.Node) (bool, er // We skip rewriting components and vulnerabilities if the node scan is older. scanUpdated := protocompat.CompareTimestamps(oldNode.GetScan().GetScanTime(), node.GetScan().GetScanTime()) <= 0 if !scanUpdated { + // Metadata-only upserts (e.g. from the informer-driven nodes pipeline) intentionally send no scan at + // all, relying on this method to preserve the stored one; only warn when a real, incoming scan with + // its own scan_time was rejected as not newer than what's stored. + if node.GetScan().GetScanTime() != nil { + log.Warnf("Rejecting incoming node scan for node %s (%s) in cluster %s (%s) as not newer than the "+ + "stored one: stored scan_time=%s, incoming scan_time=%s. The incoming scan and its "+ + "component/CVE data will be discarded and the previously stored scan will be kept.", + node.GetId(), node.GetName(), node.GetClusterName(), node.GetClusterId(), + protocompat.ConvertTimestampToString(oldNode.GetScan().GetScanTime(), time.RFC3339Nano), + protocompat.ConvertTimestampToString(node.GetScan().GetScanTime(), time.RFC3339Nano)) + } node.Scan = oldNode.GetScan() node.RiskScore = oldNode.GetRiskScore() node.SetComponents = oldNode.GetSetComponents() diff --git a/central/node/datastore/store/postgres/store_test.go b/central/node/datastore/store/postgres/store_test.go index de012c53d363e..07ff3e3018783 100644 --- a/central/node/datastore/store/postgres/store_test.go +++ b/central/node/datastore/store/postgres/store_test.go @@ -12,6 +12,7 @@ import ( "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/postgres" "github.com/stackrox/rox/pkg/postgres/pgtest" "github.com/stackrox/rox/pkg/protoassert" @@ -21,9 +22,22 @@ import ( "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/testutils" "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" ) +const rejectionWarnSnippet = "Rejecting incoming node scan for node" + +func swapObservedLogger(t interface{ Cleanup(func()) }) *observer.ObservedLogs { + core, logs := observer.New(zap.WarnLevel) + orig := log + log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} + t.Cleanup(func() { log = orig }) + return logs +} + type NodesStoreSuite struct { suite.Suite ctx context.Context @@ -151,6 +165,73 @@ func (s *NodesStoreSuite) TestStore_UpsertWithoutScan() { protoassert.Equal(s.T(), foundNode, newNode) } +// TestStore_FreshScanAlwaysWinsSequential covers the sequential happy path of the isUpdated() freshness +// guard: upsert an old scan, then upsert a strictly newer one for the same node ID, and confirm the newer +// one is persisted (both scan_time and payload advance). This isolates single-threaded correctness of the +// guard from the concurrency behavior exercised elsewhere. +func (s *NodesStoreSuite) TestStore_FreshScanAlwaysWinsSequential() { + store := New(s.pool, false, concurrency.NewKeyFence()) + + node := &storage.Node{} + s.NoError(testutils.FullInit(node, testutils.UniqueInitializer(), testutils.JSONFieldsFilter)) + for _, comp := range node.GetScan().GetComponents() { + comp.Vulns = nil + } + + oldTime := time.Now().Add(-60 * 24 * time.Hour) + node.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&oldTime) + s.NoError(store.Upsert(s.ctx, node)) + + fresh := node.CloneVT() + freshTime := time.Now() + fresh.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&freshTime) + fresh.Scan.Components = fresh.GetScan().GetComponents()[:1] // Change the payload shape too, not just the timestamp. + s.NoError(store.Upsert(s.ctx, fresh)) + + found, exists, err := store.Get(s.ctx, node.GetId()) + s.NoError(err) + s.True(exists) + s.Equal(freshTime.Unix(), found.GetScan().GetScanTime().AsTime().Unix(), + "a strictly newer scan must always replace an older one when applied sequentially") + s.Len(found.GetScan().GetComponents(), 1) +} + +// TestStore_RejectionWarningFiresOnlyForRealStaleScan validates the rejection warning in isUpdated(): +// a real, non-nil incoming scan that is older than the stored one is rejected AND warned about, while a +// metadata-only upsert (Scan == nil) - which is also "not newer" - is rejected silently (the gate). +func (s *NodesStoreSuite) TestStore_RejectionWarningFiresOnlyForRealStaleScan() { + store := New(s.pool, false, concurrency.NewKeyFence()) + logs := swapObservedLogger(s.T()) + + node := &storage.Node{} + s.NoError(testutils.FullInit(node, testutils.UniqueInitializer(), testutils.JSONFieldsFilter)) + for _, comp := range node.GetScan().GetComponents() { + comp.Vulns = nil + } + newer := time.Now() + node.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&newer) + s.NoError(store.Upsert(s.ctx, node)) + + // A real, non-nil, older scan must be rejected and warned about. + stale := node.CloneVT() + older := newer.Add(-time.Hour) + stale.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&older) + s.NoError(store.Upsert(s.ctx, stale)) + + found, exists, err := store.Get(s.ctx, node.GetId()) + s.NoError(err) + s.True(exists) + s.Equal(newer.Unix(), found.GetScan().GetScanTime().AsTime().Unix(), "stale scan must be rejected") + require.Len(s.T(), logs.FilterMessageSnippet(rejectionWarnSnippet).All(), 1) + + // A metadata-only upsert (Scan == nil) is also "not newer" but must NOT emit the warning. + metaOnly := found.CloneVT() + metaOnly.Scan = nil + s.NoError(store.Upsert(s.ctx, metaOnly)) + require.Len(s.T(), logs.FilterMessageSnippet(rejectionWarnSnippet).All(), 1, + "metadata-only upserts must not emit the rejection warning") +} + func (s *NodesStoreSuite) TestStore_OrphanedCVEs() { s.T().Setenv(env.OrphanedCVEsKeepAlive.EnvVar(), "true") if !env.OrphanedCVEsKeepAlive.BooleanSetting() { diff --git a/central/sensor/service/pipeline/nodeindex/pipeline.go b/central/sensor/service/pipeline/nodeindex/pipeline.go index 03c1b2b6c686c..18eeea9cf16ab 100644 --- a/central/sensor/service/pipeline/nodeindex/pipeline.go +++ b/central/sensor/service/pipeline/nodeindex/pipeline.go @@ -2,6 +2,7 @@ package nodeindex import ( "context" + "time" "github.com/pkg/errors" clusterDataStore "github.com/stackrox/rox/central/cluster/datastore" @@ -19,6 +20,7 @@ import ( "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/metrics" "github.com/stackrox/rox/pkg/nodes/enricher" + "github.com/stackrox/rox/pkg/protocompat" ) var ( @@ -115,6 +117,7 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe } log.Infof("Scanned index report and found %d components for node %s", len(node.GetScan().GetComponents()), nodeDatastore.NodeString(node)) + incomingScanTime := node.GetScan().GetScanTime() // Update the whole node in the database with the new and previous information. err = p.riskManager.CalculateRiskAndUpsertNode(node) @@ -122,6 +125,33 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe return errors.Wrapf(err, "failed calculating risk and upserting node %s", nodeDatastore.NodeString(node)) } + // The store may have rejected this scan as not newer than what's already persisted, in which case it + // rewrites node.Scan in place to the previously stored one before returning - see isUpdated() in + // central/node/datastore/store/postgres/store.go. Comparing scan times here, on the same object we just + // tried to upsert, tells us definitively whether the fresh scan we just processed actually landed. + persistedScanTime := node.GetScan().GetScanTime() + switch cmp := protocompat.CompareTimestamps(incomingScanTime, persistedScanTime); { + case cmp > 0: + // The store kept a scan_time older than the one we just processed. isUpdated() only rejects scans + // that are not strictly newer than the stored one, so a fresh scan should have been accepted here; + // a persisted scan older than the processed one means the fresh scan was silently dropped, which the + // freshness guard should never do. + log.Warnf("Persisted scan_time for node %s is older than the freshly processed scan_time: "+ + "processed=%s persisted=%s. The store kept an older scan than the one just processed, which the "+ + "freshness guard should have accepted - possible scan regression.", + nodeDatastore.NodeString(node), + protocompat.ConvertTimestampToString(incomingScanTime, time.RFC3339Nano), + protocompat.ConvertTimestampToString(persistedScanTime, time.RFC3339Nano)) + case cmp < 0: + // The store kept a scan_time newer than the one we just processed - the expected shape of an + // isUpdated() rejection, where node.Scan was rewritten back to the newer stored scan. + log.Warnf("Persisted scan_time for node %s does not match the freshly processed scan_time: "+ + "processed=%s persisted=%s. The store rejected this scan as not newer than the stored one.", + nodeDatastore.NodeString(node), + protocompat.ConvertTimestampToString(incomingScanTime, time.RFC3339Nano), + protocompat.ConvertTimestampToString(persistedScanTime, time.RFC3339Nano)) + } + sendComplianceAck(ctx, node, injector) return nil } diff --git a/central/sensor/service/pipeline/nodeindex/pipeline_test.go b/central/sensor/service/pipeline/nodeindex/pipeline_test.go index 5ea4a302e2ec8..539199eadcb6f 100644 --- a/central/sensor/service/pipeline/nodeindex/pipeline_test.go +++ b/central/sensor/service/pipeline/nodeindex/pipeline_test.go @@ -2,6 +2,7 @@ package nodeindex import ( "testing" + "time" clusterDatastoreMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" nodeDatastoreMocks "github.com/stackrox/rox/central/node/datastore/mocks" @@ -12,13 +13,114 @@ import ( "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/logging" nodesEnricherMocks "github.com/stackrox/rox/pkg/nodes/enricher/mocks" "github.com/stackrox/rox/pkg/protoassert" + "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/sync" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" ) +const ( + scanRejectedWarnSnippet = "rejected this scan as not newer than the stored one" + scanRegressionWarnSnippet = "possible scan regression" +) + +func swapObservedLogger(t *testing.T) *observer.ObservedLogs { + core, logs := observer.New(zap.WarnLevel) + orig := log + log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} + t.Cleanup(func() { log = orig }) + return logs +} + +// TestPipelineWarnsWhenPersistedScanTimeRegresses validates the post-upsert diagnostic: after the upsert, +// the pipeline compares the scan_time it processed against what the store actually kept (the store rewrites +// node.Scan in place when it rejects a scan as not-newer) and warns on a mismatch. +func TestPipelineWarnsWhenPersistedScanTimeRegresses(t *testing.T) { + t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") + t.Setenv(features.ScannerV4.EnvVar(), "true") + + processed := time.Now() + + cases := map[string]struct { + // persistedScanTime is what the store leaves on node.Scan after the upsert returns. + persistedScanTime time.Time + // expectSnippet is the warning we expect to see; empty means no warning at all. + expectSnippet string + }{ + // A genuine isUpdated() rejection keeps a scan strictly newer than the one we just processed. + "warns (rejection) when the store kept a newer scan": { + persistedScanTime: processed.Add(time.Hour), + expectSnippet: scanRejectedWarnSnippet, + }, + // The store kept an older scan than the fresh one - a scan the freshness guard should have accepted. + "warns (regression) when the store kept an older scan": { + persistedScanTime: processed.Add(-time.Hour), + expectSnippet: scanRegressionWarnSnippet, + }, + "silent when the fresh scan was accepted": { + persistedScanTime: processed, + expectSnippet: "", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + logs := swapObservedLogger(t) + node := &storage.Node{Id: "1", Name: "node-name", ClusterId: "cluster-id"} + + ctrl := gomock.NewController(t) + nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) + riskManager := riskManagerMocks.NewMockManager(ctrl) + enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) + + gomock.InOrder( + nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(node.GetId())).Times(1).Return(node, true, nil), + // Enrichment stamps a fresh scan_time, as the real enricher does with TimestampNow(). + enricher.EXPECT().EnrichNodeWithVulnerabilities(gomock.Any(), nil, gomock.Any()).Times(1). + DoAndReturn(func(n *storage.Node, _ *storage.NodeInventory, _ *v4.IndexReport) error { + n.Scan = &storage.NodeScan{ScanTime: protocompat.ConvertTimeToTimestampOrNil(&processed)} + return nil + }), + // Upsert rewrites node.Scan in place to whatever was persisted (mimics isUpdated()). + riskManager.EXPECT().CalculateRiskAndUpsertNode(gomock.Any()).Times(1). + DoAndReturn(func(n *storage.Node) error { + n.Scan.ScanTime = protocompat.ConvertTimeToTimestampOrNil(&tc.persistedScanTime) + return nil + }), + ) + + p := &pipelineImpl{ + nodeDatastore: nodeDatastore, + riskManager: riskManager, + enricher: enricher, + } + + err := p.Run(t.Context(), node.GetClusterId(), createMsg(mockIndexReport), nil) + require.NoError(t, err) + + rejections := logs.FilterMessageSnippet(scanRejectedWarnSnippet).All() + regressions := logs.FilterMessageSnippet(scanRegressionWarnSnippet).All() + switch tc.expectSnippet { + case scanRejectedWarnSnippet: + assert.Len(t, rejections, 1) + assert.Empty(t, regressions) + case scanRegressionWarnSnippet: + assert.Len(t, regressions, 1) + assert.Empty(t, rejections) + default: + assert.Empty(t, rejections) + assert.Empty(t, regressions) + } + }) + } +} + func TestPipelineWithEmptyIndex(t *testing.T) { t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") t.Setenv(features.ScannerV4.EnvVar(), "true") diff --git a/pkg/nodes/enricher/enricher_impl.go b/pkg/nodes/enricher/enricher_impl.go index 18016b2c10f36..45a7780a74418 100644 --- a/pkg/nodes/enricher/enricher_impl.go +++ b/pkg/nodes/enricher/enricher_impl.go @@ -100,11 +100,13 @@ func (e *enricherImpl) enrichWithScan(node *storage.Node, nodeInventory *storage return errorList.ToError() } + skippedScannerTypes := make([]string, 0, len(scanners)) for _, scanner := range scanners { // Prevent unnecessary scanner calls - v4 only works on indexReports, v2/Clairify only on nodeInventory. // If both, indexReport and nodeInventory, are unset, we do not skip to keep the legacy NodeScan v1 working. if (scanner.GetNodeScanner().Type() == types.ScannerV4 && indexReport == nil && nodeInventory != nil) || (scanner.GetNodeScanner().Type() == types.Clairify && nodeInventory == nil && indexReport != nil) { + skippedScannerTypes = append(skippedScannerTypes, scanner.GetNodeScanner().Type()) continue } log.Debugf("Enriching Node with Scanner %s / %s", scanner.GetNodeScanner().Type(), scanner.GetNodeScanner().Name()) @@ -115,6 +117,21 @@ func (e *enricherImpl) enrichWithScan(node *storage.Node, nodeInventory *storage return nil } + // Only warn for the v4/nodeindex path (indexReport != nil). A v4-only Central that still receives + // legacy v2 NodeInventory reports would otherwise log this on every such report indefinitely - benign + // noise, since v2-on-a-v4-only-Central is an expected transitional state. When an IndexReport arrives and + // every registered scanner was still skipped, ScannerV4 is not registered and the node scan silently + // never refreshes - the condition worth surfacing. + if indexReport != nil && errorList.Empty() && len(skippedScannerTypes) == len(scanners) { + // node.Scan is left completely untouched by this call (this is NOT surfaced as an error - ToError() + // returns nil below), so the caller will upsert whatever scan the node already had. That upsert looks + // identical to a successful no-op scan refresh in logs/metrics downstream. + log.Warnf("No applicable node scanner ran for node %s:%s (inventory=%t, indexReport=%t); registered "+ + "scanner types were all skipped as inapplicable: %v. The node's existing scan, if any, was not "+ + "refreshed by this call.", + node.GetClusterName(), node.GetName(), nodeInventory != nil, indexReport != nil, skippedScannerTypes) + } + return errorList.ToError() } diff --git a/pkg/nodes/enricher/enricher_impl_test.go b/pkg/nodes/enricher/enricher_impl_test.go index eadf5d83e449f..ecbbf9077b4fd 100644 --- a/pkg/nodes/enricher/enricher_impl_test.go +++ b/pkg/nodes/enricher/enricher_impl_test.go @@ -6,14 +6,29 @@ import ( v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/fixtures/fixtureconsts" + "github.com/stackrox/rox/pkg/logging" pkgMetrics "github.com/stackrox/rox/pkg/metrics" "github.com/stackrox/rox/pkg/scanners/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" "golang.org/x/sync/semaphore" ) +const allSkippedWarnSnippet = "No applicable node scanner ran" + +// swapObservedLogger replaces the package-level logger with an observer-backed one at Warn level and +// returns the observed logs plus a restore func. Lets us assert exactly which diagnostic warnings fire. +func swapObservedLogger(t *testing.T) *observer.ObservedLogs { + core, logs := observer.New(zap.WarnLevel) + orig := log + log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} + t.Cleanup(func() { log = orig }) + return logs +} + var _ types.NodeScannerWithDataSource = (*fakeNodeScannerWithDataSource)(nil) type fakeNodeScannerWithDataSource struct { @@ -22,12 +37,14 @@ type fakeNodeScannerWithDataSource struct { type opts struct { requestedScan bool + scannerType string } func newFakeNodeScannerWithDataSource(opts opts) types.NodeScannerWithDataSource { return &fakeNodeScannerWithDataSource{ nodeScanner: &fakeNodeScanner{ requestedScan: opts.requestedScan, + scannerType: opts.scannerType, }, } } @@ -44,6 +61,7 @@ var _ types.NodeScanner = (*fakeNodeScanner)(nil) type fakeNodeScanner struct { requestedScan bool + scannerType string } func (*fakeNodeScanner) MaxConcurrentNodeScanSemaphore() *semaphore.Weighted { @@ -96,7 +114,10 @@ func (*fakeNodeScanner) TestNodeScanner() error { return nil } -func (*fakeNodeScanner) Type() string { +func (f *fakeNodeScanner) Type() string { + if f.scannerType != "" { + return f.scannerType + } return "type" } @@ -261,6 +282,61 @@ func TestZeroIntegrations(t *testing.T) { assert.Equal(t, expectedErrMsg, err.Error()) } +// TestEnrichWithScan_AllScannersSkippedWarning validates the ROX-36432 diagnostic: when an IndexReport +// arrives but every registered scanner is inapplicable (no ScannerV4 registered), we warn - and, per +// review, only for the v4/nodeindex path (indexReport != nil), not for benign v2-on-a-v4-only-Central. +func TestEnrichWithScan_AllScannersSkippedWarning(t *testing.T) { + node := &storage.Node{Id: fixtureconsts.Node1, ClusterName: "cluster", Name: "node"} + + newEnricher := func(scanner types.NodeScannerWithDataSource) *enricherImpl { + return &enricherImpl{ + cves: &fakeCVESuppressor{}, + scanners: map[string]types.NodeScannerWithDataSource{ + scanner.GetNodeScanner().Type(): scanner, + }, + metrics: newMetrics(pkgMetrics.CentralSubsystem), + } + } + + t.Run("warns when only v2 registered and an IndexReport arrives", func(t *testing.T) { + logs := swapObservedLogger(t) + clairify := newFakeNodeScannerWithDataSource(opts{scannerType: types.Clairify}) + e := newEnricher(clairify) + testNode := node.CloneVT() + + err := e.enrichWithScan(testNode, nil, &v4.IndexReport{}) + require.NoError(t, err) + assert.Nil(t, testNode.GetScan(), "node scan must be left untouched when every scanner is skipped") + assert.Len(t, logs.FilterMessageSnippet(allSkippedWarnSnippet).All(), 1) + }) + + t.Run("does not warn on the v2 path (indexReport == nil)", func(t *testing.T) { + logs := swapObservedLogger(t) + // Only ScannerV4 registered; a v2 NodeInventory arrives - v4 is skipped, all skipped, but the gate + // (indexReport != nil) must suppress the warning. + v4Scanner := newFakeNodeScannerWithDataSource(opts{scannerType: types.ScannerV4}) + e := newEnricher(v4Scanner) + testNode := node.CloneVT() + + err := e.enrichWithScan(testNode, &storage.NodeInventory{}, nil) + require.NoError(t, err) + assert.Empty(t, logs.FilterMessageSnippet(allSkippedWarnSnippet).All(), + "must not warn for the routine v2-on-a-v4-only-Central case") + }) + + t.Run("does not warn when a scanner actually runs", func(t *testing.T) { + logs := swapObservedLogger(t) + v4Scanner := newFakeNodeScannerWithDataSource(opts{scannerType: types.ScannerV4}) + e := newEnricher(v4Scanner) + testNode := node.CloneVT() + + err := e.enrichWithScan(testNode, nil, &v4.IndexReport{}) + require.NoError(t, err) + assert.NotNil(t, testNode.GetScan(), "scanner should have populated the scan") + assert.Empty(t, logs.FilterMessageSnippet(allSkippedWarnSnippet).All()) + }) +} + func TestFillScanStatsWithPostgres(t *testing.T) { cases := []struct { node *storage.Node From 6409043d6974834e9bccb4274383090f2f3a5b2e Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Wed, 2 Sep 2026 13:35:08 +0000 Subject: [PATCH 163/638] ROX-36389: ensure scan stats are recomputed on rescan (#22422) --- pkg/images/enricher/enricher_v2_impl_test.go | 45 ++++++++++++++++++++ pkg/images/utils/test/utils_test.go | 32 ++++++++++++++ pkg/images/utils/utils.go | 3 -- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/pkg/images/enricher/enricher_v2_impl_test.go b/pkg/images/enricher/enricher_v2_impl_test.go index e1655968a992f..99c7ff194864b 100644 --- a/pkg/images/enricher/enricher_v2_impl_test.go +++ b/pkg/images/enricher/enricher_v2_impl_test.go @@ -508,6 +508,51 @@ func TestCVESuppressionV2(t *testing.T) { assert.Equal(t, storage.VulnerabilityState_DEFERRED, img.GetScan().GetComponents()[0].GetVulns()[0].GetState()) } +// TestEnrichImageV2_ScanStatsRecomputedOnNewScan reproduces the exact object flow used +// by the image reprocessor (central/reprocessor/reprocessor.go): an ImageV2 fetched +// from the datastore (so ScanStats is already populated from its previous scan) is +// passed into EnrichImage, which finds a genuinely new scan. ScanStats must be +// recomputed to match the new scan (ROX-36389). +func TestEnrichImageV2_ScanStatsRecomputedOnNewScan(t *testing.T) { + testutils.MustUpdateFeature(t, features.FlattenImageData, true) + ctrl := gomock.NewController(t) + + fsr := newFakeRegistryScanner(opts{}) + registrySet := registryMocks.NewMockSet(ctrl) + registrySet.EXPECT().IsEmpty().Return(false).AnyTimes() + registrySet.EXPECT().GetAllUnique().Return([]types.ImageRegistry{fsr}).AnyTimes() + + scannerSet := scannerMocks.NewMockSet(ctrl) + scannerSet.EXPECT().IsEmpty().Return(false).AnyTimes() + scannerSet.EXPECT().GetAll().Return([]scannertypes.ImageScannerWithDataSource{fsr}).AnyTimes() + + set := mocks.NewMockSet(ctrl) + set.EXPECT().RegistrySet().Return(registrySet).AnyTimes() + set.EXPECT().ScannerSet().Return(scannerSet).AnyTimes() + + mockReporter := reporterMocks.NewMockReporter(ctrl) + mockReporter.EXPECT().UpdateIntegrationHealthAsync(gomock.Any()).AnyTimes() + + enricherImpl := newEnricherV2(set, mockReporter) + + img := &storage.ImageV2{ + Id: utils.NewImageV2ID(&storage.ImageName{Registry: "reg", FullName: "reg"}, "sha"), + Digest: "sha", + Name: &storage.ImageName{Registry: "reg", FullName: "reg"}, + ScanStats: &storage.ImageV2_ScanStats{CveCount: 99}, + } + + results, err := enricherImpl.EnrichImage(emptyCtx, EnrichmentContext{}, img) + require.NoError(t, err) + assert.True(t, results.ImageUpdated) + + // The fake scanner always returns a scan with exactly 1 CVE. + require.Len(t, img.GetScan().GetComponents(), 1) + require.Len(t, img.GetScan().GetComponents()[0].GetVulns(), 1) + + assert.Equal(t, int32(1), img.GetScanStats().GetCveCount()) +} + func TestZeroIntegrationsV2(t *testing.T) { testutils.MustUpdateFeature(t, features.FlattenImageData, true) ctrl := gomock.NewController(t) diff --git a/pkg/images/utils/test/utils_test.go b/pkg/images/utils/test/utils_test.go index dd2764f687aea..116dba67278a1 100644 --- a/pkg/images/utils/test/utils_test.go +++ b/pkg/images/utils/test/utils_test.go @@ -606,6 +606,38 @@ func TestFillScanStatsV2(t *testing.T) { } } +// TestFilterSuppressedCVEsNoCloneV2 confirms that filtering out suppressed CVEs also +// updates ScanStats to match (ROX-36389). +func TestFilterSuppressedCVEsNoCloneV2(t *testing.T) { + testutils.MustUpdateFeature(t, features.FlattenImageData, true) + + image := &storage.ImageV2{ + Id: utils.NewImageV2ID(&storage.ImageName{Registry: "reg", FullName: "reg"}, "sha"), + Digest: "sha", + Name: &storage.ImageName{Registry: "reg", FullName: "reg"}, + Scan: &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + { + Vulns: []*storage.EmbeddedVulnerability{ + {Cve: "cve-1", Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY}, + {Cve: "cve-2", Severity: storage.VulnerabilitySeverity_MODERATE_VULNERABILITY_SEVERITY, State: storage.VulnerabilityState_DEFERRED}, + }, + }, + }, + }, + // ScanStats reflects the unfiltered scan, as it would for an image already + // fetched from the datastore before filtering. + ScanStats: &storage.ImageV2_ScanStats{CveCount: 2}, + } + + utils.FilterSuppressedCVEsNoCloneV2(image) + + if assert.Len(t, image.GetScan().GetComponents()[0].GetVulns(), 1) { + assert.Equal(t, "cve-1", image.GetScan().GetComponents()[0].GetVulns()[0].GetCve()) + } + assert.Equal(t, int32(1), image.GetScanStats().GetCveCount()) +} + func TestStripDatasourceNoClone(t *testing.T) { original := &storage.ImageScan{ Components: []*storage.EmbeddedImageScanComponent{ diff --git a/pkg/images/utils/utils.go b/pkg/images/utils/utils.go index 8e920cb41becf..2722e81ffde0d 100644 --- a/pkg/images/utils/utils.go +++ b/pkg/images/utils/utils.go @@ -397,9 +397,6 @@ func FillScanStatsV2(i *storage.ImageV2) { if i.GetScan() == nil { return } - if i.GetScanStats() != nil { - return - } i.ScanStats = &storage.ImageV2_ScanStats{} i.GetScanStats().ComponentCount = int32(len(i.GetScan().GetComponents())) From 0bd734a26dc3cc3fd4e611ac98d3a8b37b5039ed Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Wed, 2 Sep 2026 13:43:11 +0000 Subject: [PATCH 164/638] ROX-36528: retry network policy apply/undo calls (#22471) --- .../src/test/groovy/NetworkFlowTest.groovy | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/qa-tests-backend/src/test/groovy/NetworkFlowTest.groovy b/qa-tests-backend/src/test/groovy/NetworkFlowTest.groovy index 5b9af9972d824..d8e2a279d3358 100644 --- a/qa-tests-backend/src/test/groovy/NetworkFlowTest.groovy +++ b/qa-tests-backend/src/test/groovy/NetworkFlowTest.groovy @@ -956,7 +956,9 @@ class NetworkFlowTest extends BaseSpecification { expect: "actual policies should exist in generated response depending on delete mode" def modification = NetworkPolicyService.generateNetworkPolicies(deleteMode, 'Namespace:r/^qa2?$') - assert !(NetworkPolicyService.applyGeneratedNetworkPolicy(modification) instanceof StatusRuntimeException) + Helpers.withRetry(3, 2) { + assert !(NetworkPolicyService.applyGeneratedNetworkPolicy(modification) instanceof StatusRuntimeException) + } def appliedNetworkPolicies = getQANetworkPoliciesNamesByNamespace(true) log.info "${appliedNetworkPolicies}" @@ -1009,10 +1011,12 @@ class NetworkFlowTest extends BaseSpecification { def undoRecord = NetworkPolicyService.undoGeneratedNetworkPolicy() assert undoRecord.originalModification == modification - assert !( - NetworkPolicyService.applyGeneratedNetworkPolicy(undoRecord.undoModification) - instanceof StatusRuntimeException - ) + Helpers.withRetry(3, 2) { + assert !( + NetworkPolicyService.applyGeneratedNetworkPolicy(undoRecord.undoModification) + instanceof StatusRuntimeException + ) + } def undoNetworkPolicies = getQANetworkPoliciesNamesByNamespace(true) log.info "${undoNetworkPolicies}" assert undoNetworkPolicies == preExistingNetworkPolicies From 75882db56eec3f06bcfdcbb252375c238f221458 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 2 Sep 2026 16:03:40 +0200 Subject: [PATCH 165/638] tests(crdify): allow description changes (#22566) --- operator/tools/crdify/config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/operator/tools/crdify/config.yaml b/operator/tools/crdify/config.yaml index dc75d2e5504c9..e0fae3db71b86 100644 --- a/operator/tools/crdify/config.yaml +++ b/operator/tools/crdify/config.yaml @@ -1,4 +1,7 @@ +# Please order alphabetically by name validations: + - name: description + enforcement: Warn - name: enum enforcement: Error configuration: From 8762c4cbb4d5b45b9d1461ed8e476cc4dc6f270f Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Wed, 2 Sep 2026 07:46:07 -0700 Subject: [PATCH 166/638] ROX-36571: Fix sensor race condition (#22544) Co-authored-by: Claude Opus 4.8 --- sensor/common/clusterentities/store.go | 74 ++++++++-- .../clusterentities/store_heritage_test.go | 139 +++++++++++++++++- 2 files changed, 203 insertions(+), 10 deletions(-) diff --git a/sensor/common/clusterentities/store.go b/sensor/common/clusterentities/store.go index 1a17660f2c2d0..90313c046ac25 100644 --- a/sensor/common/clusterentities/store.go +++ b/sensor/common/clusterentities/store.go @@ -112,6 +112,24 @@ func (ed *EntityData) AddContainerID(containerID string, container ContainerMeta ed.containerIDs[containerID] = container } +// Clone returns a deep copy of the EntityData that can be mutated independently of the original. +func (ed *EntityData) Clone() *EntityData { + if ed == nil { + return nil + } + clone := &EntityData{ + ips: maps.Clone(ed.ips), + containerIDs: maps.Clone(ed.containerIDs), + } + if ed.endpoints != nil { + clone.endpoints = make(map[net.NumericEndpoint][]EndpointTargetInfo, len(ed.endpoints)) + for ep, infos := range ed.endpoints { + clone.endpoints[ep] = slices.Clone(infos) + } + } + return clone +} + // Store is a store for managing cluster entities (currently deployments only) and allows looking them up by // endpoint. type Store struct { @@ -128,6 +146,10 @@ type Store struct { pastSensors HeritageManager // heritageApplied ensures that past heritage data is applied to the current instance only once. heritageApplied concurrency.Signal + // applyHeritageLock serializes concurrent ApplyDataFromHeritageOnce calls. With the PubSub + // concurrent lane, deployment events are processed on separate goroutines, so the heritage + // apply can be entered concurrently; without this lock the "apply once" guard would race. + applyHeritageLock sync.Mutex // Cache enriched data for the current instance of Sensor. // This data won't be stored into config map. @@ -201,6 +223,13 @@ func (e *Store) GetHeritageManager() HeritageManager { // ApplyDataFromHeritageOnce adds heritage data about past sensors to the store if that hasn't happened yet. func (e *Store) ApplyDataFromHeritageOnce() { + if e.heritageApplied.IsDone() { + return + } + // Serialize concurrent callers and re-check under the lock: the initial IsDone check above + // is only a fast path. Two goroutines could both pass it before either signals completion. + e.applyHeritageLock.Lock() + defer e.applyHeritageLock.Unlock() if e.heritageApplied.IsDone() { return } @@ -238,15 +267,33 @@ func (e *Store) applyHeritageData(dg dataGetter) bool { return false } - e.currentSensorLock.RLock() - defer e.currentSensorLock.RUnlock() + var deploymentID string + var modEntityData *EntityData + // Clone under the read lock and mutate the private copy. The shared currentSensorEntityData + // is also referenced by concurrent Apply() calls (see onDeploymentCreateOrUpdate), so mutating + // it in place would race with those readers. + concurrency.WithRLock(&e.currentSensorLock, func() { + deploymentID = e.currentSensorDeploymentID + modEntityData = e.currentSensorEntityData.Clone() + }) + + // Snapshot the current Sensor's endpoints once. Each past entry derives its endpoints from + // this immutable snapshot; deriving from modEntityData.endpoints instead would compound the + // endpoints added for earlier past entries, growing exponentially with the number of entries. + srcEndpoints := maps.Clone(modEntityData.endpoints) + changed := false for _, entry := range past { - log.Infof("Applying heritage data %q to current Sensor deploymentID %s", entry.String(), e.currentSensorDeploymentID) - modEntityData := e.currentSensorEntityData - if applyPastToEntityData(modEntityData, entry) { - e.Apply(map[string]*EntityData{e.currentSensorDeploymentID: modEntityData}, true) + log.Infof("Applying heritage data %q to current Sensor deploymentID %s", entry.String(), deploymentID) + if applyPastToEntityData(modEntityData, srcEndpoints, entry) { + changed = true } } + // modEntityData accumulates every past entry, so a single Apply after the loop suffices. + // Applying inside the loop re-sent the whole growing object as an incremental update on each + // iteration; the store dedupes with sets so the result was identical, just extra work. + if changed { + e.Apply(map[string]*EntityData{deploymentID: modEntityData}, true) + } return true } @@ -266,7 +313,11 @@ func (e *Store) HasCurrentSensorMetadata() bool { } // applyPastToEntityData returns true if the `past` data was added to `data`; false otherwise. -func applyPastToEntityData(data *EntityData, past *heritage.SensorMetadata) bool { +// srcEndpoints is an immutable snapshot of the current Sensor's endpoints; past endpoints are +// derived from it rather than from data.endpoints so that endpoints added for earlier past entries +// are neither re-derived (which compounds exponentially across past entries) nor mutated while +// being ranged over. +func applyPastToEntityData(data *EntityData, srcEndpoints map[net.NumericEndpoint][]EndpointTargetInfo, past *heritage.SensorMetadata) bool { if _, found := data.containerIDs[past.ContainerID]; found { // The cluster entities store knows about this past instance of sensor. Skip adding. log.Debugf("Cluster entities store already knows about container %s. Skipping.", past.ContainerID) @@ -277,9 +328,14 @@ func applyPastToEntityData(data *EntityData, past *heritage.SensorMetadata) bool pastIP := net.ParseIP(past.PodIP) // Adding additional pod IP to the Sensor deployment data.AddIP(pastIP) - for endpoint, infos := range data.endpoints { - // Craft endpoint with the same port and proto but with past IP + // Craft endpoints with the same port and proto but with the past IP. + for endpoint, infos := range srcEndpoints { newEndpoint := net.MakeNumericEndpoint(pastIP, endpoint.IPAndPort.Port, endpoint.L4Proto) + if _, exists := data.endpoints[newEndpoint]; exists { + // The past pod IP coincides with an endpoint that already exists (e.g. the current + // Sensor's IP was previously used by this past instance); nothing new to add. + continue + } // Container port and port name included in `infos` remain the same for _, info := range infos { data.AddEndpoint(newEndpoint, info) diff --git a/sensor/common/clusterentities/store_heritage_test.go b/sensor/common/clusterentities/store_heritage_test.go index 7c7ed6a78339b..497104cbe33e3 100644 --- a/sensor/common/clusterentities/store_heritage_test.go +++ b/sensor/common/clusterentities/store_heritage_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/stackrox/rox/pkg/net" + "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/sensor/common/heritage" "github.com/stretchr/testify/assert" ) @@ -134,6 +135,73 @@ func TestStore_applyHeritageData(t *testing.T) { } } +// TestStore_ApplyHeritageDataOnce_Concurrent is a regression test for a "concurrent map writes" +// crash: with the PubSub concurrent lane, deployment events are processed on separate goroutines, +// so ApplyDataFromHeritageOnce could be entered concurrently. Both callers passed the "once" guard +// and mutated the shared currentSensorEntityData maps at the same time. Run with -race. +func TestStore_ApplyHeritageDataOnce_Concurrent(t *testing.T) { + mockHM := &mockHeritageManager{ + data: []*heritage.SensorMetadata{ + {ContainerID: "past123", PodIP: "10.1.1.1", SensorStart: time.Now().Add(-time.Hour)}, + {ContainerID: "past456", PodIP: "10.1.1.2", SensorStart: time.Now().Add(-2 * time.Hour)}, + }, + isEnabled: true, + } + store := NewStore(0, mockHM, false) + + currentData := createSensorEntityData("current123", "10.2.2.2") + // Endpoints are required to exercise EntityData.AddEndpoint, the site of the original crash. + currentData.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 8443, net.TCP), EndpointTargetInfo{ContainerPort: 8443}) + currentData.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 9090, net.TCP), EndpointTargetInfo{ContainerPort: 9090}) + store.RememberCurrentSensorMetadata("sensor-deploy-1", currentData) + + var wg sync.WaitGroup + for range 50 { + wg.Go(func() { + store.ApplyDataFromHeritageOnce() + }) + } + wg.Wait() + + assert.True(t, store.heritageApplied.IsDone()) +} + +// TestStore_ApplyHeritage_ConcurrentWithApply is a regression test for a data race between the +// heritage apply and a concurrent Apply() call on the same EntityData. onDeploymentCreateOrUpdate +// stores the deployment's EntityData via RememberCurrentSensorMetadata and then passes that same +// object to Apply(); with the PubSub concurrent lane these run on separate goroutines. Serializing +// the heritage apply is not enough: it must not mutate the shared object in place while Apply reads +// it. Run with -race. +func TestStore_ApplyHeritage_ConcurrentWithApply(t *testing.T) { + mockHM := &mockHeritageManager{ + data: []*heritage.SensorMetadata{ + {ContainerID: "past123", PodIP: "10.1.1.1", SensorStart: time.Now().Add(-time.Hour)}, + }, + isEnabled: true, + } + store := NewStore(0, mockHM, false) + + // The same object is remembered as current sensor metadata and passed to Apply below. + shared := createSensorEntityData("current123", "10.2.2.2") + shared.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 8443, net.TCP), EndpointTargetInfo{ContainerPort: 8443}) + shared.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 9090, net.TCP), EndpointTargetInfo{ContainerPort: 9090}) + store.RememberCurrentSensorMetadata("sensor-deploy-1", shared) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + store.ApplyDataFromHeritageOnce() + }() + go func() { + defer wg.Done() + store.Apply(map[string]*EntityData{"sensor-deploy-1": shared}, false) + }() + wg.Wait() + + assert.True(t, store.heritageApplied.IsDone()) +} + func TestApplyPastToEntityData(t *testing.T) { tests := map[string]struct { currentData *EntityData @@ -187,7 +255,7 @@ func TestApplyPastToEntityData(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - result := applyPastToEntityData(tt.currentData, tt.pastSensor) + result := applyPastToEntityData(tt.currentData, maps.Clone(tt.currentData.endpoints), tt.pastSensor) assert.Equal(t, tt.expectedResult, result) containerIDs := tt.currentData.GetContainerIDs("sensor") podIPs := tt.currentData.GetValidIPs() @@ -212,6 +280,75 @@ func TestApplyPastToEntityData(t *testing.T) { } } +// TestApplyPastToEntityData_NoDuplicateEndpoints is a regression test for endpoints being +// duplicated because applyPastToEntityData added entries to data.endpoints while ranging over it. +// Adding keys to a map during iteration is nondeterministic in Go (a new key "may be produced or +// may be skipped"); with enough endpoints the buggy version reliably re-visited freshly added +// past-IP endpoints and appended their info a second time. +func TestApplyPastToEntityData_NoDuplicateEndpoints(t *testing.T) { + const nPorts = 64 + data := createSensorEntityData("current123", "10.2.2.2") + for i := range nPorts { + port := uint16(1000 + i) + data.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), port, net.TCP), EndpointTargetInfo{ContainerPort: port}) + } + + added := applyPastToEntityData(data, maps.Clone(data.endpoints), &heritage.SensorMetadata{ContainerID: "past456", PodIP: "10.1.1.1"}) + assert.True(t, added) + + // Every endpoint (current and past) must carry exactly one info; a longer slice means the map + // was mutated while being ranged over and an entry was processed twice. + assert.Len(t, data.endpoints, 2*nPorts, "expected current + past endpoints and nothing else") + for ep, infos := range data.endpoints { + assert.Lenf(t, infos, 1, "endpoint %v has duplicate infos: %v", ep, infos) + } +} + +// TestApplyPastToEntityData_MultiplePastNoCompounding is a regression test for endpoints compounding +// across past entries: applyHeritageData reuses a single EntityData for all past sensors, so deriving +// past endpoints from data.endpoints (which grows each entry) rather than from an immutable snapshot +// caused the per-endpoint info slice to double with every additional past sensor (1, 2, 4, 8, ...). +func TestApplyPastToEntityData_MultiplePastNoCompounding(t *testing.T) { + data := createSensorEntityData("current123", "10.2.2.2") + data.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 8443, net.TCP), EndpointTargetInfo{ContainerPort: 8443}) + data.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 9090, net.TCP), EndpointTargetInfo{ContainerPort: 9090}) + + // Mirror applyHeritageData: snapshot once, then apply every past entry against that snapshot. + srcEndpoints := maps.Clone(data.endpoints) + past := []*heritage.SensorMetadata{ + {ContainerID: "past1", PodIP: "10.1.1.1"}, + {ContainerID: "past2", PodIP: "10.1.1.2"}, + {ContainerID: "past3", PodIP: "10.1.1.3"}, + {ContainerID: "past4", PodIP: "10.1.1.4"}, + } + for _, entry := range past { + assert.True(t, applyPastToEntityData(data, srcEndpoints, entry)) + } + + // 2 ports for the current IP plus 2 ports per past IP, each with exactly one info. + assert.Len(t, data.endpoints, 2*(1+len(past)), "unexpected number of endpoints") + for ep, infos := range data.endpoints { + assert.Lenf(t, infos, 1, "endpoint %v has duplicate infos: %v", ep, infos) + } +} + +// TestApplyPastToEntityData_PastIPEqualsCurrent is a regression test for a past pod IP that coincides +// with the current Sensor's endpoint IP: deriving a past endpoint would collide with an existing key +// and append a duplicate info to it. Applying the same past entry must leave one info per endpoint. +func TestApplyPastToEntityData_PastIPEqualsCurrent(t *testing.T) { + data := createSensorEntityData("current123", "10.2.2.2") + data.AddEndpoint(net.MakeNumericEndpoint(net.ParseIP("10.2.2.2"), 8443, net.TCP), EndpointTargetInfo{ContainerPort: 8443}) + + srcEndpoints := maps.Clone(data.endpoints) + // Past sensor reused the current pod IP but has a different container ID. + assert.True(t, applyPastToEntityData(data, srcEndpoints, &heritage.SensorMetadata{ContainerID: "past456", PodIP: "10.2.2.2"})) + + assert.Len(t, data.endpoints, 1, "no new endpoint expected for a coinciding IP") + for ep, infos := range data.endpoints { + assert.Lenf(t, infos, 1, "endpoint %v has duplicate infos: %v", ep, infos) + } +} + func TestEntityData_String_SlicesCollectFix(t *testing.T) { // Test justification: Validates the slices.Collect fix for proper formatting tests := map[string]struct { From e14d162a4371c945358d83d090365648bd5a1375 Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:56:09 -0700 Subject: [PATCH 167/638] ROX-36660: Match unset FixedBy as not fixable (#22540) --- CHANGELOG.md | 1 + .../evaluator/path_evaluators.go | 4 +- .../evaluator/pathutil/augmented_obj.go | 10 + .../evaluator/pathutil/augmented_obj_test.go | 6 + pkg/booleanpolicy/image_criteria_test.go | 185 ++++++++++++++++++ 5 files changed, 205 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c53f530ec870..ef0aa42afc471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35079: installation of the `app.k8s.io/v1beta1/Application` resource when central is installed is deprecated. It will be removed in a future release. ### Technical Changes +- ROX-36660: The **Fixable → CVE is not yet fixable** policy criterion now matches Scanner V4 CVEs that have no fix version. Scanner V4 leaves `Fixed By` unset instead of empty (Scanner V2 always set an empty string), so the matcher previously skipped those CVEs. - ROX-36490: The virtual machine enhanced data model (`ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL`) is now enabled by default. - ROX-32969: The `roxctl-linux` symlink has been removed from the `/assets/downloads/cli/` directory inside the main container image. Only the architecture-specific binaries (`roxctl-linux-amd64`, `roxctl-linux-arm64`, etc.) remain. This change does not affect CLI downloads from the Central UI or any other supported download path. - ROX-33078: Fixed telemetry gatherer failing to report database size metrics when using an external database. The database name is now read from the connection config instead of using the hardcoded default. diff --git a/pkg/booleanpolicy/evaluator/path_evaluators.go b/pkg/booleanpolicy/evaluator/path_evaluators.go index 39d1688a81a11..58b4a0915d3ca 100644 --- a/pkg/booleanpolicy/evaluator/path_evaluators.go +++ b/pkg/booleanpolicy/evaluator/path_evaluators.go @@ -61,7 +61,9 @@ func takeInterfaceMetaStep(metaStep pathutil.MetaStep, evaluator fieldEvaluator) return fieldEvaluatorFunc(func(value pathutil.AugmentedValue) (*fieldResult, bool) { underlying := value.Underlying() if underlying.IsNil() { - return nil, false + // Unset protobuf oneof (e.g. Scanner V4 not-fixable CVE: no fixed_by). + // Evaluate against the field zero value so empty-string queries can match. + return evaluator.Evaluate(value.WithUnderlying(reflect.Zero(metaStep.Type))) } nextValue := value.Elem() if nextValue.Underlying().Kind() == reflect.Pointer { diff --git a/pkg/booleanpolicy/evaluator/pathutil/augmented_obj.go b/pkg/booleanpolicy/evaluator/pathutil/augmented_obj.go index 7f01338de7f50..2b1a8266b62b9 100644 --- a/pkg/booleanpolicy/evaluator/pathutil/augmented_obj.go +++ b/pkg/booleanpolicy/evaluator/pathutil/augmented_obj.go @@ -110,6 +110,10 @@ type AugmentedValue interface { // It panics if Index(i) on the reflect.Value panics. Index(int) AugmentedValue PathFromRoot() *Path + // WithUnderlying returns a copy of this value with a different underlying + // reflect.Value, keeping the path from root and augment tree. Used when a + // protobuf oneof is unset so empty-string queries can still match. + WithUnderlying(newVal reflect.Value) AugmentedValue } type augmentedValue struct { @@ -134,6 +138,12 @@ func (v *augmentedValue) Underlying() reflect.Value { return v.underlying } +func (v *augmentedValue) WithUnderlying(newVal reflect.Value) AugmentedValue { + cp := *v + cp.underlying = newVal + return &cp +} + func (v *augmentedValue) TakeStep(metaStep MetaStep) (AugmentedValue, bool) { var newUnderlying reflect.Value var found bool diff --git a/pkg/booleanpolicy/evaluator/pathutil/augmented_obj_test.go b/pkg/booleanpolicy/evaluator/pathutil/augmented_obj_test.go index 02693afaff65a..0b058afa45df8 100644 --- a/pkg/booleanpolicy/evaluator/pathutil/augmented_obj_test.go +++ b/pkg/booleanpolicy/evaluator/pathutil/augmented_obj_test.go @@ -1,6 +1,7 @@ package pathutil import ( + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -21,6 +22,11 @@ func TestAugmentedObj(t *testing.T) { value := o.Value() assert.Equal(t, topLevelObj, value.Underlying().Interface()) + zeroed := value.WithUnderlying(reflect.ValueOf("")) + assert.Equal(t, "", zeroed.Underlying().Interface()) + assert.Equal(t, value.PathFromRoot().String(), zeroed.PathFromRoot().String()) + assert.Equal(t, topLevelObj, value.Underlying().Interface()) + intObjValue, found := value.TakeStep(MetaStep{FieldName: "IntObj"}) assert.True(t, found) assert.Equal(t, intObj, intObjValue.Underlying().Interface()) diff --git a/pkg/booleanpolicy/image_criteria_test.go b/pkg/booleanpolicy/image_criteria_test.go index 17872b6ae139e..3bd89e42d5d6d 100644 --- a/pkg/booleanpolicy/image_criteria_test.go +++ b/pkg/booleanpolicy/image_criteria_test.go @@ -178,6 +178,191 @@ func (suite *ImageCriteriaTestSuite) TestFixableAndFixTimestampAvailableCriteria } +func (suite *ImageCriteriaTestSuite) TestFixableCriteriaWithUnsetFixedBy() { + dep := &storage.Deployment{ + Id: "MIXEDFIXABLEDEPID", + Containers: []*storage.Container{ + { + Name: "nginx", + Image: &storage.ContainerImage{Id: "MIXEDFIXABLESHA"}, + }, + }, + } + + img := &storage.Image{ + Id: "MIXEDFIXABLESHA", + Name: &storage.ImageName{FullName: "mixed-fixable"}, + Scan: &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "openssl", Version: "1.2", Vulns: []*storage.EmbeddedVulnerability{ + { + Cve: "CVE-FIXABLE", + Cvss: 9, + Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, + SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{FixedBy: "v1.2"}, + }, + { + Cve: "CVE-UNFIXABLE", + Cvss: 8, + Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY, + // Scanner V4 leaves the oneof unset when there is no fix. + }, + { + Cve: "CVE-EMPTY-FIXEDBY", + Cvss: 7, + Severity: storage.VulnerabilitySeverity_MODERATE_VULNERABILITY_SEVERITY, + SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{}, + }, + }}, + }, + }, + } + suite.addDepAndImages(dep, img) + + allCVEs := []string{"CVE-FIXABLE", "CVE-UNFIXABLE", "CVE-EMPTY-FIXEDBY"} + + for name, tc := range map[string]struct { + policy *storage.Policy + expectedCVEs []string + }{ + "fixable true matches only CVEs with a fix version": { + policy: policyWithSingleKeyValue(fieldnames.Fixable, "true", false), + expectedCVEs: []string{"CVE-FIXABLE"}, + }, + "fixable false matches unset and empty FixedBy": { + policy: policyWithSingleKeyValue(fieldnames.Fixable, "false", false), + expectedCVEs: []string{"CVE-UNFIXABLE", "CVE-EMPTY-FIXEDBY"}, + }, + "negated Fixed By matches not-fixable CVEs": { + policy: policyWithSingleKeyValue(fieldnames.FixedBy, "v1.2", true), + expectedCVEs: []string{"CVE-UNFIXABLE", "CVE-EMPTY-FIXEDBY"}, + }, + "fixable false combined with CVE stays linked to that CVE": { + policy: policyWithGroups(storage.EventSource_NOT_APPLICABLE, + policyGroupWithSingleKeyValue(fieldnames.Fixable, "false", false), + policyGroupWithSingleKeyValue(fieldnames.CVE, "CVE-UNFIXABLE", false), + ), + expectedCVEs: []string{"CVE-UNFIXABLE"}, + }, + // Default policy "Fixable CVSS >= 7" stores Fixed By: .*, rewritten to .+ at match time. + "fixed by .* with CVSS >= 7 still matches only the fixable CVE": { + policy: policyWithGroups(storage.EventSource_NOT_APPLICABLE, + policyGroupWithSingleKeyValue(fieldnames.FixedBy, ".*", false), + policyGroupWithSingleKeyValue(fieldnames.CVSS, ">= 7", false), + ), + expectedCVEs: []string{"CVE-FIXABLE"}, + }, + } { + suite.Run(name, func() { + expected := set.NewFrozenStringSet(tc.expectedCVEs...) + + depMatcher, err := BuildDeploymentMatcher(tc.policy) + suite.Require().NoError(err) + depViolations, err := depMatcher.MatchDeployment(nil, enhancedDeployment(dep, suite.getImagesForDeployment(dep))) + suite.Require().NoError(err) + assertFixableCVEMatches(suite.T(), depViolations, allCVEs, expected) + + imgMatcher, err := BuildImageMatcher(tc.policy) + suite.Require().NoError(err) + imgViolations, err := imgMatcher.MatchImage(nil, img) + suite.Require().NoError(err) + assertFixableCVEMatches(suite.T(), imgViolations, allCVEs, expected) + }) + } +} + +// TestCustomerFixableNotYetFixableOnMixedCVEDeployment reproduces the customer-reported regression where a +// "Fixable → CVE is not yet fixable" policy (fieldnames.Fixable = "false") returned no violations in Policy +// Preview even when the deployment's image contained unfixable CVEs (SetFixedBy unset, Scanner V4 style). +// The test runs both the fixable=true and fixable=false policies against the same single deployment so that the +// side-by-side comparison that exposed the bug is explicit. +func (suite *ImageCriteriaTestSuite) TestCustomerFixableNotYetFixableOnMixedCVEDeployment() { + const ( + fixableCVE = "CVE-CUSTOMER-FIXABLE" + unfixableCVE = "CVE-CUSTOMER-UNFIXABLE" + ) + + dep := &storage.Deployment{ + Id: "CUSTMIXEDDEPID", + Containers: []*storage.Container{ + { + Name: "app", + Image: &storage.ContainerImage{Id: "CUSTMIXEDIMGSHA"}, + }, + }, + } + + img := &storage.Image{ + Id: "CUSTMIXEDIMGSHA", + Name: &storage.ImageName{FullName: "registry.example.com/app:latest"}, + Scan: &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{ + {Name: "openssl", Version: "3.0", Vulns: []*storage.EmbeddedVulnerability{ + { + Cve: fixableCVE, + Cvss: 9, + Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, + SetFixedBy: &storage.EmbeddedVulnerability_FixedBy{FixedBy: "v1.2"}, + }, + { + Cve: unfixableCVE, + Cvss: 8, + Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY, + // Scanner V4 leaves SetFixedBy nil when no fix is available. + }, + }}, + }, + }, + } + suite.addDepAndImages(dep, img) + + ed := enhancedDeployment(dep, suite.getImagesForDeployment(dep)) + + suite.Run("fixable=true matches only the fixable CVE", func() { + matcher, err := BuildDeploymentMatcher(policyWithSingleKeyValue(fieldnames.Fixable, "true", false)) + suite.Require().NoError(err) + violations, err := matcher.MatchDeployment(nil, ed) + suite.Require().NoError(err) + suite.Require().Len(violations.AlertViolations, 1) + suite.Contains(violations.AlertViolations[0].GetMessage(), fixableCVE, + "fixable=true must produce a violation for the fixable CVE") + suite.NotContains(violations.AlertViolations[0].GetMessage(), unfixableCVE, + "fixable=true must not mention the unfixable CVE") + }) + + suite.Run("fixable=false matches only the not-yet-fixable CVE (regression)", func() { + matcher, err := BuildDeploymentMatcher(policyWithSingleKeyValue(fieldnames.Fixable, "false", false)) + suite.Require().NoError(err) + violations, err := matcher.MatchDeployment(nil, ed) + suite.Require().NoError(err) + suite.Require().Len(violations.AlertViolations, 1, + "fixable=false must produce exactly one violation for the CVE with no fix (was zero before the fix)") + suite.Contains(violations.AlertViolations[0].GetMessage(), unfixableCVE, + "fixable=false must produce a violation for the not-yet-fixable CVE") + suite.NotContains(violations.AlertViolations[0].GetMessage(), fixableCVE, + "fixable=false must not mention the fixable CVE") + }) +} + +func assertFixableCVEMatches(t *testing.T, violations Violations, allCVEs []string, expected set.FrozenStringSet) { + t.Helper() + require.Len(t, violations.AlertViolations, expected.Cardinality()) + for _, cve := range allCVEs { + var found bool + for _, v := range violations.AlertViolations { + if strings.Contains(v.GetMessage(), cve) { + found = true + break + } + } + if expected.Contains(cve) { + assert.True(t, found, "expected a violation mentioning %s, got %v", cve, violations.AlertViolations) + continue + } + assert.False(t, found, "did not expect a violation mentioning %s, got %v", cve, violations.AlertViolations) + } +} + func (suite *ImageCriteriaTestSuite) TestDaysSinceCVEPublishedCriteria() { heartbleedDep := &storage.Deployment{ Id: "HEARTBLEEDDEPID", From e44b8e61ac5ec120880a2eaa761e8851e3ef3653 Mon Sep 17 00:00:00 2001 From: Charmik Sheth <101146970+charmik-redhat@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:06:17 -0700 Subject: [PATCH 168/638] ROX-36253: Node CVE report generator and scheduler wiring (#22420) Co-authored-by: Claude Opus 4.6 --- central/reports/common/query_builder.go | 74 ++-- central/reports/common/query_builder_test.go | 42 ++ central/reports/common/utils.go | 54 ++- .../v2/reportgenerator/common_helpers.go | 74 ++++ .../v2/reportgenerator/node/query_builder.go | 84 ++++ .../node/query_builder_test.go | 277 +++++++++++++ .../v2/reportgenerator/node/report_gen.go | 33 ++ .../reportgenerator/node/report_gen_impl.go | 265 ++++++++++++ .../node/report_gen_impl_test.go | 387 ++++++++++++++++++ .../v2/reportgenerator/node/singleton.go | 41 ++ .../v2/reportgenerator/report_gen_impl.go | 50 +-- .../scheduler/v2/reportgenerator/validate.go | 8 +- central/reports/scheduler/v2/singleton.go | 3 +- central/reports/service/v2/download.go | 3 +- 14 files changed, 1307 insertions(+), 88 deletions(-) create mode 100644 central/reports/scheduler/v2/reportgenerator/common_helpers.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/query_builder.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/query_builder_test.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/report_gen.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/report_gen_impl_test.go create mode 100644 central/reports/scheduler/v2/reportgenerator/node/singleton.go diff --git a/central/reports/common/query_builder.go b/central/reports/common/query_builder.go index 196fadc21457c..cbf2ada69d1c3 100644 --- a/central/reports/common/query_builder.go +++ b/central/reports/common/query_builder.go @@ -178,39 +178,9 @@ func (q *queryBuilder) buildEntityScopeQuery() (*v1.Query, error) { fieldLabel == search.NamespaceAnnotation if isMapField { - mapQueries := make([]*v1.Query, 0, len(rule.GetValues())) - for _, rv := range rule.GetValues() { - val := rv.GetValue() - key, value := splitLabelValue(val) - if rv.GetMatchType() == storage.MatchType_REGEX { - // For regex match, only apply the r/ prefix to parts that contain - // regex metacharacters. Literal parts stay quoted for exact matching. - rawParts := strings.SplitN(val, "=", 2) - rawValue := "" - if len(rawParts) == 2 { - rawValue = rawParts[1] - } - key = mapPartQueryString(rawParts[0]) - value = mapPartQueryString(rawValue) - } - mapQueries = append(mapQueries, - search.NewQueryBuilder().AddMapQuery(fieldLabel, key, value).ProtoQuery()) - } - conjuncts = append(conjuncts, search.DisjunctionQuery(mapQueries...)) + conjuncts = append(conjuncts, MapFieldQuery(fieldLabel, rule)) } else { - var ruleQueries []*v1.Query - for _, rv := range rule.GetValues() { - val := rv.GetValue() - if rv.GetMatchType() == storage.MatchType_REGEX { - val = search.RegexPrefix + val - ruleQueries = append(ruleQueries, - search.NewQueryBuilder().AddStrings(fieldLabel, val).ProtoQuery()) - } else { - ruleQueries = append(ruleQueries, - search.NewQueryBuilder().AddExactMatches(fieldLabel, val).ProtoQuery()) - } - } - conjuncts = append(conjuncts, search.DisjunctionQuery(ruleQueries...)) + conjuncts = append(conjuncts, ScalarFieldQuery(fieldLabel, rule)) } } @@ -220,6 +190,46 @@ func (q *queryBuilder) buildEntityScopeQuery() (*v1.Query, error) { return search.ConjunctionQuery(conjuncts...), nil } +// MapFieldQuery builds a disjunction query for a map-type search field +// (labels/annotations) from the values in an entity scope rule. +func MapFieldQuery(fieldLabel search.FieldLabel, rule *storage.EntityScopeRule) *v1.Query { + mapQueries := make([]*v1.Query, 0, len(rule.GetValues())) + for _, rv := range rule.GetValues() { + val := rv.GetValue() + key, value := splitLabelValue(val) + if rv.GetMatchType() == storage.MatchType_REGEX { + rawParts := strings.SplitN(val, "=", 2) + rawValue := "" + if len(rawParts) == 2 { + rawValue = rawParts[1] + } + key = mapPartQueryString(rawParts[0]) + value = mapPartQueryString(rawValue) + } + mapQueries = append(mapQueries, + search.NewQueryBuilder().AddMapQuery(fieldLabel, key, value).ProtoQuery()) + } + return search.DisjunctionQuery(mapQueries...) +} + +// ScalarFieldQuery builds a disjunction query for a scalar-type search field +// (names, IDs) from the values in an entity scope rule. +func ScalarFieldQuery(fieldLabel search.FieldLabel, rule *storage.EntityScopeRule) *v1.Query { + values := rule.GetValues() + ruleQueries := make([]*v1.Query, 0, len(values)) + for _, rv := range values { + val := rv.GetValue() + if rv.GetMatchType() == storage.MatchType_REGEX { + ruleQueries = append(ruleQueries, + search.NewQueryBuilder().AddRegexes(fieldLabel, val).ProtoQuery()) + } else { + ruleQueries = append(ruleQueries, + search.NewQueryBuilder().AddExactMatches(fieldLabel, val).ProtoQuery()) + } + } + return search.DisjunctionQuery(ruleQueries...) +} + // entityScopeRuleToFieldLabel returns search filter for given entity field pair func entityScopeRuleToFieldLabel(rule *storage.EntityScopeRule) (search.FieldLabel, error) { switch rule.GetEntity() { diff --git a/central/reports/common/query_builder_test.go b/central/reports/common/query_builder_test.go index a8a8e7946da3f..33124ea2f53be 100644 --- a/central/reports/common/query_builder_test.go +++ b/central/reports/common/query_builder_test.go @@ -198,6 +198,48 @@ func assertByDirectComparison(t testing.TB, expected *v1.Query, actual *v1.Query protoassert.Equal(t, expected, actual) } +func TestBuildClusterOnlyAccessScopeQuery(t *testing.T) { + testCases := map[string]struct { + rules []*storage.SimpleAccessScope_Rules + expectedQ *v1.Query + }{ + "nil rules returns empty query": { + rules: nil, + expectedQ: search.EmptyQuery(), + }, + "cluster included by name": { + rules: []*storage.SimpleAccessScope_Rules{ + {IncludedClusters: []string{"remote"}}, + }, + expectedQ: search.NewQueryBuilder().AddExactMatches(search.ClusterID, clusters[0].GetId()).ProtoQuery(), + }, + "namespace-only rule grants cluster visibility": { + rules: []*storage.SimpleAccessScope_Rules{ + { + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + {ClusterName: "secured", NamespaceName: "ns2"}, + }, + }, + }, + expectedQ: search.NewQueryBuilder().AddExactMatches(search.ClusterID, clusters[1].GetId()).ProtoQuery(), + }, + "exclude all rules returns match none": { + rules: []*storage.SimpleAccessScope_Rules{ + {}, + }, + expectedQ: getMatchNoneQuery(), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result, err := BuildClusterOnlyAccessScopeQuery(tc.rules, clusters, namespaces) + assert.NoError(t, err) + protoassert.Equal(t, tc.expectedQ, result) + }) + } +} + func TestBuildEntityScopeQuery(t *testing.T) { testCases := []struct { name string diff --git a/central/reports/common/utils.go b/central/reports/common/utils.go index 8b777b934fffb..ebac8257908a8 100644 --- a/central/reports/common/utils.go +++ b/central/reports/common/utils.go @@ -49,6 +49,32 @@ func HasValidResourceScope(scope *storage.ResourceScope) bool { return true } +// BuildClusterOnlyAccessScopeQuery builds a v1 query that filters by cluster ID +// only. Currently nodes from partially included clusters (where only a subset of +// namespaces are accessible) are visible to users, so the scope is computed with +// both clusters and namespaces to match that behavior. +func BuildClusterOnlyAccessScopeQuery( + accessScopeRules []*storage.SimpleAccessScope_Rules, + clusters []effectiveaccessscope.Cluster, + namespaces []effectiveaccessscope.Namespace, +) (*v1.Query, error) { + if accessScopeRules == nil { + return search.EmptyQuery(), nil + } + scopeTree, err := computeScopeTree(accessScopeRules, clusters, namespaces) + if err != nil { + return nil, err + } + scopeQuery, err := sac.BuildClusterLevelSACQueryFilter(scopeTree) + if err != nil { + return nil, err + } + if scopeQuery == nil { + return search.EmptyQuery(), nil + } + return scopeQuery, nil +} + // BuildAccessScopeQuery builds v1 query for given access scope rules func BuildAccessScopeQuery( accessScopeRules []*storage.SimpleAccessScope_Rules, @@ -58,6 +84,25 @@ func BuildAccessScopeQuery( if accessScopeRules == nil { return search.EmptyQuery(), nil } + scopeTree, err := computeScopeTree(accessScopeRules, clusters, namespaces) + if err != nil { + return nil, err + } + scopeQuery, err := sac.BuildClusterNamespaceLevelSACQueryFilter(scopeTree) + if err != nil { + return nil, err + } + if scopeQuery == nil { + return search.EmptyQuery(), nil + } + return scopeQuery, nil +} + +func computeScopeTree( + accessScopeRules []*storage.SimpleAccessScope_Rules, + clusters []effectiveaccessscope.Cluster, + namespaces []effectiveaccessscope.Namespace, +) (*effectiveaccessscope.ScopeTree, error) { var scopeTree *effectiveaccessscope.ScopeTree for _, rules := range accessScopeRules { sct, err := effectiveaccessscope.ComputeEffectiveAccessScope(rules, clusters, namespaces, v1.ComputeEffectiveAccessScopeRequest_MINIMAL) @@ -70,12 +115,5 @@ func BuildAccessScopeQuery( scopeTree.Merge(sct) } } - scopeQuery, err := sac.BuildClusterNamespaceLevelSACQueryFilter(scopeTree) - if err != nil { - return nil, err - } - if scopeQuery == nil { - return search.EmptyQuery(), nil - } - return scopeQuery, nil + return scopeTree, nil } diff --git a/central/reports/scheduler/v2/reportgenerator/common_helpers.go b/central/reports/scheduler/v2/reportgenerator/common_helpers.go new file mode 100644 index 0000000000000..5ded30d95a420 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/common_helpers.go @@ -0,0 +1,74 @@ +package reportgenerator + +import ( + "bytes" + "context" + "time" + + "github.com/pkg/errors" + blobDS "github.com/stackrox/rox/central/blob/datastore" + "github.com/stackrox/rox/central/reports/common" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/notifiers" + "github.com/stackrox/rox/pkg/protocompat" + "github.com/stackrox/rox/pkg/retry" + "github.com/stackrox/rox/pkg/utils" +) + +// RetryableSendReportResults sends report results via the notifier with retry logic. +func RetryableSendReportResults(ctx context.Context, reportNotifier notifiers.ReportNotifier, mailingList []string, + zippedCSVData *bytes.Buffer, emailSubject, emailBody, baseFilename string) error { + return retry.WithRetry(func() error { + return reportNotifier.ReportNotify(ctx, zippedCSVData, mailingList, emailSubject, emailBody, baseFilename) + }, + retry.OnlyRetryableErrors(), + retry.Tries(3), + retry.BetweenAttempts(func(previousAttempt int) { + wait := time.Duration(previousAttempt * previousAttempt * 100) + time.Sleep(wait * time.Millisecond) + }), + ) +} + +// SaveReportData saves zipped CSV report data to blob storage. +func SaveReportData(ctx context.Context, blobStore blobDS.Datastore, configID, reportID string, data *bytes.Buffer) error { + if data == nil { + return errors.Errorf("No data found for report config %q and id %q", configID, reportID) + } + b := &storage.Blob{ + Name: common.GetReportBlobPath(configID, reportID), + LastUpdated: protocompat.TimestampNow(), + ModifiedTime: protocompat.TimestampNow(), + Length: int64(data.Len()), + } + return blobStore.Upsert(ctx, b, data) +} + +// UpdateReportStatus updates the run state of a report snapshot. +func UpdateReportStatus(ctx context.Context, snapshotStore reportSnapshotDS.DataStore, snapshot *storage.ReportSnapshot, status storage.ReportStatus_RunState) error { + snapshot.ReportStatus.RunState = status + return snapshotStore.UpdateReportSnapshot(ctx, snapshot) +} + +// LogAndUpsertError logs the error and updates the report snapshot status to FAILURE. +// requestCtx is used to check for user cancellation; statusCtx is used for the DB update +// (should be an all-access context since requestCtx may be cancelled). +func LogAndUpsertError(requestCtx, statusCtx context.Context, snapshotStore reportSnapshotDS.DataStore, reportErr error, req *ReportRequest) { + if req.ReportSnapshot == nil || req.ReportSnapshot.GetReportStatus() == nil { + utils.Should(errors.New("Request does not have non-nil report snapshot with a non-nil report status")) + return + } + if errors.Is(context.Cause(requestCtx), ErrUserCancelled) { + log.Infof("Report for config '%s' was cancelled by user", req.ReportSnapshot.GetName()) + req.ReportSnapshot.ReportStatus.ErrorMsg = ErrUserCancelled.Error() + } else if reportErr != nil { + log.Errorf("Error while running report for config '%s': %s", req.ReportSnapshot.GetName(), reportErr) + req.ReportSnapshot.ReportStatus.ErrorMsg = reportErr.Error() + } + req.ReportSnapshot.ReportStatus.CompletedAt = protocompat.TimestampNow() + if err := UpdateReportStatus(statusCtx, snapshotStore, req.ReportSnapshot, storage.ReportStatus_FAILURE); err != nil { + log.Errorf("Error changing report status to FAILURE for report config '%s', report ID '%s': %s", + req.ReportSnapshot.GetName(), req.ReportSnapshot.GetReportId(), err) + } +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/query_builder.go b/central/reports/scheduler/v2/reportgenerator/node/query_builder.go new file mode 100644 index 0000000000000..007d7b4cf7424 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/query_builder.go @@ -0,0 +1,84 @@ +package node + +import ( + "github.com/pkg/errors" + "github.com/stackrox/rox/central/reports/common" + v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/sac/effectiveaccessscope" + "github.com/stackrox/rox/pkg/search" +) + +type queryBuilder struct { + filters *storage.NodeVulnerabilityReportFilters + entityScope *storage.EntityScope +} + +func newQueryBuilder(entityScope *storage.EntityScope, filters *storage.NodeVulnerabilityReportFilters) *queryBuilder { + return &queryBuilder{ + filters: filters, + entityScope: entityScope, + } +} + +func (q *queryBuilder) buildQuery(clusters []effectiveaccessscope.Cluster, namespaces []effectiveaccessscope.Namespace) (*v1.Query, error) { + var conjuncts []*v1.Query + + if q.entityScope != nil { + scopeQuery, err := q.buildEntityScopeQuery() + if err != nil { + return nil, err + } + conjuncts = append(conjuncts, scopeQuery) + } + + accessScopeQuery, err := common.BuildClusterOnlyAccessScopeQuery(q.filters.GetAccessScopeRules(), clusters, namespaces) + if err != nil { + return nil, err + } + conjuncts = append(conjuncts, accessScopeQuery) + + cveFilterQuery, err := search.ParseQuery(q.filters.GetQuery(), search.MatchAllIfEmpty()) + if err != nil { + return nil, err + } + conjuncts = append(conjuncts, cveFilterQuery) + + return search.ConjunctionQuery(conjuncts...), nil +} + +func (q *queryBuilder) buildEntityScopeQuery() (*v1.Query, error) { + var conjuncts []*v1.Query + for _, rule := range q.entityScope.GetRules() { + if len(rule.GetValues()) == 0 { + continue + } + fieldLabel, err := nodeEntityScopeRuleToFieldLabel(rule) + if err != nil { + return nil, err + } + if fieldLabel == search.ClusterLabel { + conjuncts = append(conjuncts, common.MapFieldQuery(fieldLabel, rule)) + } else { + conjuncts = append(conjuncts, common.ScalarFieldQuery(fieldLabel, rule)) + } + } + + if len(conjuncts) == 0 { + return search.EmptyQuery(), nil + } + return search.ConjunctionQuery(conjuncts...), nil +} + +func nodeEntityScopeRuleToFieldLabel(rule *storage.EntityScopeRule) (search.FieldLabel, error) { + if rule.GetEntity() != storage.EntityType_ENTITY_TYPE_CLUSTER { + return "", errors.Errorf("unsupported entity type %s for node reports; only ENTITY_TYPE_CLUSTER is allowed", rule.GetEntity()) + } + switch rule.GetField() { + case storage.EntityField_FIELD_NAME: + return search.Cluster, nil + case storage.EntityField_FIELD_LABEL: + return search.ClusterLabel, nil + } + return "", errors.Errorf("unsupported field %s for cluster entity scope", rule.GetField()) +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/query_builder_test.go b/central/reports/scheduler/v2/reportgenerator/node/query_builder_test.go new file mode 100644 index 0000000000000..cbd75121835fe --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/query_builder_test.go @@ -0,0 +1,277 @@ +package node + +import ( + "testing" + + v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/protoassert" + "github.com/stackrox/rox/pkg/sac/effectiveaccessscope" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/assert" +) + +var testClusters = []effectiveaccessscope.Cluster{ + &storage.Cluster{ + Id: uuid.NewV4().String(), + Name: "prod-us", + }, + &storage.Cluster{ + Id: uuid.NewV4().String(), + Name: "prod-eu", + }, +} + +func TestNodeEntityScopeRuleToFieldLabel(t *testing.T) { + testCases := map[string]struct { + rule *storage.EntityScopeRule + expected search.FieldLabel + hasError bool + }{ + "Cluster name returns search.Cluster": { + rule: &storage.EntityScopeRule{ + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + }, + expected: search.Cluster, + }, + "Cluster label returns search.ClusterLabel": { + rule: &storage.EntityScopeRule{ + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_LABEL, + }, + expected: search.ClusterLabel, + }, + "Namespace entity is not allowed": { + rule: &storage.EntityScopeRule{ + Entity: storage.EntityType_ENTITY_TYPE_NAMESPACE, + Field: storage.EntityField_FIELD_NAME, + }, + hasError: true, + }, + "Deployment entity is not allowed": { + rule: &storage.EntityScopeRule{ + Entity: storage.EntityType_ENTITY_TYPE_DEPLOYMENT, + Field: storage.EntityField_FIELD_NAME, + }, + hasError: true, + }, + "Cluster annotation is not allowed": { + rule: &storage.EntityScopeRule{ + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ANNOTATION, + }, + hasError: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result, err := nodeEntityScopeRuleToFieldLabel(tc.rule) + if tc.hasError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestBuildEntityScopeQuery(t *testing.T) { + testCases := map[string]struct { + scope *storage.EntityScope + expected *v1.Query + hasError bool + }{ + "Empty rules returns empty query": { + scope: &storage.EntityScope{}, + expected: search.EmptyQuery(), + }, + "Single cluster name rule": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "prod-us", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + expected: search.NewQueryBuilder().AddExactMatches(search.Cluster, "prod-us").ProtoQuery(), + }, + "Multiple cluster name values are ORed": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "prod-us", MatchType: storage.MatchType_EXACT}, + {Value: "prod-eu", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + expected: search.DisjunctionQuery( + search.NewQueryBuilder().AddExactMatches(search.Cluster, "prod-us").ProtoQuery(), + search.NewQueryBuilder().AddExactMatches(search.Cluster, "prod-eu").ProtoQuery(), + ), + }, + "Cluster label rule uses map query": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_LABEL, + Values: []*storage.RuleValue{ + {Value: "env=prod", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + expected: search.NewQueryBuilder().AddMapQuery(search.ClusterLabel, `"env"`, `"prod"`).ProtoQuery(), + }, + "Regex match type": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "prod-.*", MatchType: storage.MatchType_REGEX}, + }, + }, + }, + }, + expected: search.NewQueryBuilder().AddRegexes(search.Cluster, "prod-.*").ProtoQuery(), + }, + "Multiple rules are ANDed": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "prod-us", MatchType: storage.MatchType_EXACT}, + }, + }, + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_LABEL, + Values: []*storage.RuleValue{ + {Value: "tier=premium", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + expected: search.ConjunctionQuery( + search.NewQueryBuilder().AddExactMatches(search.Cluster, "prod-us").ProtoQuery(), + search.NewQueryBuilder().AddMapQuery(search.ClusterLabel, `"tier"`, `"premium"`).ProtoQuery(), + ), + }, + "Rule with empty values is skipped": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{}, + }, + }, + }, + expected: search.EmptyQuery(), + }, + "Namespace rule returns error": { + scope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_NAMESPACE, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "default", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + hasError: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + qb := &queryBuilder{entityScope: tc.scope} + result, err := qb.buildEntityScopeQuery() + if tc.hasError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + protoassert.Equal(t, tc.expected, result) + }) + } +} + +func TestBuildQuery(t *testing.T) { + testCases := map[string]struct { + entityScope *storage.EntityScope + filters *storage.NodeVulnerabilityReportFilters + hasError bool + }{ + "View-based (nil entity scope) builds without error": { + entityScope: nil, + filters: &storage.NodeVulnerabilityReportFilters{}, + }, + "Config-based with entity scope builds without error": { + entityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "prod-us", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + filters: &storage.NodeVulnerabilityReportFilters{}, + }, + "Config-based with query filter": { + entityScope: &storage.EntityScope{}, + filters: &storage.NodeVulnerabilityReportFilters{ + Query: "CVE:CVE-2021-1234", + }, + }, + "Invalid entity scope returns error": { + entityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_NAMESPACE, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "default", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + }, + filters: &storage.NodeVulnerabilityReportFilters{}, + hasError: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + qb := newQueryBuilder(tc.entityScope, tc.filters) + result, err := qb.buildQuery(testClusters, nil) + if tc.hasError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.NotNil(t, result) + }) + } +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/report_gen.go b/central/reports/scheduler/v2/reportgenerator/node/report_gen.go new file mode 100644 index 0000000000000..3e1ead3cb5235 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/report_gen.go @@ -0,0 +1,33 @@ +package node + +import ( + blobDS "github.com/stackrox/rox/central/blob/datastore" + clusterDS "github.com/stackrox/rox/central/cluster/datastore" + nodeCVEDS "github.com/stackrox/rox/central/cve/node/datastore" + namespaceDS "github.com/stackrox/rox/central/namespace/datastore" + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/pkg/notifier" + "github.com/stackrox/rox/pkg/postgres" +) + +// New creates a new NodeReportGenerator instance. +func New( + db postgres.DB, + reportSnapshotStore reportSnapshotDS.DataStore, + notificationProcessor notifier.Processor, + blobDatastore blobDS.Datastore, + clusterDatastore clusterDS.DataStore, + namespaceDatastore namespaceDS.DataStore, + nodeCVEDatastore nodeCVEDS.DataStore, +) reportGen.ReportGenerator { + return &nodeReportGeneratorImpl{ + reportSnapshotStore: reportSnapshotStore, + notificationProcessor: notificationProcessor, + blobStore: blobDatastore, + clusterDatastore: clusterDatastore, + namespaceDatastore: namespaceDatastore, + nodeCVEDatastore: nodeCVEDatastore, + db: db, + } +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go new file mode 100644 index 0000000000000..c6b40ecbc6684 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go @@ -0,0 +1,265 @@ +package node + +import ( + "bytes" + "context" + + "github.com/pkg/errors" + clusterDS "github.com/stackrox/rox/central/cluster/datastore" + "github.com/stackrox/rox/central/convert/storagetoeffectiveaccessscope" + nodeCVEDS "github.com/stackrox/rox/central/cve/node/datastore" + namespaceDS "github.com/stackrox/rox/central/namespace/datastore" + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/errorhelpers" + "github.com/stackrox/rox/pkg/notifier" + "github.com/stackrox/rox/pkg/notifiers" + "github.com/stackrox/rox/pkg/postgres" + pkgSchema "github.com/stackrox/rox/pkg/postgres/schema" + "github.com/stackrox/rox/pkg/protocompat" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/effectiveaccessscope" + "github.com/stackrox/rox/pkg/search" + pgSearch "github.com/stackrox/rox/pkg/search/postgres" + "github.com/stackrox/rox/pkg/set" + + blobDS "github.com/stackrox/rox/central/blob/datastore" +) + +var ( + nodeReportGenCtx = sac.WithAllAccess(context.Background()) + + nodeQueryParts = &reportGen.ReportQueryParts{ + Schema: pkgSchema.NodeCvesSchema, + Selects: []*v1.QuerySelect{ + search.NewQuerySelect(search.Cluster).Proto(), + search.NewQuerySelect(search.Node).Proto(), + search.NewQuerySelect(search.Component).Proto(), + search.NewQuerySelect(search.ComponentVersion).Proto(), + search.NewQuerySelect(search.CVEID).Proto(), + search.NewQuerySelect(search.CVE).Proto(), + search.NewQuerySelect(search.Fixable).Proto(), + search.NewQuerySelect(search.FixedBy).Proto(), + search.NewQuerySelect(search.Severity).Proto(), + search.NewQuerySelect(search.CVSS).Proto(), + search.NewQuerySelect(search.CVECreatedTime).Proto(), + }, + Pagination: search.NewPagination(). + Limit(int32(env.ReportMaxRows.IntegerSetting())). + Offset(0). + AddSortOption(search.NewSortOption(search.Cluster)). + AddSortOption(search.NewSortOption(search.Node)). + Proto(), + } +) + +type nodeReportGeneratorImpl struct { + reportSnapshotStore reportSnapshotDS.DataStore + notificationProcessor notifier.Processor + blobStore blobDS.Datastore + clusterDatastore clusterDS.DataStore + namespaceDatastore namespaceDS.DataStore + nodeCVEDatastore nodeCVEDS.DataStore + db postgres.DB +} + +func (rg *nodeReportGeneratorImpl) ProcessReportRequest(ctx context.Context, req *reportGen.ReportRequest) { + ctx = sac.WithAllAccess(ctx) + + if err := reportGen.ValidateReportRequest(req); err != nil { + reportGen.LogAndUpsertError(ctx, nodeReportGenCtx, rg.reportSnapshotStore, errors.Wrap(err, "Invalid report request"), req) + return + } + + if err := reportGen.UpdateReportStatus(nodeReportGenCtx, rg.reportSnapshotStore, req.ReportSnapshot, storage.ReportStatus_PREPARING); err != nil { + reportGen.LogAndUpsertError(ctx, nodeReportGenCtx, rg.reportSnapshotStore, errors.Wrap(err, "Error changing report status to PREPARING"), req) + return + } + + if err := rg.generateReportAndNotify(ctx, req); err != nil { + reportGen.LogAndUpsertError(ctx, nodeReportGenCtx, rg.reportSnapshotStore, err, req) + return + } + + if req.ReportSnapshot.GetReportStatus().GetReportNotificationMethod() == storage.ReportStatus_EMAIL { + if err := reportGen.UpdateReportStatus(nodeReportGenCtx, rg.reportSnapshotStore, req.ReportSnapshot, storage.ReportStatus_DELIVERED); err != nil { + reportGen.LogAndUpsertError(ctx, nodeReportGenCtx, rg.reportSnapshotStore, errors.Wrap(err, "Error changing report status to DELIVERED"), req) + } + } +} + +func (rg *nodeReportGeneratorImpl) generateReportAndNotify(ctx context.Context, req *reportGen.ReportRequest) error { + if err := ctx.Err(); err != nil { + return err + } + + cveResponses, numResults, err := rg.getReportData(ctx, req.ReportSnapshot) + if err != nil { + return err + } + + if err := ctx.Err(); err != nil { + return err + } + + zippedCSVData, err := generateCSV(cveResponses, req.ReportSnapshot.GetName()) + if err != nil { + return err + } + + req.ReportSnapshot.ReportStatus.CompletedAt = protocompat.TimestampNow() + if err := reportGen.UpdateReportStatus(nodeReportGenCtx, rg.reportSnapshotStore, req.ReportSnapshot, storage.ReportStatus_GENERATED); err != nil { + return errors.Wrap(err, "Error changing report status to GENERATED") + } + if err := ctx.Err(); err != nil { + return err + } + + switch req.ReportSnapshot.GetReportStatus().GetReportNotificationMethod() { + case storage.ReportStatus_DOWNLOAD: + parentDir := req.ReportSnapshot.GetReportConfigurationId() + if req.ReportSnapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_VIEW_BASED { + parentDir = "view-based-report" + } + if err := reportGen.SaveReportData(nodeReportGenCtx, rg.blobStore, parentDir, req.ReportSnapshot.GetReportId(), zippedCSVData); err != nil { + return errors.Wrap(err, "error persisting blob") + } + case storage.ReportStatus_EMAIL: + return rg.sendEmailNotification(req, zippedCSVData, numResults) + } + return nil +} + +func (rg *nodeReportGeneratorImpl) sendEmailNotification(req *reportGen.ReportRequest, zippedCSVData *bytes.Buffer, numResults int) error { + defaultEmailSubject, err := reportGen.FormatEmailSubject(reportGen.NodeDefaultEmailSubjectTemplate, req.ReportSnapshot) + if err != nil { + return errors.Wrap(err, "Error generating email subject") + } + + templateStr := reportGen.NodeDefaultEmailBodyTemplate + if numResults == 0 { + zippedCSVData = nil + templateStr = reportGen.NodeDefaultNoVulnsEmailBodyTemplate + } + + defaultEmailBody, err := reportGen.FormatEmailBody(templateStr) + if err != nil { + return errors.Wrap(err, "Error generating email body") + } + + configDetailsHTML, err := reportGen.FormatNodeReportConfigDetails(req.ReportSnapshot, numResults) + if err != nil { + return errors.Wrap(err, "Error adding report config details") + } + + errorList := errorhelpers.NewErrorList("Error sending email notifications: ") + for _, notifierSnap := range req.ReportSnapshot.GetNotifiers() { + nf := rg.notificationProcessor.GetNotifier(nodeReportGenCtx, notifierSnap.GetEmailConfig().GetNotifierId()) + reportNotifier, ok := nf.(notifiers.ReportNotifier) + if !ok { + errorList.AddError(errors.Errorf("incorrect type of notifier '%s'", notifierSnap.GetEmailConfig().GetNotifierId())) + continue + } + customBody := notifierSnap.GetEmailConfig().GetCustomBody() + emailBody := defaultEmailBody + if customBody != "" { + emailBody = customBody + } + customSubject := notifierSnap.GetEmailConfig().GetCustomSubject() + emailSubject := defaultEmailSubject + if customSubject != "" { + emailSubject = customSubject + } + emailBodyWithConfigDetails := reportGen.AddReportConfigDetails(emailBody, configDetailsHTML) + reportName := req.ReportSnapshot.GetName() + if err := reportGen.RetryableSendReportResults(nodeReportGenCtx, reportNotifier, notifierSnap.GetEmailConfig().GetMailingLists(), + zippedCSVData, emailSubject, emailBodyWithConfigDetails, reportName); err != nil { + errorList.AddError(errors.Errorf("Error sending email for notifier '%s': %s", + notifierSnap.GetEmailConfig().GetNotifierId(), err)) + } + } + if !errorList.Empty() { + return errorList.ToError() + } + return nil +} + +func (rg *nodeReportGeneratorImpl) getReportData(ctx context.Context, snap *storage.ReportSnapshot) ([]*NodeCVEQueryResponse, int, error) { + clusters, namespaces, err := rg.getClustersAndNamespacesForSAC(ctx) + if err != nil { + return nil, 0, err + } + + var entityScope *storage.EntityScope + if snap.GetReportStatus().GetReportRequestType() != storage.ReportStatus_VIEW_BASED { + entityScope = snap.GetResourceScope().GetEntityScope() + } + + qb := newQueryBuilder(entityScope, snap.GetNodeVulnReportFilters()) + query, err := qb.buildQuery(clusters, namespaces) + if err != nil { + return nil, 0, errors.Wrap(err, "error building node report query") + } + + return rg.executeQuery(ctx, query) +} + +func (rg *nodeReportGeneratorImpl) executeQuery(ctx context.Context, query *v1.Query) ([]*NodeCVEQueryResponse, int, error) { + query.Pagination = nodeQueryParts.Pagination + query.Selects = nodeQueryParts.Selects + + var cveResponses []*NodeCVEQueryResponse + cveIDs := set.NewStringSet() + err := pgSearch.RunSelectRequestForSchemaFn[NodeCVEQueryResponse](ctx, rg.db, + nodeQueryParts.Schema, query, func(r *NodeCVEQueryResponse) error { + cveResponses = append(cveResponses, r) + if r.GetCVEID() != "" { + cveIDs.Add(r.GetCVEID()) + } + return nil + }) + if err != nil { + return nil, 0, errors.Wrap(err, "Failed to collect node CVE report data") + } + + cveResponses, err = rg.withCVEReferenceLinks(ctx, cveResponses, cveIDs) + if err != nil { + return nil, 0, err + } + + return cveResponses, len(cveResponses), nil +} + +func (rg *nodeReportGeneratorImpl) getClustersAndNamespacesForSAC(ctx context.Context) ([]effectiveaccessscope.Cluster, []effectiveaccessscope.Namespace, error) { + allClusters, err := rg.clusterDatastore.GetClusters(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "error fetching clusters to build report query") + } + allNamespaces, err := rg.namespaceDatastore.GetAllNamespaces(ctx) + if err != nil { + return nil, nil, errors.Wrap(err, "error fetching namespaces to build report query") + } + return storagetoeffectiveaccessscope.Clusters(allClusters), storagetoeffectiveaccessscope.Namespaces(allNamespaces), nil +} + +func (rg *nodeReportGeneratorImpl) withCVEReferenceLinks(ctx context.Context, responses []*NodeCVEQueryResponse, cveIDs set.StringSet) ([]*NodeCVEQueryResponse, error) { + nodeCVEs, err := rg.nodeCVEDatastore.GetBatch(ctx, cveIDs.AsSlice()) + if err != nil { + return nil, err + } + + cveRefLinks := make(map[string]string, len(nodeCVEs)) + for _, cve := range nodeCVEs { + cveRefLinks[cve.GetId()] = cve.GetCveBaseInfo().GetLink() + } + + for _, res := range responses { + if link, ok := cveRefLinks[res.GetCVEID()]; ok { + res.Link = link + } + } + return responses, nil +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl_test.go b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl_test.go new file mode 100644 index 0000000000000..81a0c959e7a1c --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl_test.go @@ -0,0 +1,387 @@ +//go:build sql_integration + +package node + +import ( + "context" + "fmt" + "testing" + "time" + + clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" + nodeCVEDS "github.com/stackrox/rox/central/cve/node/datastore" + namespaceDSMocks "github.com/stackrox/rox/central/namespace/datastore/mocks" + nodeDS "github.com/stackrox/rox/central/node/datastore" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/fixtures" + "github.com/stackrox/rox/pkg/postgres/pgtest" + postgresSchema "github.com/stackrox/rox/pkg/postgres/schema" + "github.com/stackrox/rox/pkg/protocompat" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +type nodeReportData struct { + clusterNames []string + nodeNames []string + componentNames []string + cveNames []string +} + +func TestNodeReportGenerator(t *testing.T) { + suite.Run(t, new(NodeReportGeneratorTestSuite)) +} + +type NodeReportGeneratorTestSuite struct { + suite.Suite + + ctx context.Context + testDB *pgtest.TestPostgres + clusterDatastore *clusterDSMocks.MockDataStore + namespaceDatastore *namespaceDSMocks.MockDataStore + nodeDatastore nodeDS.DataStore + nodeCVEDatastore nodeCVEDS.DataStore + reportGenerator *nodeReportGeneratorImpl + + clusters []*storage.Cluster + namespaces []*storage.NamespaceMetadata +} + +func (s *NodeReportGeneratorTestSuite) SetupSuite() { + s.ctx = sac.WithAllAccess(context.Background()) + mockCtrl := gomock.NewController(s.T()) + s.testDB = pgtest.ForT(s.T()) + + s.clusterDatastore = clusterDSMocks.NewMockDataStore(mockCtrl) + s.namespaceDatastore = namespaceDSMocks.NewMockDataStore(mockCtrl) + + s.nodeDatastore = nodeDS.GetTestPostgresDataStore(s.T(), s.testDB.DB) + var err error + s.nodeCVEDatastore, err = nodeCVEDS.GetTestPostgresDataStore(s.T(), s.testDB.DB) + s.Require().NoError(err) + + s.clusters = []*storage.Cluster{ + {Id: uuid.NewV4().String(), Name: "c1"}, + {Id: uuid.NewV4().String(), Name: "c2"}, + } + s.namespaces = []*storage.NamespaceMetadata{ + {Id: uuid.NewV4().String(), Name: "ns1", ClusterId: s.clusters[0].GetId(), ClusterName: "c1"}, + {Id: uuid.NewV4().String(), Name: "ns2", ClusterId: s.clusters[1].GetId(), ClusterName: "c2"}, + } + s.clusterDatastore.EXPECT().GetClusters(gomock.Any()). + Return(s.clusters, nil).AnyTimes() + s.namespaceDatastore.EXPECT().GetAllNamespaces(gomock.Any()). + Return(s.namespaces, nil).AnyTimes() + + nodes := s.testNodes() + for _, node := range nodes { + s.Require().NoError(s.nodeDatastore.UpsertNode(s.ctx, node)) + } + + s.reportGenerator = &nodeReportGeneratorImpl{ + clusterDatastore: s.clusterDatastore, + namespaceDatastore: s.namespaceDatastore, + nodeCVEDatastore: s.nodeCVEDatastore, + db: s.testDB.DB, + } +} + +func (s *NodeReportGeneratorTestSuite) TearDownSuite() { + s.truncateTable(postgresSchema.NodeCvesTableName) + s.truncateTable(postgresSchema.NodeComponentsCvesEdgesTableName) + s.truncateTable(postgresSchema.NodeComponentsTableName) + s.truncateTable(postgresSchema.NodeComponentEdgesTableName) + s.truncateTable(postgresSchema.NodesTableName) +} + +func (s *NodeReportGeneratorTestSuite) truncateTable(name string) { + sql := fmt.Sprintf("TRUNCATE %s CASCADE", name) + _, err := s.testDB.Exec(s.ctx, sql) + s.NoError(err) +} + +func (s *NodeReportGeneratorTestSuite) testNodes() []*storage.Node { + t, err := protocompat.ConvertTimeToTimestampOrError(time.Unix(0, 1000)) + s.Require().NoError(err) + + var nodes []*storage.Node + for _, cluster := range s.clusters { + for i := range 2 { + nodeName := fmt.Sprintf("%s_node%d", cluster.GetName(), i) + node := &storage.Node{ + Id: uuid.NewV4().String(), + Name: nodeName, + ClusterId: cluster.GetId(), + ClusterName: cluster.GetName(), + Scan: &storage.NodeScan{ + ScanTime: protocompat.TimestampNow(), + OperatingSystem: "linux", + Components: []*storage.EmbeddedNodeScanComponent{ + { + Name: fmt.Sprintf("%s_comp", nodeName), + Version: "1.0", + Vulnerabilities: []*storage.NodeVulnerability{ + { + CveBaseInfo: &storage.CVEInfo{ + Cve: fmt.Sprintf("CVE-critical-%s_comp", nodeName), + CreatedAt: t, + }, + Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, + Cvss: 9.0, + SetFixedBy: &storage.NodeVulnerability_FixedBy{ + FixedBy: "1.1", + }, + }, + { + CveBaseInfo: &storage.CVEInfo{ + Cve: fmt.Sprintf("CVE-low-%s_comp", nodeName), + CreatedAt: t, + }, + Severity: storage.VulnerabilitySeverity_LOW_VULNERABILITY_SEVERITY, + Cvss: 2.0, + }, + }, + }, + }, + }, + } + nodes = append(nodes, node) + } + } + return nodes +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_AllNodes() { + snap := s.testNodeReportSnapshot(nil, "", nil, storage.ReportStatus_ON_DEMAND) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1", "c2"}, collected.clusterNames) + s.ElementsMatch( + []string{"c1_node0", "c1_node1", "c2_node0", "c2_node1"}, + collected.nodeNames, + ) + s.ElementsMatch( + []string{"c1_node0_comp", "c1_node1_comp", "c2_node0_comp", "c2_node1_comp"}, + collected.componentNames, + ) + s.ElementsMatch( + []string{ + "CVE-critical-c1_node0_comp", "CVE-low-c1_node0_comp", + "CVE-critical-c1_node1_comp", "CVE-low-c1_node1_comp", + "CVE-critical-c2_node0_comp", "CVE-low-c2_node0_comp", + "CVE-critical-c2_node1_comp", "CVE-low-c2_node1_comp", + }, + collected.cveNames, + ) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_EntityScope_SingleCluster() { + entityScope := &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "c1", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + } + scopeRules := []*storage.SimpleAccessScope_Rules{ + {IncludedClusters: []string{"c1"}}, + } + snap := s.testNodeReportSnapshot(entityScope, "", scopeRules, storage.ReportStatus_ON_DEMAND) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1"}, collected.clusterNames) + s.ElementsMatch([]string{"c1_node0", "c1_node1"}, collected.nodeNames) + s.ElementsMatch([]string{"c1_node0_comp", "c1_node1_comp"}, collected.componentNames) + s.ElementsMatch( + []string{ + "CVE-critical-c1_node0_comp", "CVE-low-c1_node0_comp", + "CVE-critical-c1_node1_comp", "CVE-low-c1_node1_comp", + }, + collected.cveNames, + ) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_PartiallyIncludedCluster() { + // User has namespace-only access (no cluster-level rule) for c1. + // Nodes from c1 should still be visible because the cluster is partially included. + scopeRules := []*storage.SimpleAccessScope_Rules{ + { + IncludedNamespaces: []*storage.SimpleAccessScope_Rules_Namespace{ + {ClusterName: "c1", NamespaceName: "ns1"}, + }, + }, + } + snap := s.testNodeReportSnapshot(nil, "", scopeRules, storage.ReportStatus_VIEW_BASED) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1"}, collected.clusterNames) + s.ElementsMatch([]string{"c1_node0", "c1_node1"}, collected.nodeNames) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_EntityScope_WithCVSSFilter() { + entityScope := &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "c1", MatchType: storage.MatchType_EXACT}, + }, + }, + }, + } + scopeRules := []*storage.SimpleAccessScope_Rules{ + {IncludedClusters: []string{"c1"}}, + } + snap := s.testNodeReportSnapshot(entityScope, "CVSS:>=7.0", scopeRules, storage.ReportStatus_ON_DEMAND) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1"}, collected.clusterNames) + s.ElementsMatch([]string{"c1_node0", "c1_node1"}, collected.nodeNames) + s.ElementsMatch([]string{"c1_node0_comp", "c1_node1_comp"}, collected.componentNames) + s.ElementsMatch( + []string{ + "CVE-critical-c1_node0_comp", + "CVE-critical-c1_node1_comp", + }, + collected.cveNames, + ) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_ViewBased() { + scopeRules := []*storage.SimpleAccessScope_Rules{ + {IncludedClusters: []string{"c1"}}, + } + snap := s.testNodeReportSnapshot(nil, "CVSS:>=7.0", scopeRules, storage.ReportStatus_VIEW_BASED) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1"}, collected.clusterNames) + s.ElementsMatch([]string{"c1_node0", "c1_node1"}, collected.nodeNames) + s.ElementsMatch([]string{"c1_node0_comp", "c1_node1_comp"}, collected.componentNames) + s.ElementsMatch( + []string{ + "CVE-critical-c1_node0_comp", + "CVE-critical-c1_node1_comp", + }, + collected.cveNames, + ) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_ClusterRegexScope() { + entityScope := &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_NAME, + Values: []*storage.RuleValue{ + {Value: "c.*", MatchType: storage.MatchType_REGEX}, + }, + }, + }, + } + snap := s.testNodeReportSnapshot(entityScope, "", nil, storage.ReportStatus_ON_DEMAND) + cveResponses, _, err := s.reportGenerator.getReportData(s.ctx, snap) + s.NoError(err) + + collected := collectNodeReportData(cveResponses) + s.ElementsMatch([]string{"c1", "c2"}, collected.clusterNames) + s.ElementsMatch( + []string{"c1_node0", "c1_node1", "c2_node0", "c2_node1"}, + collected.nodeNames, + ) + s.ElementsMatch( + []string{"c1_node0_comp", "c1_node1_comp", "c2_node0_comp", "c2_node1_comp"}, + collected.componentNames, + ) + s.ElementsMatch( + []string{ + "CVE-critical-c1_node0_comp", "CVE-low-c1_node0_comp", + "CVE-critical-c1_node1_comp", "CVE-low-c1_node1_comp", + "CVE-critical-c2_node0_comp", "CVE-low-c2_node0_comp", + "CVE-critical-c2_node1_comp", "CVE-low-c2_node1_comp", + }, + collected.cveNames, + ) +} + +func (s *NodeReportGeneratorTestSuite) TestGetReportData_CancelledContext() { + snap := s.testNodeReportSnapshot(nil, "", nil, storage.ReportStatus_ON_DEMAND) + ctx, cancel := context.WithCancel(s.ctx) + cancel() + + _, _, err := s.reportGenerator.getReportData(ctx, snap) + s.Error(err) + s.ErrorIs(err, context.Canceled) +} + +func (s *NodeReportGeneratorTestSuite) testNodeReportSnapshot( + entityScope *storage.EntityScope, + query string, + scopeRules []*storage.SimpleAccessScope_Rules, + requestType storage.ReportStatus_RunMethod, +) *storage.ReportSnapshot { + snap := fixtures.GetReportSnapshot() + snap.Filter = &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: query, + AccessScopeRules: scopeRules, + }, + } + snap.ReportStatus = &storage.ReportStatus{ + ReportRequestType: requestType, + } + if entityScope != nil { + snap.ResourceScope = &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: entityScope, + }, + } + } else { + snap.ResourceScope = nil + } + snap.Collection = nil + return snap +} + +func collectNodeReportData(responses []*NodeCVEQueryResponse) *nodeReportData { + clusterNames := set.NewStringSet() + nodeNames := set.NewStringSet() + componentNames := set.NewStringSet() + cveNames := make([]string, 0, len(responses)) + + for _, res := range responses { + if res.GetCluster() != "" { + clusterNames.Add(res.GetCluster()) + } + if res.GetNode() != "" { + nodeNames.Add(res.GetNode()) + } + if res.GetComponent() != "" { + componentNames.Add(res.GetComponent()) + } + cveNames = append(cveNames, res.GetCVE()) + } + return &nodeReportData{ + clusterNames: clusterNames.AsSlice(), + nodeNames: nodeNames.AsSlice(), + componentNames: componentNames.AsSlice(), + cveNames: cveNames, + } +} diff --git a/central/reports/scheduler/v2/reportgenerator/node/singleton.go b/central/reports/scheduler/v2/reportgenerator/node/singleton.go new file mode 100644 index 0000000000000..20788359a00d3 --- /dev/null +++ b/central/reports/scheduler/v2/reportgenerator/node/singleton.go @@ -0,0 +1,41 @@ +package node + +import ( + blobDS "github.com/stackrox/rox/central/blob/datastore" + clusterDS "github.com/stackrox/rox/central/cluster/datastore" + nodeCVEDS "github.com/stackrox/rox/central/cve/node/datastore" + "github.com/stackrox/rox/central/globaldb" + namespaceDS "github.com/stackrox/rox/central/namespace/datastore" + notifierProcessor "github.com/stackrox/rox/central/notifier/processor" + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/sync" +) + +var ( + once sync.Once + rg reportGen.ReportGenerator +) + +func initialize() { + if !features.NodeVulnerabilityReports.Enabled() { + return + } + rg = New( + globaldb.GetPostgres(), + reportSnapshotDS.Singleton(), + notifierProcessor.Singleton(), + blobDS.Singleton(), + clusterDS.Singleton(), + namespaceDS.Singleton(), + nodeCVEDS.Singleton(), + ) +} + +// Singleton returns a singleton instance of the node ReportGenerator. +// Returns nil when the NodeVulnerabilityReports feature flag is disabled. +func Singleton() reportGen.ReportGenerator { + once.Do(initialize) + return rg +} diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index cc0de655a13e4..ce0a92bbd860c 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -36,13 +36,11 @@ import ( pkgSchema "github.com/stackrox/rox/pkg/postgres/schema" "github.com/stackrox/rox/pkg/postgres/walker" "github.com/stackrox/rox/pkg/protocompat" - "github.com/stackrox/rox/pkg/retry" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/sac/effectiveaccessscope" "github.com/stackrox/rox/pkg/search" pgSearch "github.com/stackrox/rox/pkg/search/postgres" "github.com/stackrox/rox/pkg/set" - "github.com/stackrox/rox/pkg/utils" ) var ( @@ -486,18 +484,7 @@ func (rg *reportGeneratorImpl) streamQueryToCSV( } func (rg *reportGeneratorImpl) saveReportData(ctx context.Context, configID, reportID string, data *bytes.Buffer) error { - if data == nil { - return errors.Errorf("No data found for report config %q and id %q", configID, reportID) - } - - // Store downloadable report in blob storage - b := &storage.Blob{ - Name: common.GetReportBlobPath(configID, reportID), - LastUpdated: protocompat.TimestampNow(), - ModifiedTime: protocompat.TimestampNow(), - Length: int64(data.Len()), - } - return rg.blobStore.Upsert(ctx, b, data) + return SaveReportData(ctx, rg.blobStore, configID, reportID, data) } func (rg *reportGeneratorImpl) getReportDataSQF(ctx context.Context, snap *storage.ReportSnapshot, collection *storage.ResourceCollection, @@ -673,16 +660,8 @@ func (rg *reportGeneratorImpl) buildReportQuery(ctx context.Context, snap *stora func (rg *reportGeneratorImpl) retryableSendReportResults(reportNotifier notifiers.ReportNotifier, mailingList []string, zippedCSVData *bytes.Buffer, emailSubject, emailBody, baseFilename string) error { - return retry.WithRetry(func() error { - return reportNotifier.ReportNotify(reportGenCtx, zippedCSVData, mailingList, emailSubject, emailBody, baseFilename) - }, - retry.OnlyRetryableErrors(), - retry.Tries(3), - retry.BetweenAttempts(func(previousAttempt int) { - wait := time.Duration(previousAttempt * previousAttempt * 100) - time.Sleep(wait * time.Millisecond) - }), - ) + return RetryableSendReportResults(reportGenCtx, reportNotifier, mailingList, + zippedCSVData, emailSubject, emailBody, baseFilename) } func (rg *reportGeneratorImpl) lastSuccessfulScheduledReportTime(snap *storage.ReportSnapshot) (time.Time, error) { @@ -755,30 +734,11 @@ func (rg *reportGeneratorImpl) withCVEReferenceLinks(ctx context.Context, imageC } func (rg *reportGeneratorImpl) updateReportStatus(snapshot *storage.ReportSnapshot, status storage.ReportStatus_RunState) error { - snapshot.ReportStatus.RunState = status - return rg.reportSnapshotStore.UpdateReportSnapshot(reportGenCtx, snapshot) + return UpdateReportStatus(reportGenCtx, rg.reportSnapshotStore, snapshot, status) } func (rg *reportGeneratorImpl) logAndUpsertError(ctx context.Context, reportErr error, req *ReportRequest) { - if req.ReportSnapshot == nil || req.ReportSnapshot.GetReportStatus() == nil { - utils.Should(errors.New("Request does not have non-nil report snapshot with a non-nil report status")) - return - } - if errors.Is(context.Cause(ctx), ErrUserCancelled) { - log.Infof("Report for config '%s' was cancelled by user", req.ReportSnapshot.GetName()) - req.ReportSnapshot.ReportStatus.ErrorMsg = ErrUserCancelled.Error() - } else if reportErr != nil { - log.Errorf("Error while running report for config '%s': %s", req.ReportSnapshot.GetName(), reportErr) - req.ReportSnapshot.ReportStatus.ErrorMsg = reportErr.Error() - } - req.ReportSnapshot.ReportStatus.CompletedAt = protocompat.TimestampNow() - // Use reportGenCtx for status update since the request context may be cancelled - err := rg.updateReportStatus(req.ReportSnapshot, storage.ReportStatus_FAILURE) - - if err != nil { - log.Errorf("Error changing report status to FAILURE for report config '%s', report ID '%s': %s", - req.ReportSnapshot.GetName(), req.ReportSnapshot.GetReportId(), err) - } + LogAndUpsertError(ctx, reportGenCtx, rg.reportSnapshotStore, reportErr, req) } func selectSchema() *walker.Schema { diff --git a/central/reports/scheduler/v2/reportgenerator/validate.go b/central/reports/scheduler/v2/reportgenerator/validate.go index bd5de5f06c746..bec14d35abd0c 100644 --- a/central/reports/scheduler/v2/reportgenerator/validate.go +++ b/central/reports/scheduler/v2/reportgenerator/validate.go @@ -2,6 +2,7 @@ package reportgenerator import ( "github.com/pkg/errors" + "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/errorhelpers" ) @@ -19,10 +20,15 @@ func ValidateReportRequest(request *ReportRequest) error { } else if request.ReportSnapshot.GetReportStatus() == nil { errorList.AddError(errors.New("Report request does not have a valid report snapshot with report status")) } - // only check resource scope is non nil if report snapshot is for config based vuln reports + // only check resource scope is non nil if report snapshot is for config based reports if request.ReportSnapshot.GetVulnReportFilters() != nil && request.ReportSnapshot.GetResourceScope() == nil { errorList.AddError(errors.New("Report request does not have a valid non-nil resource scope.")) } + if request.ReportSnapshot.GetNodeVulnReportFilters() != nil && + request.ReportSnapshot.GetReportStatus().GetReportRequestType() != storage.ReportStatus_VIEW_BASED && + request.ReportSnapshot.GetResourceScope() == nil { + errorList.AddError(errors.New("Report request does not have a valid non-nil resource scope.")) + } return errorList.ToError() } diff --git a/central/reports/scheduler/v2/singleton.go b/central/reports/scheduler/v2/singleton.go index a0abd532d8cfb..bafe399d2806e 100644 --- a/central/reports/scheduler/v2/singleton.go +++ b/central/reports/scheduler/v2/singleton.go @@ -4,6 +4,7 @@ import ( notifierDS "github.com/stackrox/rox/central/notifier/datastore" reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + nodeReportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator/node" reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" "github.com/stackrox/rox/central/reports/validation" collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" @@ -24,7 +25,7 @@ func initialize() { collectionDatastore, notifierDS.Singleton(), reportGen.Singleton(), - nil, + nodeReportGen.Singleton(), validation.Singleton(), ) } diff --git a/central/reports/service/v2/download.go b/central/reports/service/v2/download.go index 78644c66c95d6..71b28d12dca0a 100644 --- a/central/reports/service/v2/download.go +++ b/central/reports/service/v2/download.go @@ -110,7 +110,8 @@ func (h *downloadHandler) handle(w http.ResponseWriter, r *http.Request) { ) parentDir := rep.GetReportConfigurationId() - if rep.GetViewBasedVulnReportFilters() != nil { + // View-based report blobs are stored under "view-based-report" dir, not under a dir named after reportConfigID. + if status.GetReportRequestType() == storage.ReportStatus_VIEW_BASED { parentDir = "view-based-report" } _, exists, err := h.blobStore.Get(ctx, common.GetReportBlobPath(parentDir, id), buf) From 31ec7fc35044d10867845c83e04c57d606f4b736 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:20:23 -0500 Subject: [PATCH 169/638] ROX-36103: Node reporting view based jobs (#22568) --- .../ReportJob/MyLastJobStatus.test.tsx | 1 + .../ReportJob/ReportJobStatusFilter.test.tsx | 19 +- .../ReportJob/ReportJobStatusFilter.tsx | 24 +-- .../Schedules/components/ReportJobs.tsx | 18 +- .../NodeVulnerabilityReportsPage.tsx | 3 +- .../ViewBasedJobs/NodeViewBasedJobsPage.tsx | 170 ++++++++++++++++++ .../ViewBasedJobs/NodeViewBasedJobsTable.tsx | 89 +++++++++ .../VulnReports/ViewBasedReportsTab.tsx | 12 +- .../VulnReports/ViewBasedReportsTable.tsx | 4 +- .../src/services/NodeReportsService.ts | 47 ++++- .../src/services/ReportsService.types.ts | 14 +- 11 files changed, 340 insertions(+), 61 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsPage.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsTable.tsx diff --git a/ui/apps/platform/src/Components/ReportJob/MyLastJobStatus.test.tsx b/ui/apps/platform/src/Components/ReportJob/MyLastJobStatus.test.tsx index 13f6b1498b0d6..dd6da5d74df0a 100644 --- a/ui/apps/platform/src/Components/ReportJob/MyLastJobStatus.test.tsx +++ b/ui/apps/platform/src/Components/ReportJob/MyLastJobStatus.test.tsx @@ -8,6 +8,7 @@ import type { ReportStatus } from 'types/reportJob'; import MyLastJobStatus from './MyLastJobStatus'; const baseReportSnapshot: Omit = { + type: 'VULNERABILITY', reportConfigId: 'report-config-id-1', reportJobId: 'report-job-id-1', name: 'test-name-1', diff --git a/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.test.tsx b/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.test.tsx index 63f5271412e03..04f8a07da48c8 100644 --- a/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.test.tsx +++ b/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.test.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; -import ReportJobStatusFilter, { ensureReportJobStatuses } from './ReportJobStatusFilter'; +import ReportJobStatusFilter, { isReportJobStatus } from './ReportJobStatusFilter'; import type { ReportJobStatus } from './types'; const getCheckboxOption = (name: string) => { @@ -93,22 +93,17 @@ describe('ReportJobStatusFilter', () => { expect(checkboxOptionForError).toBeChecked(); }); - // Tests for the "ensureReportJobStatuses" helper function - describe('ensureReportJobStatuses', () => { + // Tests for the "isReportJobStatus" helper function + describe('isReportJobStatus', () => { test('should filter out all incorrect values', async () => { - expect(ensureReportJobStatuses('')).toEqual([]); - expect(ensureReportJobStatuses(['TEST', 'BLAH', 'LOADING'])).toEqual([]); + expect(['TEST', 'BLAH', 'LOADING'].filter(isReportJobStatus)).toEqual([]); }); test('should filter to show all correct values', async () => { expect( - ensureReportJobStatuses([ - 'PREPARING', - 'WAITING', - 'DOWNLOAD_GENERATED', - 'EMAIL_DELIVERED', - 'ERROR', - ]) + ['PREPARING', 'WAITING', 'DOWNLOAD_GENERATED', 'EMAIL_DELIVERED', 'ERROR'].filter( + isReportJobStatus + ) ).toEqual(['PREPARING', 'WAITING', 'DOWNLOAD_GENERATED', 'EMAIL_DELIVERED', 'ERROR']); }); }); diff --git a/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.tsx b/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.tsx index 07a9a1cdf3237..5d1f4764e6280 100644 --- a/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.tsx +++ b/ui/apps/platform/src/Components/ReportJob/ReportJobStatusFilter.tsx @@ -5,32 +5,10 @@ import CheckboxSelect from 'Components/CheckboxSelect'; import { reportJobStatusLabels, reportJobStatuses } from './types'; import type { ReportJobStatus } from './types'; -function isReportJobStatus(value: string): value is ReportJobStatus { +export function isReportJobStatus(value: string): value is ReportJobStatus { return value in reportJobStatuses; } -/** - * Ensures that the given search filter value is converted to an array of valid report job status values. - * - * Example: - * ensureReportJobStatuses(["WAITING", "PREPARING"]); // returns ["WAITING", "PREPARING"] - * ensureReportJobStatuses("WAITING"); // returns [] (since input is not an array) - * ensureReportJobStatuses(undefined); // returns [] - * - * @param searchFilterValue - The input value, which can be a string, an array of strings, or undefined. - * @returns {ReportJobStatus[]} - If the input is an array of strings, it filters the values that match valid "ReportJobStatus"s - * and returns them as an array. - * If the input is not an array or undefined, it returns an empty array. - */ -export function ensureReportJobStatuses( - searchFilterValue: string | string[] | undefined -): ReportJobStatus[] { - if (Array.isArray(searchFilterValue)) { - return searchFilterValue.filter((value) => isReportJobStatus(value)); - } - return []; -} - export type ReportJobStatusFilterProps = { availableStatuses: ReportJobStatus[]; selectedStatuses: ReportJobStatus[]; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ReportJobs.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ReportJobs.tsx index d95bf0396095f..e86a537a50f31 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ReportJobs.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ReportJobs.tsx @@ -31,7 +31,7 @@ import { getTableUIState } from 'utils/getTableUIState'; import useDeleteDownloadModal from 'Containers/Vulnerabilities/VulnerablityReporting/hooks/useDeleteDownloadModal'; import DeleteModal from 'Components/PatternFly/DeleteModal'; import ReportJobStatusFilter, { - ensureReportJobStatuses, + isReportJobStatus, } from 'Components/ReportJob/ReportJobStatusFilter'; import MyJobsFilter from 'Components/ReportJob/MyJobsFilter'; import type { ReportJobStatus } from 'Components/ReportJob/types'; @@ -45,7 +45,7 @@ function getConfigName(snapshot: ComplianceReportSnapshot) { return snapshot.name; } -function createQueryFromReportJobStatusFilters(jobStatusFilters: string[]) { +function createQueryFromReportJobStatusFilters(reportJobStatuses: ReportJobStatus[]) { const query: Record = { 'Compliance Report State': [], 'Compliance Report Notification Method': [], @@ -70,8 +70,6 @@ function createQueryFromReportJobStatusFilters(jobStatusFilters: string[]) { }, }; - const reportJobStatuses = ensureReportJobStatuses(jobStatusFilters); - reportJobStatuses.forEach((jobStatus) => { const queryMapping = jobStatusQueryMappings[jobStatus]; if (queryMapping) { @@ -103,7 +101,9 @@ function ReportJobs({ scanConfigId }: ReportJobsProps) { 'true', ]); - const reportJobStatusFilters = ensureStringArray(searchFilter['Compliance Report Job Status']); + const reportJobStatusFilters = ensureStringArray( + searchFilter['Compliance Report Job Status'] + ).filter(isReportJobStatus); const query = getRequestQueryStringForSearchFilter({ ...createQueryFromReportJobStatusFilters(reportJobStatusFilters), @@ -151,10 +151,8 @@ function ReportJobs({ scanConfigId }: ReportJobsProps) { const onReportJobStatusFilterChange = (_checked: boolean, selectedStatus: ReportJobStatus) => { const isStatusIncluded = reportJobStatusFilters.includes(selectedStatus); const newFilters = isStatusIncluded - ? ensureReportJobStatuses( - reportJobStatusFilters.filter((status) => status !== selectedStatus) - ) - : ensureReportJobStatuses([...reportJobStatusFilters, selectedStatus]); + ? reportJobStatusFilters.filter((status) => status !== selectedStatus) + : [...reportJobStatusFilters, selectedStatus]; analyticsTrack({ event: 'Compliance Report Job Status Filtered', properties: { @@ -197,7 +195,7 @@ function ReportJobs({ scanConfigId }: ReportJobsProps) { 'PARTIAL_SCAN_ERROR_EMAIL', 'ERROR', ]} - selectedStatuses={ensureReportJobStatuses(reportJobStatusFilters)} + selectedStatuses={reportJobStatusFilters} onChange={onReportJobStatusFilterChange} /> diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx index 18e114e123394..16b34edd888d9 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx @@ -4,6 +4,7 @@ import usePermissions from 'hooks/usePermissions'; import NodeConfigurationsList from './Configurations/NodeConfigurationsList'; import NodeVulnerabilityReportsLayout from './NodeVulnerabilityReportsLayout'; +import NodeViewBasedJobsPage from './ViewBasedJobs/NodeViewBasedJobsPage'; function NodeVulnerabilityReportsPage() { const { hasReadAccess } = usePermissions(); @@ -23,7 +24,7 @@ function NodeVulnerabilityReportsPage() { ) } /> - View-based reports} /> + } /> ); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsPage.tsx new file mode 100644 index 0000000000000..68fd94a662277 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsPage.tsx @@ -0,0 +1,170 @@ +import { useCallback, useMemo } from 'react'; +import { + Content, + PageSection, + Pagination, + Toolbar, + ToolbarContent, + ToolbarItem, + useInterval, +} from '@patternfly/react-core'; + +import PageTitle from 'Components/PageTitle'; +import MyJobsFilter from 'Components/ReportJob/MyJobsFilter'; +import ReportJobStatusFilter, { + isReportJobStatus, +} from 'Components/ReportJob/ReportJobStatusFilter'; +import type { ReportJobStatus } from 'Components/ReportJob/types'; +import useRestQuery from 'hooks/useRestQuery'; +import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; +import useURLSort from 'hooks/useURLSort'; +import useURLStringUnion from 'hooks/useURLStringUnion'; +import { + getViewBasedMyNodeReportHistory, + getViewBasedNodeReportHistory, +} from 'services/NodeReportsService'; +import { toggleItemInArray } from 'utils/arrayUtils'; +import { ensureBoolean, ensureStringArray } from 'utils/ensure'; +import { getTableUIState } from 'utils/getTableUIState'; + +import NodeViewBasedReportsTable from './NodeViewBasedJobsTable'; + +const sortOptions = { + sortFields: ['Report Completion Time'], + defaultSortOption: { field: 'Report Completion Time', direction: 'desc' } as const, +}; + +const reportStateByJobStatus: Partial> = { + WAITING: 'WAITING', + PREPARING: 'PREPARING', + ERROR: 'FAILURE', + DOWNLOAD_GENERATED: 'GENERATED', +}; + +function NodeViewBasedJobsPage() { + const { page, perPage, setPage, setPerPage } = useURLPagination(10); + const { sortOption, getSortParams } = useURLSort(sortOptions); + const { searchFilter, setSearchFilter } = useURLSearch(); + const [isViewingOnlyMyJobs, setIsViewingOnlyMyJobs] = useURLStringUnion('viewOnlyMyJobs', [ + 'false', + 'true', + ]); + + const reportJobStatusFilters = useMemo(() => { + return ensureStringArray(searchFilter['Report Job Status']).filter(isReportJobStatus); + }, [searchFilter]); + + const fetchViewBasedNodeReportsHistoryCallback = useCallback(() => { + const fetchHistory = + isViewingOnlyMyJobs === 'true' + ? getViewBasedMyNodeReportHistory + : getViewBasedNodeReportHistory; + + return fetchHistory({ + searchFilter: { + 'Report State': reportJobStatusFilters.flatMap( + (status) => reportStateByJobStatus[status] ?? [] + ), + }, + page, + perPage, + sortOption, + }); + }, [reportJobStatusFilters, page, perPage, sortOption, isViewingOnlyMyJobs]); + + const { data, isLoading, error, refetch } = useRestQuery( + fetchViewBasedNodeReportsHistoryCallback, + { + clearErrorBeforeRequest: false, + } + ); + + const tableState = getTableUIState({ + isLoading, + data, + error, + searchFilter, + isPolling: true, + }); + + const onReportJobStatusFilterChange = (_checked: boolean, selectedStatus: ReportJobStatus) => { + const newFilters = toggleItemInArray(reportJobStatusFilters, selectedStatus); + setSearchFilter({ + ...searchFilter, + 'Report Job Status': newFilters, + }); + setPage(1); + }; + + const onMyJobsFilterChange = (checked: boolean) => { + setIsViewingOnlyMyJobs(String(checked)); + setPage(1); + }; + + useInterval(refetch, 10000); + + return ( + <> + + + + Check job status and download view-based reports in CSV format. Jobs are purged + according to retention settings. + + + + + + + + + + + + + {/* TODO: Change this to determinate pagination pattern */} + ( + + + {firstIndex} - {lastIndex} + {' '} + of many + + )} + page={page} + perPage={perPage} + onSetPage={(_, newPage) => setPage(newPage)} + onPerPageSelect={(_, newPerPage) => setPerPage(newPerPage)} + isCompact + /> + + + + { + setSearchFilter({}); + setPage(1); + }} + /> + + + ); +} + +export default NodeViewBasedJobsPage; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsTable.tsx new file mode 100644 index 0000000000000..7a1815809a944 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/ViewBasedJobs/NodeViewBasedJobsTable.tsx @@ -0,0 +1,89 @@ +import { Link } from 'react-router-dom-v5-compat'; +import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; + +import ReportJobStatus from 'Components/ReportJob/ReportJobStatus'; +import { onDownloadReport } from 'Components/ReportJob/utils'; +import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import useAuthStatus from 'hooks/useAuthStatus'; +import type { GetSortParams } from 'hooks/useURLSort'; +import { vulnerabilitiesNodeCvesPath } from 'routePaths'; +import type { NodeViewBasedReportSnapshot } from 'services/ReportsService.types'; +import { getDateTime } from 'utils/dateUtils'; +import type { TableUIState } from 'utils/getTableUIState'; + +export type NodeViewBasedJobsTableProps = { + tableState: TableUIState; + getSortParams: GetSortParams; + onClearFilters: () => void; +}; + +function NodeViewBasedJobsTable({ + tableState, + getSortParams, + onClearFilters, +}: NodeViewBasedJobsTableProps) { + const { currentUser } = useAuthStatus(); + + return ( +
    Request nameJob ID Requester Job status Completed
    +
    - Virtual machineCVE severityVirtual machineCVE severity CVE statusCVSSGuest OSCVSSGuest OS Affected components
    CVETop severityTop CVE severity CVE statusCVSSTop CVSS Affected components
    {cve} + + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.cy.jsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.cy.jsx index 87c9882b9b9c8..63f87555670b7 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.cy.jsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.cy.jsx @@ -173,7 +173,7 @@ describe(Cypress.spec.relative, () => { cy.findAllByRole('row') .eq(rowIndex) .within(() => { - cy.get(`td[data-label="CVE severity"]:contains("${expectedSeverity}")`); + cy.get(`td[data-label="Top CVE severity"]:contains("${expectedSeverity}")`); }); }); }); @@ -277,7 +277,7 @@ describe(Cypress.spec.relative, () => { .eq(rowIndex) .within(() => { cy.get( - `td[data-label="CVSS"]:contains("${expectedCVSS} (${expectedVersion})")` + `td[data-label="Top CVSS"]:contains("${expectedCVSS} (${expectedVersion})")` ); }); }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.tsx index 869aff6e9509d..20674365abeab 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/NodeCve/AffectedNodesTable.tsx @@ -109,9 +109,9 @@ function AffectedNodesTable({
    NodeCVE severityTop CVE severity CVE statusCVSSTop CVSS Cluster Operating system Affected components + + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx index b6d64c4787426..d1660a2226dae 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx @@ -112,9 +112,9 @@ function VirtualMachinePageVulnerabilities({
    CVECVE severityTop CVE severity CVE statusCVSSTop CVSS EPSS probability + @@ -154,7 +154,7 @@ function VirtualMachinePageVulnerabilities({ isFixable={cve.isFixable} /> + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachineVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachineVulnerabilitiesTable.tsx index 0208980734d71..ddc01e1ecc3b3 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachineVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachineVulnerabilitiesTable.tsx @@ -34,7 +34,7 @@ export const defaultColumns = { isUntoggleAble: true, }, cveSeverity: { - title: 'CVE severity', + title: 'Top CVE severity', isShownByDefault: true, }, cveStatus: { @@ -42,7 +42,7 @@ export const defaultColumns = { isShownByDefault: true, }, cvss: { - title: 'CVSS', + title: 'Top CVSS', isShownByDefault: true, }, epssProbability: { @@ -90,14 +90,14 @@ function VirtualMachineVulnerabilitiesTable({ className={getVisibilityClass('cveSeverity')} sort={getSortParams(CVE_SEVERITY_SORT_FIELD)} > - CVE severity + Top CVE severity CVE status - CVSS + Top CVSS + Virtual machineCVE severityTop CVE severity CVE statusCVSSTop CVSS Guest OS Affected components
    + @@ -102,7 +102,7 @@ function AffectedVirtualMachinesTable({ isFixable={virtualMachine.isFixable} /> + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/AffectedImagesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/AffectedImagesTable.tsx index bcae00f4b83f7..b3a5d2476e8e7 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/AffectedImagesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/AffectedImagesTable.tsx @@ -47,7 +47,7 @@ export const defaultColumns = { isUntoggleAble: true, }, cveSeverity: { - title: 'CVE severity', + title: 'Top CVE severity', isShownByDefault: true, }, cveStatus: { @@ -55,7 +55,7 @@ export const defaultColumns = { isShownByDefault: true, }, cvss: { - title: 'CVSS', + title: 'Top CVSS', isShownByDefault: true, }, nvdCvss: { @@ -165,13 +165,13 @@ function AffectedImagesTable({ className={getVisibilityClass('cveSeverity')} sort={getSortParams('Severity')} > - CVE severity + Top CVE severity CVE status {isFiltered && } CVSSTop CVSS NVD CVSS @@ -264,7 +264,7 @@ function AffectedImagesTable({ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx index 25a806bff9be5..565e279f5996a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx @@ -49,7 +49,7 @@ export const defaultColumns = { isShownByDefault: true, }, cveSeverity: { - title: 'CVE severity', + title: 'Top CVE severity', isShownByDefault: true, }, cveStatus: { @@ -145,7 +145,7 @@ function DeploymentVulnerabilitiesTable({ className={getVisibilityClass('cveSeverity')} sort={getSortParams('Severity')} > - CVE severity + Top CVE severity CVE status @@ -263,7 +263,7 @@ function DeploymentVulnerabilitiesTable({ CVE status {isFiltered && } - CVSS + Top CVSS NVD CVSS {isVulnerabilitySeverity(severity) && ( @@ -376,7 +376,7 @@ function ImageVulnerabilitiesTable({
    + + + + + + + + + Go to Node CVEs, + }} + filteredEmptyProps={{ onClearFilters }} + renderer={({ data }) => + data.map((snapshot) => { + const { user, reportStatus, isDownloadAvailable, reportJobId, name } = + snapshot; + const areDownloadActionsDisabled = currentUser.userId !== user.id; + + return ( + + + + + + + + + ); + }) + } + /> +
    Job nameRequesterJob statusCompleted
    {name}{user.name} + + onDownloadReport({ + reportJobId, + name, + completedAt: reportStatus.completedAt, + baseDownloadURL: + '/api/reports/node/jobs/download', + }) + } + /> + + {reportStatus.completedAt + ? getDateTime(reportStatus.completedAt) + : '-'} +
    + ); +} + +export default NodeViewBasedJobsTable; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx index 895e3ae8d89d2..2527790711611 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTab.tsx @@ -21,7 +21,7 @@ import useURLStringUnion from 'hooks/useURLStringUnion'; import { fetchViewBasedReportHistory } from 'services/ReportsService'; import PageTitle from 'Components/PageTitle'; import ReportJobStatusFilter, { - ensureReportJobStatuses, + isReportJobStatus, } from 'Components/ReportJob/ReportJobStatusFilter'; import MyJobsFilter from 'Components/ReportJob/MyJobsFilter'; import type { ReportJobStatus } from 'Components/ReportJob/types'; @@ -33,7 +33,7 @@ const sortOptions = { defaultSortOption: { field: 'Report Completion Time', direction: 'desc' } as const, }; -function createQueryFromReportJobStatusFilters(jobStatusFilters: string[]) { +function createQueryFromReportJobStatusFilters(reportJobStatuses: ReportJobStatus[]) { const query: Record = { 'Report State': [], }; @@ -48,8 +48,6 @@ function createQueryFromReportJobStatusFilters(jobStatusFilters: string[]) { }, }; - const reportJobStatuses = ensureReportJobStatuses(jobStatusFilters); - reportJobStatuses.forEach((jobStatus) => { const queryMapping = jobStatusQueryMappings[jobStatus]; if (queryMapping) { @@ -73,7 +71,7 @@ function ViewBasedReportsTab() { ]); const reportJobStatusFilters = useMemo(() => { - return ensureStringArray(searchFilter['Report Job Status']); + return ensureStringArray(searchFilter['Report Job Status']).filter(isReportJobStatus); }, [searchFilter]); const fetchViewBasedReportsHistoryCallback = useCallback(() => { @@ -110,7 +108,7 @@ function ViewBasedReportsTab() { ); setSearchFilter({ ...searchFilter, - 'Report Job Status': ensureReportJobStatuses(newFilters), + 'Report Job Status': newFilters, }); setPage(1); @@ -164,7 +162,7 @@ function ViewBasedReportsTab() { 'DOWNLOAD_GENERATED', 'ERROR', ]} - selectedStatuses={ensureReportJobStatuses(reportJobStatusFilters)} + selectedStatuses={reportJobStatusFilters} onChange={onReportJobStatusFilterChange} /> diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx index 11fd7e2999abd..c4380c371fce6 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ViewBasedReportsTable.tsx @@ -96,7 +96,7 @@ function ViewBasedReportsTable({ - + @@ -119,7 +119,7 @@ function ViewBasedReportsTable({ return ( - diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/AffectedVirtualMachinesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/AffectedVirtualMachinesTable.tsx index 2613fd53c1463..28782384ab32a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/AffectedVirtualMachinesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/AffectedVirtualMachinesTable.tsx @@ -14,6 +14,7 @@ import type { TableUIState } from 'utils/getTableUIState'; import type { VMCVEAffectedVMRow } from 'services/VirtualMachineService'; import { getVirtualMachineEntityPagePath } from '../../utils/searchUtils'; +import AffectedComponentsTable from '../components/AffectedComponentsTable'; import { CVE_SEVERITY_SORT_FIELD, CVSS_SORT_FIELD, @@ -31,12 +32,14 @@ export const sortFields = [ export const defaultSortOption = { field: CVE_SEVERITY_SORT_FIELD, direction: 'desc' } as const; export type AffectedVirtualMachinesTableProps = { + cveId: string; tableState: TableUIState; getSortParams: UseURLSortResult['getSortParams']; onClearFilters: () => void; }; function AffectedVirtualMachinesTable({ + cveId, tableState, getSortParams, onClearFilters, @@ -119,7 +122,12 @@ function AffectedVirtualMachinesTable({ diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx index cfa9254105da8..4a0cf267fb1d3 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachineCve/VirtualMachineCvePage.tsx @@ -7,11 +7,7 @@ import { Flex, PageSection, Pagination, - Split, - SplitItem, - Title, } from '@patternfly/react-core'; -import pluralize from 'pluralize'; import PageTitle from 'Components/PageTitle'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; @@ -146,27 +142,19 @@ function VirtualMachineCvePage() { /> - - - - - {`${affectedVMCount} ${pluralize('virtual machine', affectedVMCount)} affected`} - - - - - setPage(newPage)} - onPerPageSelect={(_, newPerPage) => { - setPerPage(newPerPage); - }} - /> - - + + setPage(newPage)} + onPerPageSelect={(_, newPerPage) => { + setPerPage(newPerPage); + }} + /> + { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/components/AffectedComponentsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/components/AffectedComponentsTable.tsx new file mode 100644 index 0000000000000..c8db0243edde5 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/components/AffectedComponentsTable.tsx @@ -0,0 +1,77 @@ +import { useCallback } from 'react'; +import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; + +import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import useRestQuery from 'hooks/useRestQuery'; +import { getVMCVEComponents } from 'services/VirtualMachineService'; +import { getTableUIState } from 'utils/getTableUIState'; + +import AdvisoryLinkOrText from '../../components/AdvisoryLinkOrText'; +import FixedByVersion from '../../components/FixedByVersion'; +import { sourceTypeLabels } from '../../constants'; + +export type AffectedComponentsTableProps = { + virtualMachineId: string; + cveId: string; +}; + +function AffectedComponentsTable({ virtualMachineId, cveId }: AffectedComponentsTableProps) { + const colSpan = 5; + const fetchComponents = useCallback( + () => getVMCVEComponents(virtualMachineId, cveId), + [virtualMachineId, cveId] + ); + const { data, isLoading, error } = useRestQuery(fetchComponents); + + const tableState = getTableUIState({ + isLoading, + data: data?.components, + error, + searchFilter: {}, + }); + + return ( +
    Job IDJob name Requester Job status Completed
    + - Affected component details coming soon + {isExpanded && ( + + )}
    - Affected component details coming soon + {isExpanded && ( + + )}
    + + + + + + + + + + ( + + {data.map( + ({ componentName, componentVersion, source, fixedBy, advisory }) => ( + + + + + + + + ) + )} + + )} + /> +
    ComponentVersionCVE fixed inAdvisoryType
    {componentName}{componentVersion} + + + + {sourceTypeLabels[source]}
    + ); +} + +export default AffectedComponentsTable; diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index f74848fdb36c9..d438d703fe149 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -3,6 +3,7 @@ import qs from 'qs'; import axios from 'services/instance'; import type { VulnerabilitySeverity } from 'types/cve.proto'; import type { ScanComponent, SourceType } from 'types/scanComponent.proto'; +import type { Advisory } from 'types/vulnerability.proto'; import type { SearchFilter, SearchQueryOptions } from 'types/search'; import { applyRegexSearchModifiers, @@ -142,6 +143,18 @@ export type ListVMCVEAffectedVMsResponse = { totalCount: number; }; +export type VMCVEComponentRow = { + componentName: string; + componentVersion: string; + source: SourceType; + fixedBy: string; + advisory: Advisory | null; +}; + +export type GetVMCVEComponentsResponse = { + components: VMCVEComponentRow[]; +}; + export type VMCVEByVMRow = { cve: string; severity: VulnerabilitySeverity; @@ -153,7 +166,7 @@ export type VMCVEByVMRow = { publishedOn?: string; summary: string; link: string; - advisory?: { name: string; link: string }; + advisory: Advisory | null; }; export type ListVMCVEsByVMResponse = { @@ -290,6 +303,15 @@ export function listVMCVEAffectedVMs( .then((response) => response.data); } +export function getVMCVEComponents( + vmId: string, + cveId: string +): Promise { + return axios + .get(`/v2/virtualmachines/${vmId}/cves/${cveId}/components`) + .then((response) => response.data); +} + export function listVMCVEsByVM( vmId: string, { searchFilter, page, perPage, sortOption }: SearchQueryOptions From 5fd40a2091ea821e571fa212198670d21f0d2446 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:58:19 -0500 Subject: [PATCH 177/638] ROX-36614: Virtual Machines Overview Sorting (#22510) --- .../Overview/VirtualMachineCvesCveTable.tsx | 27 ++++++++++++++----- .../VirtualMachineCvesToggleGroup.tsx | 19 +++++++------ .../Overview/VirtualMachineCvesVmTable.tsx | 22 ++++++++++++--- .../Vulnerabilities/utils/sortFields.ts | 2 ++ .../src/services/VirtualMachineService.ts | 2 ++ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx index 3b094e82c1605..1fda7aa8133a7 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesCveTable.tsx @@ -7,6 +7,7 @@ import DateDistance from 'Components/DateDistance'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; import type { UseURLPaginationResult } from 'hooks/useURLPagination'; import useRestQuery from 'hooks/useRestQuery'; +import useURLSort from 'hooks/useURLSort'; import { listVMCVEs } from 'services/VirtualMachineService'; import type { SearchFilter } from 'types/search'; import { getTableUIState } from 'utils/getTableUIState'; @@ -14,10 +15,19 @@ import { getTableUIState } from 'utils/getTableUIState'; import SeverityCountLabels from '../../components/SeverityCountLabels'; import TableEntityToolbar from '../../components/TableEntityToolbar'; import { getVirtualMachineEntityPagePath } from '../../utils/searchUtils'; +import { + CVE_PUBLISHED_ON_SORT_FIELD, + CVE_SORT_FIELD, + CVSS_SORT_FIELD, +} from '../../utils/sortFields'; import { formatEpssProbabilityAsPercent } from '../../WorkloadCves/Tables/table.utils'; import VirtualMachineCvesFilterToolbar from './VirtualMachineCvesFilterToolbar'; import VirtualMachineCvesToggleGroup from './VirtualMachineCvesToggleGroup'; +const sortFields = [CVE_SORT_FIELD, CVSS_SORT_FIELD, CVE_PUBLISHED_ON_SORT_FIELD]; + +const defaultSortOption = { field: CVSS_SORT_FIELD, direction: 'desc' } as const; + type VirtualMachineCvesCveTableProps = { searchFilter: SearchFilter; pagination: UseURLPaginationResult; @@ -32,10 +42,15 @@ function VirtualMachineCvesCveTable({ onClearFilters, }: VirtualMachineCvesCveTableProps) { const { page, perPage } = pagination; + const { sortOption, getSortParams } = useURLSort({ + sortFields, + defaultSortOption, + onSort: () => pagination.setPage(1, 'replace'), + }); const fetchVirtualMachineCVEs = useCallback( - () => listVMCVEs({ searchFilter, page, perPage }), - [searchFilter, page, perPage] + () => listVMCVEs({ searchFilter, page, perPage, sortOption }), + [searchFilter, page, perPage, sortOption] ); const { data, isLoading, error } = useRestQuery(fetchVirtualMachineCVEs); @@ -65,12 +80,12 @@ function VirtualMachineCvesCveTable({ > - CVE + CVE Virtual machines by severity - Top CVSS + Top CVSS Affected virtual machines EPSS probability - First discovered + Published - + diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx index 994a908879d0d..efca36f316d5c 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesToggleGroup.tsx @@ -1,10 +1,12 @@ import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core'; import useURLPagination from 'hooks/useURLPagination'; +import useURLParameter from 'hooks/useURLParameter'; import useURLStringUnion from 'hooks/useURLStringUnion'; import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; import { virtualMachineEntityTabValues } from '../../types'; +import type { VirtualMachineEntityTab } from '../../types'; // Shared entity toggle for both overview tables, which each render their own toolbar. // Factored out to make sure the two tables stay in sync. State is read from the URL, not props. @@ -14,6 +16,13 @@ function VirtualMachineCvesToggleGroup() { virtualMachineEntityTabValues ); const { setPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); + const [, setSortOption] = useURLParameter('sortOption', undefined); + + function selectEntityTab(entityTab: VirtualMachineEntityTab) { + setActiveEntityTabKey(entityTab); + setPage(1); + setSortOption(undefined); + } return ( @@ -21,19 +30,13 @@ function VirtualMachineCvesToggleGroup() { text="CVEs" buttonId="CVE" isSelected={activeEntityTabKey === 'CVE'} - onChange={() => { - setActiveEntityTabKey('CVE'); - setPage(1); - }} + onChange={() => selectEntityTab('CVE')} /> { - setActiveEntityTabKey('VirtualMachine'); - setPage(1); - }} + onChange={() => selectEntityTab('VirtualMachine')} /> ); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx index 2ce7d69cde079..fb5f86aa3ca45 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesVmTable.tsx @@ -19,12 +19,16 @@ import { getTableUIState } from 'utils/getTableUIState'; import SeverityCountLabels from '../../components/SeverityCountLabels'; import TableEntityToolbar from '../../components/TableEntityToolbar'; import { getVirtualMachineEntityPagePath } from '../../utils/searchUtils'; -import { VIRTUAL_MACHINE_SORT_FIELD } from '../../utils/sortFields'; +import { + CLUSTER_SORT_FIELD, + NAMESPACE_SORT_FIELD, + VIRTUAL_MACHINE_SORT_FIELD, +} from '../../utils/sortFields'; import VirtualMachineCvesFilterToolbar from './VirtualMachineCvesFilterToolbar'; import VirtualMachineCvesToggleGroup from './VirtualMachineCvesToggleGroup'; import VirtualMachinesCvesTableLegacy from './VirtualMachinesCvesTableLegacy'; -const sortFields = [VIRTUAL_MACHINE_SORT_FIELD]; +const sortFields = [VIRTUAL_MACHINE_SORT_FIELD, CLUSTER_SORT_FIELD, NAMESPACE_SORT_FIELD]; const defaultSortOption = { field: VIRTUAL_MACHINE_SORT_FIELD, direction: 'asc' } as const; @@ -134,8 +138,18 @@ function VirtualMachineCvesVmTableEnhanced({ CVEs by severity - Cluster - Namespace + + Cluster + + + Namespace + Scanned components diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts b/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts index e119225f16106..7fa1b67923b10 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/utils/sortFields.ts @@ -6,8 +6,10 @@ export const CVE_SEVERITY_SORT_FIELD = 'Severity'; export const CVE_STATUS_SORT_FIELD = 'Fixable'; export const CVE_TYPE_SORT_FIELD = 'CVE Type'; export const CVE_COUNT_SORT_FIELD = 'CVE Count'; +export const CVE_PUBLISHED_ON_SORT_FIELD = 'CVE Published On'; export const OPERATING_SYSTEM_SORT_FIELD = 'Operating System'; export const COMPONENT_SORT_FIELD = 'Component'; +export const NAMESPACE_SORT_FIELD = 'Namespace'; // Cluster sort fields export const CLUSTER_SORT_FIELD = 'Cluster'; diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index d438d703fe149..0f8baf3f33652 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -260,11 +260,13 @@ export function listVMCVEs({ searchFilter, page, perPage, + sortOption, }: SearchQueryOptions): Promise { const params = buildNestedRawQueryParams({ page, perPage, searchFilter: applyRegexSearchModifiers(searchFilter ?? {}), + sortOption, }); return axios .get(`/v2/virtualmachines/cves?${params}`) From ba2d8ee96641201ac7af6ac6ed409cf5d0bca9f0 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:23:02 -0500 Subject: [PATCH 178/638] ROX-36601: Virtual machine CVE severity widgets (#22537) --- .../virtualMachineCves/getVMVulnSummary.json | 11 +++++ .../VirtualMachineCve.helpers.ts | 5 ++ .../virtualMachinePage.test.ts | 16 +++++++ .../VirtualMachinePageVulnerabilities.tsx | 46 +++++++++++++++++-- .../src/services/VirtualMachineService.ts | 26 +++++++++++ 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVMVulnSummary.json diff --git a/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVMVulnSummary.json b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVMVulnSummary.json new file mode 100644 index 0000000000000..fc763174c4c29 --- /dev/null +++ b/ui/apps/platform/cypress/fixtures/vulnerabilities/virtualMachineCves/getVMVulnSummary.json @@ -0,0 +1,11 @@ +{ + "severityCounts": { + "critical": { "total": 1, "fixable": 1 }, + "important": { "total": 1, "fixable": 1 }, + "moderate": { "total": 1, "fixable": 1 }, + "low": { "total": 0, "fixable": 0 }, + "unknown": { "total": 0, "fixable": 0 } + }, + "fixableCount": 3, + "notFixableCount": 0 +} diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts index 8af516a170a53..ba4cb9b5ef70b 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/VirtualMachineCve.helpers.ts @@ -7,6 +7,7 @@ export const getVirtualMachineAlias = 'getVirtualMachine'; export const getVirtualMachineCveComponentsAlias = 'getVirtualMachineCveComponents'; export const listVirtualMachineCvesAlias = 'listVirtualMachineCves'; export const listVirtualMachineComponentsAlias = 'listVirtualMachineComponents'; +export const getVirtualMachineVulnSummaryAlias = 'getVirtualMachineVulnSummary'; export const routeMatcherMapForVirtualMachines = { [listVirtualMachinesAlias]: { @@ -28,6 +29,10 @@ export const routeMatcherMapForVirtualMachineVulnerabilities = { method: 'GET', url: '/v2/virtualmachines/*/cves?*', }, + [getVirtualMachineVulnSummaryAlias]: { + method: 'GET', + url: '/v2/virtualmachines/*/vuln-summary?*', + }, }; export const routeMatcherMapForVirtualMachineComponents = { diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts index c16e430dc8bdd..88417e6d3ceb6 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachinePage.test.ts @@ -6,6 +6,7 @@ import { assertCannotFindThePage } from '../../../helpers/visit'; import { getVirtualMachineAlias, getVirtualMachineCveComponentsAlias, + getVirtualMachineVulnSummaryAlias, listVirtualMachineComponentsAlias, listVirtualMachineCvesAlias, routeMatcherMapForVirtualMachineComponents, @@ -20,10 +21,12 @@ const fixturePathGetVM = 'vulnerabilities/virtualMachineCves/getVM'; const fixturePathListCves = 'vulnerabilities/virtualMachineCves/listVirtualMachineCves'; const fixturePathListComponents = 'vulnerabilities/virtualMachineCves/listVirtualMachineComponents'; const fixturePathCveComponents = 'vulnerabilities/virtualMachineCves/getVMCVEComponents'; +const fixturePathVulnSummary = 'vulnerabilities/virtualMachineCves/getVMVulnSummary'; const staticResponseMapForVirtualMachineVulnerabilities = { [getVirtualMachineAlias]: { fixture: fixturePathGetVM }, [listVirtualMachineCvesAlias]: { fixture: fixturePathListCves }, + [getVirtualMachineVulnSummaryAlias]: { fixture: fixturePathVulnSummary }, }; function visitWithVulnFixtures() { @@ -146,6 +149,19 @@ describe('Virtual Machine CVEs - Virtual Machine Page', () => { [listVirtualMachineCvesAlias]: { body: { cves: [], totalCount: 0 }, }, + [getVirtualMachineVulnSummaryAlias]: { + body: { + severityCounts: { + critical: { total: 0, fixable: 0 }, + important: { total: 0, fixable: 0 }, + moderate: { total: 0, fixable: 0 }, + low: { total: 0, fixable: 0 }, + unknown: { total: 0, fixable: 0 }, + }, + fixableCount: 0, + notFixableCount: 0, + }, + }, }); cy.get('body').contains('No CVEs were detected for this virtual machine'); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx index bc7fdc20a94f9..318ffb3bfd6c9 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageVulnerabilities.tsx @@ -12,17 +12,24 @@ import useRestQuery from 'hooks/useRestQuery'; import useURLPagination from 'hooks/useURLPagination'; import useURLSearch from 'hooks/useURLSearch'; import useURLSort from 'hooks/useURLSort'; -import { listVMCVEsByVM } from 'services/VirtualMachineService'; +import { getVMVulnSummary, listVMCVEsByVM } from 'services/VirtualMachineService'; import { getTableUIState } from 'utils/getTableUIState'; import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; +import BySeveritySummaryCard from '../../components/BySeveritySummaryCard'; +import CvesByStatusSummaryCard from '../../components/CvesByStatusSummaryCard'; +import { SummaryCard, SummaryCardLayout } from '../../components/SummaryCardLayout'; import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; import AffectedComponentsTable from '../components/AffectedComponentsTable'; -import VirtualMachineScanScopeAlert from '../components/VirtualMachineScanScopeAlert'; import { virtualMachineCVESearchFilterConfig, virtualMachineComponentSearchFilterConfig, } from '../../searchFilterConfig'; +import { + getHiddenSeverities, + getHiddenStatuses, + parseQuerySearchFilter, +} from '../../utils/searchUtils'; import { CVE_EPSS_PROBABILITY_SORT_FIELD, CVE_SEVERITY_SORT_FIELD, @@ -56,6 +63,7 @@ function VirtualMachinePageVulnerabilities({ }: VirtualMachinePageVulnerabilitiesProps) { const { page, perPage, setPage, setPerPage } = useURLPagination(DEFAULT_VM_PAGE_SIZE); const { searchFilter, setSearchFilter } = useURLSearch(); + const querySearchFilter = parseQuerySearchFilter(searchFilter); const { sortOption, getSortParams } = useURLSort({ sortFields, defaultSortOption, @@ -64,6 +72,16 @@ function VirtualMachinePageVulnerabilities({ const expandedRowSet = useSet(); const colSpan = 7; + const fetchSummary = useCallback( + () => getVMVulnSummary(virtualMachineId, parseQuerySearchFilter(searchFilter)), + [virtualMachineId, searchFilter] + ); + const { + data: summary, + isLoading: isLoadingSummary, + error: summaryError, + } = useRestQuery(fetchSummary); + const fetchCVEs = useCallback( () => listVMCVEsByVM(virtualMachineId, { searchFilter, page, perPage, sortOption }), [virtualMachineId, searchFilter, page, perPage, sortOption] @@ -84,7 +102,6 @@ function VirtualMachinePageVulnerabilities({ return ( - + + ( + + )} + /> + ( + + )} + /> + diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index 0f8baf3f33652..2d44604ba180e 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -174,6 +174,12 @@ export type ListVMCVEsByVMResponse = { totalCount: number; }; +export type VMVulnSummary = { + severityCounts: VulnCountBySeverity; + fixableCount: number; + notFixableCount: number; +}; + export type VMComponentScanStatus = | 'NOT_SCANNED' | 'SCAN_PENDING' @@ -314,6 +320,26 @@ export function getVMCVEComponents( .then((response) => response.data); } +// Might consider updating buildNestedRawQueryParams to handle this case (no pagination). +export function getVMVulnSummary( + virtualMachineId: string, + searchFilter: SearchFilter +): Promise { + const params = qs.stringify( + { + query: { + query: getRequestQueryStringForSearchFilter( + applyRegexSearchModifiers(searchFilter) + ), + }, + }, + { arrayFormat: 'repeat', allowDots: true } + ); + return axios + .get(`/v2/virtualmachines/${virtualMachineId}/vuln-summary?${params}`) + .then((response) => response.data); +} + export function listVMCVEsByVM( vmId: string, { searchFilter, page, perPage, sortOption }: SearchQueryOptions From 1a0b1d5c3ccc8dc8a36a10007caa2663f8304298 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 02:58:44 +0000 Subject: [PATCH 179/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 60dd9e0 (#22606) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 645008c3cd8c5..97d1b26e7bd99 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index ffcfa9c4d3f40..205ab4e53feb6 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 78d0e87519cba..3fcc1d2413b33 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 2e3ce58efe46f..195c07cd7e6e2 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 9814d2a889b7c..29618c0bd9419 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 9f4194cc2308a..788a72b6a9844 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 205ac1fd3c30c..4a7a7603b37f6 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index a58378f6e645f..ef17814cd46d3 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:b7840765915d72045a21755ee99f06f790ea96615c0cb063f37ee6c43ce4faa7 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 - name: kind value: task resolver: bundles From 1445482eee5d5120a876b532d9698c4f9f590b31 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:15:36 +0000 Subject: [PATCH 180/638] Update registry.redhat.io/rhel9/postgresql-15:latest Docker digest to 26ca481 (#22607) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- image/postgres/konflux.Dockerfile | 2 +- scanner/image/db/konflux.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index a31569928fdc1..64fef48112d32 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:175e5b0bffe9b9dabfb3e7fe2d707c64e6301d1e2dfe97acdac2e085561026de AS final +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:26ca481f6fea9df09cb9e758188ad96382262d7487b104b1d6fd97b9b1ad15c9 AS final USER root diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index a999f3a755be8..306822c3d4e71 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:175e5b0bffe9b9dabfb3e7fe2d707c64e6301d1e2dfe97acdac2e085561026de +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:26ca481f6fea9df09cb9e758188ad96382262d7487b104b1d6fd97b9b1ad15c9 ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi From 31ea52caba4a28445e492a6856bf9fb37583d151 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:35:20 +0000 Subject: [PATCH 181/638] Refresh RPM lockfiles [SECURITY] (#22491) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 352 ++++++++++++++++++++++++------------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 76ad27aa7bf30..8aed50cd25edf 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -291,13 +291,13 @@ arches: name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/d/dbus-broker-28-7.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/d/dbus-broker-28-9.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 173303 - checksum: sha256:342af53fbf4254e34b5f19a6be6f22c67551c26dd0c49016edc93e806621b75d + size: 174219 + checksum: sha256:ffebc8de0cd9ed86122973be161b617aa46ce1e020f61f9e8e5a42246d4bd495 name: dbus-broker - evr: 28-7.el9 - sourcerpm: dbus-broker-28-7.el9.src.rpm + evr: 28-9.el9_8 + sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 18551 @@ -417,13 +417,13 @@ arches: name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gzip-1.12-1.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gzip-1.12-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 169809 - checksum: sha256:45710df49b439ddc4a2848fd3877367761b574234ae28b6be46f1cf54f3fcdca + size: 171305 + checksum: sha256:efafc848fdaa3a8e5e77baddc07abc7d800dc973efd44ecf492bc64dca4fabe6 name: gzip - evr: 1.12-1.el9 - sourcerpm: gzip-1.12-1.el9.src.rpm + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/j/jq-1.6-19.el9_8.2.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 191195 @@ -676,20 +676,20 @@ arches: name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-18.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 222382 - checksum: sha256:6d529b3f31dee553349277c9f72da4f14ae54a2d48d10aa3088b45fbe90f99c2 + size: 223114 + checksum: sha256:80f3962d3eb780ca6d6d4f8c6841b003a907d758ad8ad1c3d785e9cf327672f6 name: libssh - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-18.el9.noarch.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 14685 - checksum: sha256:ab353cbff2269677fc7364bfcddb8e11537ea83361a0ad642e60eac87ed67fbb + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 723913 @@ -984,13 +984,13 @@ arches: name: systemd-rpm-macros evr: 252-67.el9_8.4 sourcerpm: systemd-252-67.el9_8.4.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/t/tar-1.34-11.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/t/tar-1.34-13.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 904777 - checksum: sha256:62f985a87e048caa3ef883966d6176448398abc42df00ebff07e7311ff5f425d + size: 909271 + checksum: sha256:ee4fa57c4bb87613f6fbd4b785a9e6162d43f046d1bbdd5022771652b931e9d0 name: tar - evr: 2:1.34-11.el9 - sourcerpm: tar-1.34-11.el9.src.rpm + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 933286 @@ -1183,12 +1183,12 @@ arches: checksum: sha256:3fe74a2b4fb4485c93e974010d9376e30a63dfcc628bfd6c01837c27b4953912 name: dbus evr: 1:1.12.20-8.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/d/dbus-broker-28-7.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/d/dbus-broker-28-9.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 254475 - checksum: sha256:aced3097fbb8a424ca1816b8cb3e79960a9ccf7ba139538282886e692c317b29 + size: 258866 + checksum: sha256:9ccbb440edb6826689fd5e0d650de4f08550c3c0f3b2732c54c1e99b0d361824 name: dbus-broker - evr: 28-7.el9 + evr: 28-9.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/d/diffutils-3.7-12.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 1477522 @@ -1261,12 +1261,12 @@ arches: checksum: sha256:81b14432ebe1645b74b57592f1dcde8fab15ec13632f483f72ff2407ed16c33e name: grep evr: 3.6-5.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gzip-1.12-1.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gzip-1.12-2.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 856147 - checksum: sha256:a05f582ec42e89258ee5e10af96dee4300bcb2a6a69a76bfb5b46f79e6a6a47b + size: 859023 + checksum: sha256:f28fcbe8f3de2ec7dcdb51b1f51f03f61d0afa18c9fef10d210f40ff0c71f3a0 name: gzip - evr: 1.12-1.el9 + evr: 1.12-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/j/jq-1.6-19.el9_8.2.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 1512470 @@ -1405,12 +1405,12 @@ arches: checksum: sha256:734651070d0113de033da80114b416931c4c0be21ce51f6b1c1641b1185c34f3 name: libsigsegv evr: 2.13-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/l/libssh-0.10.4-18.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/l/libssh-0.10.4-19.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 707007 - checksum: sha256:e012994887f4f4c4367bce98f2eb5589bfa5c7926d65471cefeee12bee636b1d + size: 720407 + checksum: sha256:83663aff05b77225cded03c63955736eddc0d6ecf6072cb4355563094a2a3250 name: libssh - evr: 0.10.4-18.el9 + evr: 0.10.4-19.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/l/libtasn1-4.16.0-10.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 1899881 @@ -1585,12 +1585,12 @@ arches: checksum: sha256:e44181d1de2a135a5507b956d18a9eb11951a35aaa9c15dbc0f144ed8313f732 name: systemd evr: 252-67.el9_8.4 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/t/tar-1.34-11.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/t/tar-1.34-13.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 2294162 - checksum: sha256:13204f7499944b3fe773cbdf0b9bb2de31eaa9a4c69f31830fb9e54506bcb9b6 + size: 2312101 + checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar - evr: 2:1.34-11.el9 + evr: 2:1.34-13.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 933774 @@ -1622,10 +1622,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0b335b05c4b4910b48f590d68defa760399c9416dab4f2d1dda50fa8499dc40c-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/1d3cc796cab3c0dd44dada85781f30cba28ce9786bee64eed403cf9e6005e153-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 65051 - checksum: sha256:0b335b05c4b4910b48f590d68defa760399c9416dab4f2d1dda50fa8499dc40c + size: 65846 + checksum: sha256:1d3cc796cab3c0dd44dada85781f30cba28ce9786bee64eed403cf9e6005e153 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1915,13 +1915,13 @@ arches: name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/d/dbus-broker-28-7.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/d/dbus-broker-28-9.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 192179 - checksum: sha256:dfe710f3a07cd31fd144f439deaed0ef78b5ea65caecb754bc69959908191af7 + size: 192671 + checksum: sha256:62f23c1c9a1354c21b0b2d4c4013874b8c60f11e134f8cdf6f0314adb4bf3bbe name: dbus-broker - evr: 28-7.el9 - sourcerpm: dbus-broker-28-7.el9.src.rpm + evr: 28-9.el9_8 + sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 18551 @@ -2041,13 +2041,13 @@ arches: name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gzip-1.12-1.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gzip-1.12-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 175705 - checksum: sha256:55b983f08d8b2a0741b07f114cdba89a8ecb207064c001e90e4c76a13836d458 + size: 176201 + checksum: sha256:6303a915e54bf2df02c788387d44b4e08882c1431b896483482fa87a2b799972 name: gzip - evr: 1.12-1.el9 - sourcerpm: gzip-1.12-1.el9.src.rpm + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/j/jq-1.6-19.el9_8.2.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 210467 @@ -2307,20 +2307,20 @@ arches: name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libssh-0.10.4-18.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 250594 - checksum: sha256:15cff94afc4645b00c917abd7780a6a3ee1b476dff3eaccaaed93b88298a2ca1 + size: 252645 + checksum: sha256:44d3d65ac69d08e4ce6053e477b236772065f48c4be8162370976d3bd0f3e82f name: libssh - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libssh-config-0.10.4-18.el9.noarch.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 14685 - checksum: sha256:ab353cbff2269677fc7364bfcddb8e11537ea83361a0ad642e60eac87ed67fbb + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 856889 @@ -2615,13 +2615,13 @@ arches: name: systemd-rpm-macros evr: 252-67.el9_8.4 sourcerpm: systemd-252-67.el9_8.4.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/t/tar-1.34-11.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/t/tar-1.34-13.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 945887 - checksum: sha256:cb2fe4ca6c89e1a9fff648629cc7b9b7f33a7853a8ae74c10baa1d5b731d2447 + size: 947907 + checksum: sha256:d1edfa14f40d3556861be567c693a18cf025f7d1a1e041142a8a7cae6f7b32f7 name: tar - evr: 2:1.34-11.el9 - sourcerpm: tar-1.34-11.el9.src.rpm + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 933286 @@ -2814,12 +2814,12 @@ arches: checksum: sha256:3fe74a2b4fb4485c93e974010d9376e30a63dfcc628bfd6c01837c27b4953912 name: dbus evr: 1:1.12.20-8.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/d/dbus-broker-28-7.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/d/dbus-broker-28-9.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 254475 - checksum: sha256:aced3097fbb8a424ca1816b8cb3e79960a9ccf7ba139538282886e692c317b29 + size: 258866 + checksum: sha256:9ccbb440edb6826689fd5e0d650de4f08550c3c0f3b2732c54c1e99b0d361824 name: dbus-broker - evr: 28-7.el9 + evr: 28-9.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/d/diffutils-3.7-12.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 1477522 @@ -2892,12 +2892,12 @@ arches: checksum: sha256:81b14432ebe1645b74b57592f1dcde8fab15ec13632f483f72ff2407ed16c33e name: grep evr: 3.6-5.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gzip-1.12-1.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gzip-1.12-2.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 856147 - checksum: sha256:a05f582ec42e89258ee5e10af96dee4300bcb2a6a69a76bfb5b46f79e6a6a47b + size: 859023 + checksum: sha256:f28fcbe8f3de2ec7dcdb51b1f51f03f61d0afa18c9fef10d210f40ff0c71f3a0 name: gzip - evr: 1.12-1.el9 + evr: 1.12-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/j/jq-1.6-19.el9_8.2.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 1512470 @@ -3042,12 +3042,12 @@ arches: checksum: sha256:734651070d0113de033da80114b416931c4c0be21ce51f6b1c1641b1185c34f3 name: libsigsegv evr: 2.13-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/l/libssh-0.10.4-18.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/l/libssh-0.10.4-19.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 707007 - checksum: sha256:e012994887f4f4c4367bce98f2eb5589bfa5c7926d65471cefeee12bee636b1d + size: 720407 + checksum: sha256:83663aff05b77225cded03c63955736eddc0d6ecf6072cb4355563094a2a3250 name: libssh - evr: 0.10.4-18.el9 + evr: 0.10.4-19.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/l/libtasn1-4.16.0-10.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 1899881 @@ -3222,12 +3222,12 @@ arches: checksum: sha256:e44181d1de2a135a5507b956d18a9eb11951a35aaa9c15dbc0f144ed8313f732 name: systemd evr: 252-67.el9_8.4 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/t/tar-1.34-11.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/t/tar-1.34-13.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 2294162 - checksum: sha256:13204f7499944b3fe773cbdf0b9bb2de31eaa9a4c69f31830fb9e54506bcb9b6 + size: 2312101 + checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar - evr: 2:1.34-11.el9 + evr: 2:1.34-13.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 933774 @@ -3259,10 +3259,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/45cfc2702162c5dcf3a4586d596601c70a645d75236fbbfb4e6307e251d5dfa1-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/6df8b63607e968c2033b6ac4bfbdc54ba278886844e7c040e50ab62a3dd831cb-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 64975 - checksum: sha256:45cfc2702162c5dcf3a4586d596601c70a645d75236fbbfb4e6307e251d5dfa1 + size: 65342 + checksum: sha256:6df8b63607e968c2033b6ac4bfbdc54ba278886844e7c040e50ab62a3dd831cb - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3552,13 +3552,13 @@ arches: name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/d/dbus-broker-28-7.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/d/dbus-broker-28-9.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 169208 - checksum: sha256:68a4e64a8591df9ae3086014572da3d3b5eb2316a0c2c5e78aaed576c359fd81 + size: 170185 + checksum: sha256:d366b1927ddd1322a8b2ad5e9c54a9f7c0ce69be227a20c86a4a660b404991c0 name: dbus-broker - evr: 28-7.el9 - sourcerpm: dbus-broker-28-7.el9.src.rpm + evr: 28-9.el9_8 + sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 18551 @@ -3678,13 +3678,13 @@ arches: name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gzip-1.12-1.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gzip-1.12-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 173101 - checksum: sha256:50034ee6281864a218a5f3bc47de5afb434400fb8415907fd31d8351adbdc5a6 + size: 174286 + checksum: sha256:6a78d3ceeb859f4be0ae548a63556ede364b89cc9e984063e011086e497ae24d name: gzip - evr: 1.12-1.el9 - sourcerpm: gzip-1.12-1.el9.src.rpm + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/j/jq-1.6-19.el9_8.2.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 206781 @@ -3937,20 +3937,20 @@ arches: name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libssh-0.10.4-18.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 212889 - checksum: sha256:363b892f5c1a3b50916f93cbc942cb1526d08bace674b9499fe7aeab00637a4c + size: 214570 + checksum: sha256:d8f81f77d6fa830d001ea0336b7b4dfa955bd94e885205f8eb62b21e9439b264 name: libssh - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libssh-config-0.10.4-18.el9.noarch.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 14685 - checksum: sha256:ab353cbff2269677fc7364bfcddb8e11537ea83361a0ad642e60eac87ed67fbb + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 795149 @@ -4245,13 +4245,13 @@ arches: name: systemd-rpm-macros evr: 252-67.el9_8.4 sourcerpm: systemd-252-67.el9_8.4.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/t/tar-1.34-11.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/t/tar-1.34-13.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 907745 - checksum: sha256:2eee88e1c470b033bd44432f4ed5b1a41ffc7132ebe51f8ce12fa9268314e841 + size: 910326 + checksum: sha256:811cba7426379544fd3fbf6e5a8638bed49132e447b0c536c91e6fd315fac6a0 name: tar - evr: 2:1.34-11.el9 - sourcerpm: tar-1.34-11.el9.src.rpm + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 933286 @@ -4438,12 +4438,12 @@ arches: checksum: sha256:3fe74a2b4fb4485c93e974010d9376e30a63dfcc628bfd6c01837c27b4953912 name: dbus evr: 1:1.12.20-8.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/d/dbus-broker-28-7.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/d/dbus-broker-28-9.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 254475 - checksum: sha256:aced3097fbb8a424ca1816b8cb3e79960a9ccf7ba139538282886e692c317b29 + size: 258866 + checksum: sha256:9ccbb440edb6826689fd5e0d650de4f08550c3c0f3b2732c54c1e99b0d361824 name: dbus-broker - evr: 28-7.el9 + evr: 28-9.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/d/diffutils-3.7-12.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 1477522 @@ -4516,12 +4516,12 @@ arches: checksum: sha256:81b14432ebe1645b74b57592f1dcde8fab15ec13632f483f72ff2407ed16c33e name: grep evr: 3.6-5.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gzip-1.12-1.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gzip-1.12-2.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 856147 - checksum: sha256:a05f582ec42e89258ee5e10af96dee4300bcb2a6a69a76bfb5b46f79e6a6a47b + size: 859023 + checksum: sha256:f28fcbe8f3de2ec7dcdb51b1f51f03f61d0afa18c9fef10d210f40ff0c71f3a0 name: gzip - evr: 1.12-1.el9 + evr: 1.12-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/j/jq-1.6-19.el9_8.2.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 1512470 @@ -4660,12 +4660,12 @@ arches: checksum: sha256:734651070d0113de033da80114b416931c4c0be21ce51f6b1c1641b1185c34f3 name: libsigsegv evr: 2.13-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/l/libssh-0.10.4-18.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/l/libssh-0.10.4-19.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 707007 - checksum: sha256:e012994887f4f4c4367bce98f2eb5589bfa5c7926d65471cefeee12bee636b1d + size: 720407 + checksum: sha256:83663aff05b77225cded03c63955736eddc0d6ecf6072cb4355563094a2a3250 name: libssh - evr: 0.10.4-18.el9 + evr: 0.10.4-19.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/l/libtasn1-4.16.0-10.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 1899881 @@ -4840,12 +4840,12 @@ arches: checksum: sha256:e44181d1de2a135a5507b956d18a9eb11951a35aaa9c15dbc0f144ed8313f732 name: systemd evr: 252-67.el9_8.4 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/t/tar-1.34-11.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/t/tar-1.34-13.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 2294162 - checksum: sha256:13204f7499944b3fe773cbdf0b9bb2de31eaa9a4c69f31830fb9e54506bcb9b6 + size: 2312101 + checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar - evr: 2:1.34-11.el9 + evr: 2:1.34-13.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 933774 @@ -4877,10 +4877,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/f186465d263407d3495046b9360b8cebfc9452a198d5442945b9e3c80bcf6c26-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/58ca23e61b6e5f0529bb37677aad2f0c9de777270b2d77b91d0fbfb3665db082-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 63167 - checksum: sha256:f186465d263407d3495046b9360b8cebfc9452a198d5442945b9e3c80bcf6c26 + size: 63808 + checksum: sha256:58ca23e61b6e5f0529bb37677aad2f0c9de777270b2d77b91d0fbfb3665db082 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -5163,13 +5163,13 @@ arches: name: dbus evr: 1:1.12.20-8.el9 sourcerpm: dbus-1.12.20-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/d/dbus-broker-28-7.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/d/dbus-broker-28-9.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 179634 - checksum: sha256:de9869c08df7f6952787d0335b9bf1a09b328bea920556a59af07c8e085dd3cb + size: 180434 + checksum: sha256:2c3b853f8548394af581f487d3682e3e6a4fd27bb451088ce278c7f00af454db name: dbus-broker - evr: 28-7.el9 - sourcerpm: dbus-broker-28-7.el9.src.rpm + evr: 28-9.el9_8 + sourcerpm: dbus-broker-28-9.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/d/dbus-common-1.12.20-8.el9.noarch.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 18551 @@ -5289,13 +5289,13 @@ arches: name: grep evr: 3.6-5.el9 sourcerpm: grep-3.6-5.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gzip-1.12-1.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gzip-1.12-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 171206 - checksum: sha256:c8b3e0414d55b1eedb0185a564ac6cb2368bee2fd5f995447d045f6a714488ac + size: 172571 + checksum: sha256:a87bdcce45011f232758c01bd99c564b5f6b549d391646cc573a132d22604b85 name: gzip - evr: 1.12-1.el9 - sourcerpm: gzip-1.12-1.el9.src.rpm + evr: 1.12-2.el9_8 + sourcerpm: gzip-1.12-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/j/jq-1.6-19.el9_8.2.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 197453 @@ -5541,20 +5541,20 @@ arches: name: libsmartcols evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-18.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libssh-0.10.4-19.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 225119 - checksum: sha256:4a3ec8a0cc0d4f693a876c522d3e4bc2296180b9ec05f958f6a0aa8658702458 + size: 225821 + checksum: sha256:10d0ecb7cef182f8d11eb4bc772943a60c48b8171f602ffd83bb79b9edf5eb44 name: libssh - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-18.el9.noarch.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libssh-config-0.10.4-19.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 14685 - checksum: sha256:ab353cbff2269677fc7364bfcddb8e11537ea83361a0ad642e60eac87ed67fbb + size: 14764 + checksum: sha256:ce06b99793b9e5abc50a58ebc3ef11037f2efbdb5f819a25f8de4994613194d4 name: libssh-config - evr: 0.10.4-18.el9 - sourcerpm: libssh-0.10.4-18.el9.src.rpm + evr: 0.10.4-19.el9_8 + sourcerpm: libssh-0.10.4-19.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libstdc++-11.5.0-14.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 763021 @@ -5849,13 +5849,13 @@ arches: name: systemd-rpm-macros evr: 252-67.el9_8.4 sourcerpm: systemd-252-67.el9_8.4.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/t/tar-1.34-11.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/t/tar-1.34-13.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 913256 - checksum: sha256:58b622ab7bc225731a2473e677f16c9eb51b1ed218a86a7aa039a6fed665f245 + size: 914200 + checksum: sha256:913d84cd94463e3f0400d80c6742a2974eeab6445ac71ff9eb802a70779c146f name: tar - evr: 2:1.34-11.el9 - sourcerpm: tar-1.34-11.el9.src.rpm + evr: 2:1.34-13.el9_8 + sourcerpm: tar-1.34-13.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 933286 @@ -6048,12 +6048,12 @@ arches: checksum: sha256:3fe74a2b4fb4485c93e974010d9376e30a63dfcc628bfd6c01837c27b4953912 name: dbus evr: 1:1.12.20-8.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/d/dbus-broker-28-7.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/d/dbus-broker-28-9.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 254475 - checksum: sha256:aced3097fbb8a424ca1816b8cb3e79960a9ccf7ba139538282886e692c317b29 + size: 258866 + checksum: sha256:9ccbb440edb6826689fd5e0d650de4f08550c3c0f3b2732c54c1e99b0d361824 name: dbus-broker - evr: 28-7.el9 + evr: 28-9.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/d/diffutils-3.7-12.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 1477522 @@ -6126,12 +6126,12 @@ arches: checksum: sha256:81b14432ebe1645b74b57592f1dcde8fab15ec13632f483f72ff2407ed16c33e name: grep evr: 3.6-5.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gzip-1.12-1.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gzip-1.12-2.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 856147 - checksum: sha256:a05f582ec42e89258ee5e10af96dee4300bcb2a6a69a76bfb5b46f79e6a6a47b + size: 859023 + checksum: sha256:f28fcbe8f3de2ec7dcdb51b1f51f03f61d0afa18c9fef10d210f40ff0c71f3a0 name: gzip - evr: 1.12-1.el9 + evr: 1.12-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/j/jq-1.6-19.el9_8.2.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 1512470 @@ -6270,12 +6270,12 @@ arches: checksum: sha256:734651070d0113de033da80114b416931c4c0be21ce51f6b1c1641b1185c34f3 name: libsigsegv evr: 2.13-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/l/libssh-0.10.4-18.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/l/libssh-0.10.4-19.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 707007 - checksum: sha256:e012994887f4f4c4367bce98f2eb5589bfa5c7926d65471cefeee12bee636b1d + size: 720407 + checksum: sha256:83663aff05b77225cded03c63955736eddc0d6ecf6072cb4355563094a2a3250 name: libssh - evr: 0.10.4-18.el9 + evr: 0.10.4-19.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/l/libtasn1-4.16.0-10.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 1899881 @@ -6450,12 +6450,12 @@ arches: checksum: sha256:e44181d1de2a135a5507b956d18a9eb11951a35aaa9c15dbc0f144ed8313f732 name: systemd evr: 252-67.el9_8.4 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/t/tar-1.34-11.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/t/tar-1.34-13.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 2294162 - checksum: sha256:13204f7499944b3fe773cbdf0b9bb2de31eaa9a4c69f31830fb9e54506bcb9b6 + size: 2312101 + checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar - evr: 2:1.34-11.el9 + evr: 2:1.34-13.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 933774 @@ -6487,7 +6487,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/5318852ad7d83cc0af24d7be94083c7668329ad1405e50de53c854035760a048-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/4922fd428e4deac3257e65043e322f514d7300a15d85cdbb9df469db892f88b1-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 67412 - checksum: sha256:5318852ad7d83cc0af24d7be94083c7668329ad1405e50de53c854035760a048 + size: 68056 + checksum: sha256:4922fd428e4deac3257e65043e322f514d7300a15d85cdbb9df469db892f88b1 From 442c9d72c990ad8f7087a316a366f90cfa8d46b0 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:44:51 +0000 Subject: [PATCH 182/638] Refresh RPM lockfiles (#22546) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> From dc9796dfddb4f92f1faceb61823fc01bb9220592 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:57:18 +0000 Subject: [PATCH 183/638] Update Konflux references (#22608) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 97d1b26e7bd99..dabb87caba9b1 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -350,7 +350,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 - name: kind value: task resolver: bundles @@ -444,7 +444,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 - name: kind value: task resolver: bundles @@ -470,7 +470,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e - name: kind value: task resolver: bundles @@ -496,7 +496,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 195c07cd7e6e2..85571e4dbacc4 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -372,7 +372,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 - name: kind value: task resolver: bundles @@ -466,7 +466,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 - name: kind value: task resolver: bundles @@ -492,7 +492,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e - name: kind value: task resolver: bundles @@ -518,7 +518,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 29618c0bd9419..3cd54dd6501a9 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -536,7 +536,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3@sha256:4a4dfe9d15f165d346eae60a67571672eeaa1d4d4789f036a732f8f28254f870 + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:721a5937da7958a568b6ed4c2b97c6c9047cd795de1a9c3dcfff4b5936be4bc3 - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 - name: kind value: task resolver: bundles @@ -644,7 +644,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 - name: kind value: task resolver: bundles @@ -670,7 +670,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e - name: kind value: task resolver: bundles @@ -696,7 +696,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index ef17814cd46d3..f865e34956ed1 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -370,7 +370,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3@sha256:6a54d74739332eedf1228f3f37a434f810ec33bbf6db1d311b293a2b770239c5 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 - name: kind value: task resolver: bundles @@ -464,7 +464,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:c06bee8d1655c153ef2b2234d44d824a5d613d54abf592b85702f81a73873bd0 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 - name: kind value: task resolver: bundles @@ -490,7 +490,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:6a7fbfac7f7a9f95a2881e388d14e18e4fd9aacaa6d4ade9370fbed2c87dc989 + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e - name: kind value: task resolver: bundles @@ -516,7 +516,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:97231d2d0fb3a54ef6f653e5a355afe965674f8386cf3c8339c6704751c146b5 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 - name: kind value: task resolver: bundles From 47bb1fbb433305b7f26244bd44a4502877e52e97 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Thu, 3 Sep 2026 13:30:51 +0200 Subject: [PATCH 184/638] ROX-36628: bump claircore to pseudo-version with ecosystem-filter fix (#22585) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 67dab160c2516..9c7f6cb0b99ec 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.70.1 - github.com/quay/claircore v1.5.54 + github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96 github.com/quay/claircore/toolkit v1.6.1 github.com/quay/zlog/v2 v2.1.1 github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 diff --git a/go.sum b/go.sum index d4adb3a119820..fca5070e86e69 100644 --- a/go.sum +++ b/go.sum @@ -1173,8 +1173,8 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/quay/claircore v1.5.54 h1:NC7+QfwV8yo/KJ/B8mz1GD8BIG16aIJ3FuHcBTnsjL4= -github.com/quay/claircore v1.5.54/go.mod h1:hOp0JqY8vOZPdBakFRt3JwG8q4K+gnRkG9RGzt1DoB4= +github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96 h1:bpYvJiQTRbsyTPmdXX/a+05Hy5nHtCcjoswOPR/LgjM= +github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96/go.mod h1:hOp0JqY8vOZPdBakFRt3JwG8q4K+gnRkG9RGzt1DoB4= github.com/quay/claircore/toolkit v1.0.0/go.mod h1:3ELtgf92x7o1JCTSKVOAqhcnCTXc4s5qiGaEDx62i20= github.com/quay/claircore/toolkit v1.6.1 h1:a3X1v28n5bo05T94cBlaoPU8xYCErRGPM3l6AlmYWj4= github.com/quay/claircore/toolkit v1.6.1/go.mod h1:dTzlp1pgNchc+CN73HJiG9+e0ygU54QPt397eXnGGo4= From b213e16921b08bb23cf842268475c8b5dd74af06 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 3 Sep 2026 11:40:28 +0000 Subject: [PATCH 185/638] ROX-36250: add validation for node vulnerability reports (#22506) Co-authored-by: Claude Sonnet 4.5 --- central/reports/validation/validate.go | 313 ++++++--- .../validation/validate_comprehensive_test.go | 613 ++++++++++++++++++ central/reports/validation/validate_test.go | 13 +- 3 files changed, 847 insertions(+), 92 deletions(-) create mode 100644 central/reports/validation/validate_comprehensive_test.go diff --git a/central/reports/validation/validate.go b/central/reports/validation/validate.go index dd9ebfc085b5c..0823bfc4cf795 100644 --- a/central/reports/validation/validate.go +++ b/central/reports/validation/validate.go @@ -59,7 +59,7 @@ func New(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotDatastore // ValidateReportConfiguration validates the given report configuration object func (v *Validator) ValidateReportConfiguration(config *apiV2.ReportConfiguration) error { if config.GetName() == "" { - return errors.Wrap(errox.InvalidArgs, "Report configuration name is empty") + return errox.InvalidArgs.New("Report configuration name is empty") } if err := v.validateSchedule(config); err != nil { @@ -85,27 +85,27 @@ func (v *Validator) validateSchedule(config *apiV2.ReportConfiguration) error { } switch schedule.GetIntervalType() { case apiV2.ReportSchedule_UNSET: - return errors.Wrap(errox.InvalidArgs, "Report configuration schedule must be one of DAILY, WEEKLY, or MONTHLY") + return errox.InvalidArgs.New("report configuration schedule must be one of DAILY, WEEKLY, or MONTHLY") case apiV2.ReportSchedule_DAILY: if schedule.GetDaysOfWeek() != nil || schedule.GetDaysOfMonth() != nil { - return errors.Wrap(errox.InvalidArgs, "Daily schedule must not specify days of week or days of month") + return errox.InvalidArgs.New("daily schedule must not specify days of week or days of month") } case apiV2.ReportSchedule_WEEKLY: if schedule.GetDaysOfWeek() == nil || len(schedule.GetDaysOfWeek().GetDays()) == 0 { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify days of week for weekly schedule") + return errox.InvalidArgs.New("report configuration must specify days of week for weekly schedule") } for _, day := range schedule.GetDaysOfWeek().GetDays() { if day < 0 || day > 6 { - return errors.Wrap(errox.InvalidArgs, "Invalid schedule: Days of the week can be Sunday (0) - Saturday(6)") + return errox.InvalidArgs.New("invalid schedule: days of the week can be Sunday (0) - Saturday(6)") } } case apiV2.ReportSchedule_MONTHLY: if schedule.GetDaysOfMonth() == nil || len(schedule.GetDaysOfMonth().GetDays()) == 0 { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify days of the month for monthly schedule") + return errox.InvalidArgs.New("report configuration must specify days of the month for monthly schedule") } for _, day := range schedule.GetDaysOfMonth().GetDays() { if day != 1 && day != 15 { - return errors.Wrap(errox.InvalidArgs, "Reports can be sent out only 1st or 15th day of the month") + return errox.InvalidArgs.New("reports can be sent out only 1st or 15th day of the month") } } } @@ -116,13 +116,13 @@ func (v *Validator) validateNotifiers(config *apiV2.ReportConfiguration) error { notifiers := config.GetNotifiers() if len(notifiers) == 0 { if config.GetSchedule() != nil { - return errors.Wrap(errox.InvalidArgs, "Report configurations with a schedule must specify a notifier.") + return errox.InvalidArgs.New("report configurations with a schedule must specify a notifier") } return nil } for _, notifier := range notifiers { if notifier.GetEmailConfig() == nil { - return errors.Wrap(errox.InvalidArgs, "Notifier must specify an email notifier configuration") + return errox.InvalidArgs.New("notifier must specify an email notifier configuration") } if err := v.validateEmailConfig(notifier.GetEmailConfig()); err != nil { return err @@ -133,24 +133,24 @@ func (v *Validator) validateNotifiers(config *apiV2.ReportConfiguration) error { func (v *Validator) validateEmailConfig(emailConfig *apiV2.EmailNotifierConfiguration) error { if emailConfig.GetNotifierId() == "" { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid email notifier") + return errox.InvalidArgs.New("report configuration must specify a valid email notifier") } if len(emailConfig.GetMailingLists()) == 0 { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify at least one email recipient to send the report to") + return errox.InvalidArgs.New("report configuration must specify at least one email recipient to send the report to") } subjectMaxLen := env.ReportCustomEmailSubjectMaxLen.IntegerSetting() if len(emailConfig.GetCustomSubject()) > subjectMaxLen { - return errors.Wrapf(errox.InvalidArgs, "Custom email subject must be fewer than %d characters", subjectMaxLen) + return errox.InvalidArgs.Newf("custom email subject must be fewer than %d characters", subjectMaxLen) } bodyMaxLen := env.ReportCustomEmailBodyMaxLen.IntegerSetting() if len(emailConfig.GetCustomBody()) > bodyMaxLen { - return errors.Wrapf(errox.InvalidArgs, "Custom email body must be fewer than than %d characters", bodyMaxLen) + return errox.InvalidArgs.Newf("custom email body must be fewer than %d characters", bodyMaxLen) } errorList := errorhelpers.NewErrorList("Invalid email addresses in mailing list: ") for _, addr := range emailConfig.GetMailingLists() { if _, err := mail.ParseAddress(addr); err != nil { - errorList.AddError(errors.Wrapf(errox.InvalidArgs, "Invalid email recipient address: %s", addr)) + errorList.AddError(errox.InvalidArgs.Newf("invalid email recipient address: %s", addr)) } } if !errorList.Empty() { @@ -171,24 +171,54 @@ func (v *Validator) validateEmailConfig(emailConfig *apiV2.EmailNotifierConfigur func (v *Validator) validateResourceScope(config *apiV2.ReportConfiguration) error { scope := config.GetResourceScope() if scope == nil { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid resource scope") + return errox.InvalidArgs.New("report configuration must specify a valid resource scope") } + + // Nodes are cluster-level resources without namespace hierarchy + if config.GetType() == apiV2.ReportConfiguration_NODE_VULNERABILITY { + return v.validateNodeResourceScope(scope) + } + switch ref := scope.GetScopeReference().(type) { case *apiV2.ResourceScope_CollectionScope: return v.validateCollectionScope(ref.CollectionScope) case *apiV2.ResourceScope_EntityScope: if !features.VulnerabilityReportsEnhancedFiltering.Enabled() { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid collection as resource scope") + return errox.InvalidArgs.New("report configuration must specify a valid collection as resource scope") } return validateEntityScope(ref.EntityScope) default: - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid resource scope") + return errox.InvalidArgs.New("report configuration must specify a valid resource scope") + } +} + +func (v *Validator) validateNodeResourceScope(scope *apiV2.ResourceScope) error { + entityScope, ok := scope.GetScopeReference().(*apiV2.ResourceScope_EntityScope) + if !ok { + return errox.InvalidArgs.New("node vulnerability reports must use entity scope (cluster-based scoping only)") + } + + if entityScope.EntityScope == nil { + return errox.InvalidArgs.New("entity scope cannot be nil") + } + + if err := validateEntityScope(entityScope.EntityScope); err != nil { + return err + } + + // Nodes exist at cluster scope only - unlike deployments, they have no namespace association + for _, rule := range entityScope.EntityScope.GetRules() { + if rule.GetEntity() != apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER { + return errox.InvalidArgs.Newf("node vulnerability reports only support cluster-level scoping, got: %s", rule.GetEntity()) + } } + + return nil } func (v *Validator) validateCollectionScope(collectionRef *apiV2.CollectionReference) error { if collectionRef == nil || collectionRef.GetCollectionId() == "" { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid collection ID") + return errox.InvalidArgs.New("report configuration must specify a valid collection ID") } collectionID := collectionRef.GetCollectionId() // Use allAccessCtx since report creator/updater might not have permissions for workflowAdministrationSAC @@ -211,7 +241,7 @@ func (v *Validator) validateCollectionScope(collectionRef *apiV2.CollectionRefer // 5. a rule has no values, or a label rule contains values that are not in `key=value` format. func validateEntityScope(es *apiV2.EntityScope) error { if es == nil { - return errors.Wrap(errox.InvalidArgs, "Report configuration must specify a valid resource scope: either a collection scope with a valid collection ID or a non-nil entity scope") + return errox.InvalidArgs.New("report configuration must specify a valid resource scope: either a collection scope with a valid collection ID or a non-nil entity scope") } type entityFieldKey struct { entity apiV2.ScopeEntity @@ -220,22 +250,22 @@ func validateEntityScope(es *apiV2.EntityScope) error { seen := set.NewSet[entityFieldKey]() for _, rule := range es.GetRules() { if rule.GetEntity() == apiV2.ScopeEntity_SCOPE_ENTITY_UNSET { - return errors.Wrapf(errox.InvalidArgs, "Unexpected entity scope rule: %s", rule.GetEntity()) + return errox.InvalidArgs.Newf("unexpected entity scope rule: %s", rule.GetEntity()) } if rule.GetField() == apiV2.ScopeField_FIELD_UNSET { - return errors.Wrapf(errox.InvalidArgs, "Unexpected entity in scope rule for %s with an unset field", rule.GetEntity()) + return errox.InvalidArgs.Newf("unexpected entity in scope rule for %s with an unset field", rule.GetEntity()) } // Cluster annotation is not indexed and therefore unsupported. if rule.GetEntity() == apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER && rule.GetField() == apiV2.ScopeField_FIELD_ANNOTATION { - return errors.Wrap(errox.InvalidArgs, "Annotation field is not supported for cluster entity scope rules") + return errox.InvalidArgs.New("annotation field is not supported for cluster entity scope rules") } key := entityFieldKey{entity: rule.GetEntity(), field: rule.GetField()} if !seen.Add(key) { - return errors.Wrapf(errox.InvalidArgs, - "One rule per (entity, field) pair in entity scope rules is expected. Duplicate (entity, field) pair found: entity=%v field=%v. ", rule.GetEntity(), rule.GetField()) + return errox.InvalidArgs.Newf( + "one rule per (entity, field) pair in entity scope rules is expected; duplicate (entity, field) pair found: entity=%v field=%v", rule.GetEntity(), rule.GetField()) } if len(rule.GetValues()) == 0 { - return errors.Wrapf(errox.InvalidArgs, + return errox.InvalidArgs.Newf( "provide at least one matching value for entity=%v field=%v rule", rule.GetEntity(), rule.GetField()) } isMapField := rule.GetField() == apiV2.ScopeField_FIELD_LABEL || rule.GetField() == apiV2.ScopeField_FIELD_ANNOTATION @@ -244,19 +274,19 @@ func validateEntityScope(es *apiV2.EntityScope) error { if isMapField { mapKey, mapValue, found := strings.Cut(valOfValue, "=") if !found { - return errors.Wrapf(errox.InvalidArgs, "%v values must be in 'key=value' format", rule.GetField()) + return errox.InvalidArgs.Newf("%v values must be in 'key=value' format", rule.GetField()) } // Check the key for a Kubernetes qualified name. if rv.GetMatchType() == apiV2.MatchType_EXACT { if errs := k8sValidation.IsLabelKey(mapKey); len(errs) > 0 { - return errors.Wrapf(errox.InvalidArgs, "invalid %v key %q: %s", rule.GetField(), mapKey, strings.Join(errs, "; ")) + return errox.InvalidArgs.Newf("invalid %v key %q: %s", rule.GetField(), mapKey, strings.Join(errs, "; ")) } } valOfValue = mapValue } if rv.GetMatchType() == apiV2.MatchType_REGEX { if _, err := regexp.Compile(valOfValue); err != nil { - return errors.Wrapf(errox.InvalidArgs, "invalid regex %q: %v", valOfValue, err) + return errox.InvalidArgs.CausedByf("invalid regex %q: %v", valOfValue, err) } } } @@ -265,30 +295,67 @@ func validateEntityScope(es *apiV2.EntityScope) error { } func (v *Validator) validateReportFilters(config *apiV2.ReportConfiguration) error { - filters := config.GetVulnReportFilters() + switch config.GetType() { + case apiV2.ReportConfiguration_VULNERABILITY: + return v.validateImageFilters(config.GetVulnReportFilters()) + case apiV2.ReportConfiguration_NODE_VULNERABILITY: + return v.validateNodeFilters(config.GetNodeVulnReportFilters()) + default: + return errox.InvalidArgs.New("unsupported report type") + } +} + +func (v *Validator) validateImageFilters(filters *apiV2.VulnerabilityReportFilters) error { if filters == nil { - return errors.Wrap(errox.InvalidArgs, "Report configuration must include Vulnerability report filters") + return errox.InvalidArgs.New("report configuration must include vulnerability report filters") } if len(filters.GetImageTypes()) == 0 { - return errors.Wrap(errox.InvalidArgs, "Vulnerability report filters should specify which image types to scan for CVEs. "+ - "The valid options are 'DEPLOYED' and 'WATCHED'.") + return errox.InvalidArgs.New("vulnerability report filters should specify which image types to scan for CVEs; " + + "the valid options are 'DEPLOYED' and 'WATCHED'") } if filters.GetCvesSince() == nil { - return errors.Wrap(errox.InvalidArgs, "Vulnerability report filters must specify how far back in time to look for CVEs. "+ - "The valid options are 'sinceLastSentScheduledReport', 'allVuln', and 'startDate'") + return errox.InvalidArgs.New("vulnerability report filters must specify how far back in time to look for CVEs; " + + "the valid options are 'sinceLastSentScheduledReport', 'allVuln', and 'startDate'") } if features.VulnerabilityReportsEnhancedFiltering.Enabled() { if q := filters.GetQuery(); q != "" { if _, err := search.ParseQuery(q); err != nil { - return errors.Wrapf(errox.InvalidArgs, "Invalid query in vulnerability report filters: %s", err) + return errox.InvalidArgs.CausedByf("invalid query in vulnerability report filters: %v", err) } } } return nil } +func (v *Validator) validateNodeFilters(filters *apiV2.NodeVulnerabilityReportFilters) error { + if filters == nil { + return errox.InvalidArgs.New("node vulnerability report filters cannot be nil") + } + + // TODO: Add support for since_last_sent_scheduled_report and since_start_date filters + // once FirstNodeOccurrence timestamp is available in the data model + if filters.GetCvesSince() == nil { + return errox.InvalidArgs.New("node vulnerability report filters must specify CVE time filter") + } + + switch filters.GetCvesSince().(type) { + case *apiV2.NodeVulnerabilityReportFilters_AllVuln: + // Valid - this is the supported type + default: + return errox.InvalidArgs.New("only all vulnerabilities filter is currently supported for node reports (since_last_sent and since_start_date require FirstNodeOccurrence timestamp)") + } + + if q := filters.GetQuery(); q != "" { + if _, err := search.ParseQuery(q); err != nil { + return errox.InvalidArgs.CausedByf("invalid query in node vulnerability report filters: %v", err) + } + } + + return nil +} + // ValidateAndGenerateReportRequest validates the report configuration for which report is requested and generates a report request func (v *Validator) ValidateAndGenerateReportRequest( configID string, @@ -305,14 +372,14 @@ func (v *Validator) ValidateAndGenerateReportRequest( } // Verify ResourceScope is non-nil if !common.HasValidResourceScope(config.GetResourceScope()) { - return nil, errors.Wrapf(errox.InvalidArgs, - "Report configuration '%s' has an empty resource scope (no collection ID or entity scope)", + return nil, errox.InvalidArgs.Newf( + "report configuration '%s' has an empty resource scope (no collection ID or entity scope)", configID) } if notificationMethod == storage.ReportStatus_EMAIL && len(config.GetNotifiers()) == 0 { - return nil, errors.Wrap(errox.InvalidArgs, - "Email request sent for a report configuration that does not have any email notifiers configured") + return nil, errox.InvalidArgs.New( + "email request sent for a report configuration that does not have any email notifiers configured") } var collection *storage.ResourceCollection @@ -454,13 +521,33 @@ func generateReportSnapshot( requestType storage.ReportStatus_RunMethod, requesterID authn.Identity, ) *storage.ReportSnapshot { + var requester *storage.SlimUser + switch requestType { + case storage.ReportStatus_ON_DEMAND: + requester = &storage.SlimUser{ + Id: requesterID.UID(), + Name: stringutils.FirstNonEmpty(requesterID.FullName(), requesterID.FriendlyName()), + } + case storage.ReportStatus_SCHEDULED: + requester = config.GetCreator() + } + + var snapshotType storage.ReportSnapshot_ReportType + switch config.GetType() { + case storage.ReportConfiguration_VULNERABILITY: + snapshotType = storage.ReportSnapshot_VULNERABILITY + case storage.ReportConfiguration_NODE_VULNERABILITY: + snapshotType = storage.ReportSnapshot_NODE_VULNERABILITY + } + snapshot := &storage.ReportSnapshot{ ReportConfigurationId: config.GetId(), Name: config.GetName(), Description: config.GetDescription(), - Type: storage.ReportSnapshot_VULNERABILITY, + Type: snapshotType, Collection: collectionSnapshot(collection), Schedule: config.GetSchedule(), + Requester: requester, ReportStatus: &storage.ReportStatus{ RunState: storage.ReportStatus_WAITING, ReportRequestType: requestType, @@ -468,25 +555,33 @@ func generateReportSnapshot( }, } - if features.VulnerabilityReportsEnhancedFiltering.Enabled() { + if features.VulnerabilityReportsEnhancedFiltering.Enabled() || config.GetType() == storage.ReportConfiguration_NODE_VULNERABILITY { snapshot.ResourceScope = config.GetResourceScope().CloneVT() } - reportFilters := config.GetVulnReportFilters().CloneVT() - var requester *storage.SlimUser - switch requestType { - case storage.ReportStatus_ON_DEMAND: - reportFilters.AccessScopeRules = common.ExtractAccessScopeRules(requesterID) - requester = &storage.SlimUser{ - Id: requesterID.UID(), - Name: stringutils.FirstNonEmpty(requesterID.FullName(), requesterID.FriendlyName()), + switch config.GetType() { + case storage.ReportConfiguration_VULNERABILITY: + reportFilters := config.GetVulnReportFilters() + if reportFilters != nil { + reportFilters = reportFilters.CloneVT() + if requestType == storage.ReportStatus_ON_DEMAND { + reportFilters.AccessScopeRules = common.ExtractAccessScopeRules(requesterID) + } + snapshot.Filter = &storage.ReportSnapshot_VulnReportFilters{ + VulnReportFilters: reportFilters, + } + } + case storage.ReportConfiguration_NODE_VULNERABILITY: + nodeFilters := config.GetNodeVulnReportFilters() + if nodeFilters != nil { + nodeFilters = nodeFilters.CloneVT() + if requestType == storage.ReportStatus_ON_DEMAND { + nodeFilters.AccessScopeRules = common.ExtractAccessScopeRules(requesterID) + } + snapshot.Filter = &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: nodeFilters, + } } - case storage.ReportStatus_SCHEDULED: - requester = config.GetCreator() - } - snapshot.Requester = requester - snapshot.Filter = &storage.ReportSnapshot_VulnReportFilters{ - VulnReportFilters: reportFilters, } notifierSnaps := make([]*storage.NotifierSnapshot, 0, len(config.GetNotifiers())) @@ -542,44 +637,90 @@ func (v *Validator) ValidateAndGenerateViewBasedReportRequest( requesterID authn.Identity, ) (*reportGen.ReportRequest, error) { if req == nil { - return nil, errors.Wrap(errox.InvalidArgs, "Empty request") - } - - // Currently only vulnerability view-based reports are supported. - if req.GetType() != apiV2.ReportRequestViewBased_VULNERABILITY { - return nil, errors.Wrap(errox.InvalidArgs, "unsupported report type") - } - - // Validate filters. - vbFilters := req.GetViewBasedVulnReportFilters() - if vbFilters == nil { - return nil, errors.Wrap(errox.InvalidArgs, "view-based vulnerability report filters must be provided") + return nil, errox.InvalidArgs.New("empty request") } - // Convert API filters to storage filters. - storageFilters := &storage.ViewBasedVulnerabilityReportFilters{ - Query: vbFilters.GetQuery(), - AccessScopeRules: common.ExtractAccessScopeRules(requesterID), - } requester := &storage.SlimUser{ Id: requesterID.UID(), Name: stringutils.FirstNonEmpty(requesterID.FullName(), requesterID.FriendlyName()), } - // Build report snapshot. - snapshot := &storage.ReportSnapshot{ - Name: generateViewBasedRequestName(requester), - Type: storage.ReportSnapshot_VULNERABILITY, - AreaOfConcern: req.GetAreaOfConcern(), - ReportStatus: &storage.ReportStatus{ - RunState: storage.ReportStatus_WAITING, - ReportRequestType: storage.ReportStatus_VIEW_BASED, - ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, - }, - Filter: &storage.ReportSnapshot_ViewBasedVulnReportFilters{ - ViewBasedVulnReportFilters: storageFilters, - }, - Requester: requester, + var snapshot *storage.ReportSnapshot + + switch req.GetType() { + case apiV2.ReportRequestViewBased_VULNERABILITY: + // Validate filters. + vbFilters := req.GetViewBasedVulnReportFilters() + if vbFilters == nil { + return nil, errox.InvalidArgs.New("view-based vulnerability report filters must be provided") + } + + // Convert API filters to storage filters. + storageFilters := &storage.ViewBasedVulnerabilityReportFilters{ + Query: vbFilters.GetQuery(), + AccessScopeRules: common.ExtractAccessScopeRules(requesterID), + } + + // Build report snapshot. + snapshot = &storage.ReportSnapshot{ + Name: generateViewBasedRequestName(requester), + Type: storage.ReportSnapshot_VULNERABILITY, + AreaOfConcern: req.GetAreaOfConcern(), + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_WAITING, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + Filter: &storage.ReportSnapshot_ViewBasedVulnReportFilters{ + ViewBasedVulnReportFilters: storageFilters, + }, + Requester: requester, + } + + case apiV2.ReportRequestViewBased_NODE_VULNERABILITY: + // Validate filters. + nodeFilters := req.GetNodeVulnReportFilters() + if nodeFilters == nil { + return nil, errox.InvalidArgs.New("node vulnerability report filters must be provided") + } + + if err := v.validateNodeFilters(nodeFilters); err != nil { + return nil, err + } + + // Convert API filters to storage filters. + storageFilters := &storage.NodeVulnerabilityReportFilters{ + Query: nodeFilters.GetQuery(), + AccessScopeRules: common.ExtractAccessScopeRules(requesterID), + } + + switch nodeFilters.GetCvesSince().(type) { + case *apiV2.NodeVulnerabilityReportFilters_AllVuln: + storageFilters.CvesSince = &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: nodeFilters.GetAllVuln(), + } + default: + return nil, errox.InvalidArgs.New("unsupported CVE time filter for node vulnerability reports") + } + + // Build report snapshot. + snapshot = &storage.ReportSnapshot{ + Name: generateViewBasedRequestName(requester), + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + AreaOfConcern: req.GetAreaOfConcern(), + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_WAITING, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + Filter: &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: storageFilters, + }, + Requester: requester, + } + + default: + return nil, errox.InvalidArgs.New("unsupported report type") } return &reportGen.ReportRequest{ diff --git a/central/reports/validation/validate_comprehensive_test.go b/central/reports/validation/validate_comprehensive_test.go new file mode 100644 index 0000000000000..4fe02a86e45c6 --- /dev/null +++ b/central/reports/validation/validate_comprehensive_test.go @@ -0,0 +1,613 @@ +package validation + +import ( + "testing" + + notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" + reportConfigDSMocks "github.com/stackrox/rox/central/reports/config/datastore/mocks" + snapshotDSMocks "github.com/stackrox/rox/central/reports/snapshot/datastore/mocks" + collectionDSMocks "github.com/stackrox/rox/central/resourcecollection/datastore/mocks" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestValidateReportConfiguration(t *testing.T) { + t.Setenv(features.VulnerabilityReportsEnhancedFiltering.EnvVar(), "true") + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + config *apiV2.ReportConfiguration + setupMocks func() + expectError bool + errContains string + }{ + "empty name fails": { + config: &apiV2.ReportConfiguration{ + Name: "", + }, + setupMocks: func() {}, + expectError: true, + errContains: "name is empty", + }, + "scheduled report without notifiers fails": { + config: &apiV2.ReportConfiguration{ + Name: "Test Report", + Schedule: &apiV2.ReportSchedule{ + IntervalType: apiV2.ReportSchedule_DAILY, + Hour: 9, + Minute: 0, + }, + }, + setupMocks: func() {}, + expectError: true, + errContains: "must specify a notifier", + }, + "missing resource scope fails": { + config: &apiV2.ReportConfiguration{ + Name: "Test Report", + }, + setupMocks: func() {}, + expectError: true, + errContains: "must specify a valid resource scope", + }, + "valid node report with entity scope": { + config: &apiV2.ReportConfiguration{ + Name: "Node Report", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "prod", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + }, + Filter: &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + }, + setupMocks: func() {}, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.setupMocks() + err := validator.ValidateReportConfiguration(tc.config) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateNotifiers(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + config *apiV2.ReportConfiguration + setupMocks func() + expectError bool + errContains string + }{ + "no notifiers without schedule is valid": { + config: &apiV2.ReportConfiguration{ + Notifiers: nil, + Schedule: nil, + }, + setupMocks: func() {}, + expectError: false, + }, + "notifier without email config fails": { + config: &apiV2.ReportConfiguration{ + Notifiers: []*apiV2.NotifierConfiguration{ + {}, + }, + }, + setupMocks: func() {}, + expectError: true, + errContains: "must specify an email notifier configuration", + }, + "valid notifier": { + config: &apiV2.ReportConfiguration{ + Notifiers: []*apiV2.NotifierConfiguration{ + { + NotifierConfig: &apiV2.NotifierConfiguration_EmailConfig{ + EmailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{"user@example.com"}, + }, + }, + }, + }, + }, + setupMocks: func() { + notifierDS.EXPECT().Exists(gomock.Any(), "notifier-1").Return(true, nil) + }, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.setupMocks() + err := validator.validateNotifiers(tc.config) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateEmailConfig(t *testing.T) { + tests := map[string]struct { + emailConfig *apiV2.EmailNotifierConfiguration + setupMocks func(*notifierDSMocks.MockDataStore) + expectError bool + errContains string + }{ + "empty notifier ID fails": { + emailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "", + MailingLists: []string{"user@example.com"}, + }, + setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) {}, + expectError: true, + errContains: "must specify a valid email notifier", + }, + "empty mailing list fails": { + emailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{}, + }, + setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) {}, + expectError: true, + errContains: "must specify at least one email recipient", + }, + "invalid email address fails": { + emailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{"invalid-email"}, + }, + setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) {}, + expectError: true, + errContains: "invalid email recipient address", + }, + "notifier not found fails": { + emailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "missing-notifier", + MailingLists: []string{"user@example.com"}, + }, + setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) { + notifierDS.EXPECT().Exists(gomock.Any(), "missing-notifier").Return(false, nil) + }, + expectError: true, + errContains: "not found", + }, + "valid email config": { + emailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{"user@example.com", "admin@example.com"}, + CustomSubject: "Test Report", + CustomBody: "Report body", + }, + setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) { + notifierDS.EXPECT().Exists(gomock.Any(), "notifier-1").Return(true, nil) + }, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Create fresh mocks for each subtest to avoid mock conflicts + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tc.setupMocks(notifierDS) + err := validator.validateEmailConfig(tc.emailConfig) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateNodeResourceScope(t *testing.T) { + t.Setenv(features.VulnerabilityReportsEnhancedFiltering.EnvVar(), "true") + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + scope *apiV2.ResourceScope + expectError bool + errContains string + }{ + "collection scope not allowed for nodes": { + scope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_CollectionScope{ + CollectionScope: &apiV2.CollectionReference{ + CollectionId: "collection-1", + }, + }, + }, + expectError: true, + errContains: "must use entity scope", + }, + "nil entity scope fails": { + scope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: nil, + }, + }, + expectError: true, + errContains: "cannot be nil", + }, + "namespace scope not allowed for nodes": { + scope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_NAMESPACE, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "default", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + }, + expectError: true, + errContains: "only support cluster-level scoping", + }, + "valid cluster scope": { + scope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "prod", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + }, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := validator.validateNodeResourceScope(tc.scope) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateCollectionScope(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + collectionRef *apiV2.CollectionReference + setupMocks func() + expectError bool + errContains string + }{ + "nil collection reference fails": { + collectionRef: nil, + setupMocks: func() {}, + expectError: true, + errContains: "must specify a valid collection ID", + }, + "empty collection ID fails": { + collectionRef: &apiV2.CollectionReference{ + CollectionId: "", + }, + setupMocks: func() {}, + expectError: true, + errContains: "must specify a valid collection ID", + }, + "collection not found fails": { + collectionRef: &apiV2.CollectionReference{ + CollectionId: "missing-collection", + }, + setupMocks: func() { + collectionDS.EXPECT().Exists(gomock.Any(), "missing-collection").Return(false, nil) + }, + expectError: true, + errContains: "not found", + }, + "valid collection": { + collectionRef: &apiV2.CollectionReference{ + CollectionId: "collection-1", + }, + setupMocks: func() { + collectionDS.EXPECT().Exists(gomock.Any(), "collection-1").Return(true, nil) + }, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.setupMocks() + err := validator.validateCollectionScope(tc.collectionRef) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateNodeFilters(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + filters *apiV2.NodeVulnerabilityReportFilters + expectError bool + errContains string + }{ + "nil filters fail": { + filters: nil, + expectError: true, + errContains: "cannot be nil", + }, + "missing CVE time filter fails": { + filters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: nil, + }, + expectError: true, + errContains: "must specify CVE time filter", + }, + "valid all vulnerabilities filter": { + filters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + expectError: false, + }, + "valid filter with query": { + filters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + Query: "Cluster:prod", + }, + expectError: false, + }, + "invalid query fails": { + filters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + Query: "invalid query syntax [", + }, + expectError: true, + errContains: "invalid query", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := validator.validateNodeFilters(tc.filters) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateResourceScope(t *testing.T) { + t.Setenv(features.VulnerabilityReportsEnhancedFiltering.EnvVar(), "true") + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + config *apiV2.ReportConfiguration + setupMocks func() + expectError bool + errContains string + }{ + "nil scope fails": { + config: &apiV2.ReportConfiguration{ + Type: apiV2.ReportConfiguration_VULNERABILITY, + ResourceScope: nil, + }, + setupMocks: func() {}, + expectError: true, + errContains: "must specify a valid resource scope", + }, + "valid collection scope for vulnerability report": { + config: &apiV2.ReportConfiguration{ + Type: apiV2.ReportConfiguration_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_CollectionScope{ + CollectionScope: &apiV2.CollectionReference{ + CollectionId: "collection-1", + }, + }, + }, + }, + setupMocks: func() { + collectionDS.EXPECT().Exists(gomock.Any(), "collection-1").Return(true, nil) + }, + expectError: false, + }, + "valid entity scope for vulnerability report": { + config: &apiV2.ReportConfiguration{ + Type: apiV2.ReportConfiguration_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "prod", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + }, + }, + setupMocks: func() {}, + expectError: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.setupMocks() + err := validator.validateResourceScope(tc.config) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateCancelReportRequest(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + tests := map[string]struct { + reportID string + setupMocks func() + expectError bool + errContains string + }{ + "snapshot not found": { + reportID: "missing-report", + setupMocks: func() { + snapshotDS.EXPECT().Get(gomock.Any(), "missing-report").Return(nil, false, nil) + }, + expectError: true, + errContains: "not found", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tc.setupMocks() + requester := &storage.SlimUser{ + Id: "user-1", + Name: "Test User", + } + err := validator.ValidateCancelReportRequest(tc.reportID, requester) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/central/reports/validation/validate_test.go b/central/reports/validation/validate_test.go index 1c0d8aa065a97..3328409deffcd 100644 --- a/central/reports/validation/validate_test.go +++ b/central/reports/validation/validate_test.go @@ -64,7 +64,7 @@ func TestValidateEntityScope(t *testing.T) { makeRule(apiV2.ScopeEntity_SCOPE_ENTITY_UNSET, apiV2.ScopeField_FIELD_NAME, exactValue("x")), ), expectError: true, - errContains: "Unexpected entity scope rule:", + errContains: "unexpected entity scope rule:", }, "unset field": { scope: makeEntityScope( @@ -79,7 +79,7 @@ func TestValidateEntityScope(t *testing.T) { makeRule(apiV2.ScopeEntity_SCOPE_ENTITY_DEPLOYMENT, apiV2.ScopeField_FIELD_NAME, exactValue("b")), ), expectError: true, - errContains: "Duplicate", + errContains: "duplicate", }, "no values in rule": { scope: makeEntityScope( @@ -126,7 +126,7 @@ func TestValidateEntityScope(t *testing.T) { makeRule(apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, apiV2.ScopeField_FIELD_ANNOTATION, exactValue("k=v")), ), expectError: true, - errContains: "Annotation", + errContains: "annotation", }, "annotation value missing equals sign": { scope: makeEntityScope( @@ -214,7 +214,7 @@ func TestValidateSchedule(t *testing.T) { }, }, expectError: true, - errContains: "Daily schedule must not specify", + errContains: "daily schedule must not specify", }, { testname: "Daily schedule with days_of_month is invalid", @@ -229,7 +229,7 @@ func TestValidateSchedule(t *testing.T) { }, }, expectError: true, - errContains: "Daily schedule must not specify", + errContains: "daily schedule must not specify", }, { testname: "Valid weekly schedule", @@ -293,6 +293,7 @@ func TestValidateReportFiltersQuery(t *testing.T) { v := &Validator{} baseFilters := func(query string) *apiV2.ReportConfiguration { return &apiV2.ReportConfiguration{ + Type: apiV2.ReportConfiguration_VULNERABILITY, Filter: &apiV2.ReportConfiguration_VulnReportFilters{ VulnReportFilters: &apiV2.VulnerabilityReportFilters{ ImageTypes: []apiV2.VulnerabilityReportFilters_ImageType{apiV2.VulnerabilityReportFilters_DEPLOYED}, @@ -319,7 +320,7 @@ func TestValidateReportFiltersQuery(t *testing.T) { err := v.validateReportFilters(baseFilters(tc.query)) if tc.expectError { assert.Error(t, err) - assert.Contains(t, err.Error(), "Invalid query") + assert.Contains(t, err.Error(), "invalid query") } else { assert.NoError(t, err) } From a943bf50b453438462dfe5e47b616fc4725a9394 Mon Sep 17 00:00:00 2001 From: Kirk Bater Date: Thu, 3 Sep 2026 08:57:06 -0400 Subject: [PATCH 186/638] ROX-36677: remove disabled ui elements for ScannerV2 (#22558) --- .../legacyScannerFeatureFlag.test.ts | 19 ++--- .../platformCves/clusterDetailPage.test.ts | 50 ------------ .../platformCves/cveDetailPage.test.ts | 61 --------------- .../platformCvesFeatureFlag.test.ts | 55 -------------- .../platformCvesOverviewPage.test.ts | 76 ------------------- .../platform/src/Containers/MainPage/Body.tsx | 17 ----- .../SystemHealth/SystemHealthPage.tsx | 35 +-------- 7 files changed, 9 insertions(+), 304 deletions(-) delete mode 100644 ui/apps/platform/cypress/integration/vulnerabilities/platformCves/clusterDetailPage.test.ts delete mode 100644 ui/apps/platform/cypress/integration/vulnerabilities/platformCves/cveDetailPage.test.ts delete mode 100644 ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesFeatureFlag.test.ts delete mode 100644 ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesOverviewPage.test.ts diff --git a/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts b/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts index 54c06f39e768e..dab6f1c1e4ad1 100644 --- a/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts +++ b/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts @@ -2,7 +2,7 @@ import dateFns from 'date-fns'; import withAuth from '../helpers/basicAuth'; import { interceptAndOverrideFeatureFlags } from '../helpers/request'; -import { visit } from '../helpers/visit'; +import { assertCannotFindThePage, visit } from '../helpers/visit'; import { credentialForScannerExpiryAlias, integrationHealthVulnDefinitionsAlias, @@ -22,8 +22,6 @@ const scannerCredentialExpiryBanner = '.pf-v6-c-banner:contains("Scanner certifi const stackRoxScannerTileSelector = '[data-testid="integration-tile"]:contains("StackRox Scanner")'; const nodeCveScannerInfoBoxSelector = '.pf-v6-c-alert:contains("StackRox Scanner")'; -const disabledMessage = 'disabled by your administrator'; - describe('Legacy Scanner feature flag (ROX_LEGACY_SCANNER)', () => { withAuth(); @@ -56,30 +54,29 @@ describe('Legacy Scanner feature flag (ROX_LEGACY_SCANNER)', () => { cy.get(scannerCredentialExpiryBanner).should('not.exist'); }); - it('should show disabled vuln definitions card with message on System Health page', () => { + it('should not show vuln definitions card on System Health page', () => { visitSystemHealthWithKeysRemoved([ credentialForScannerExpiryAlias, integrationHealthVulnDefinitionsAlias, ]); - cy.get(vulnDefinitionsCardSelector).should('contain', disabledMessage); + cy.get(vulnDefinitionsCardSelector).should('not.exist'); }); - it('should show disabled scanner certificate card with message on System Health page', () => { + it('should not show scanner certificate card on System Health page', () => { visitSystemHealthWithKeysRemoved([ credentialForScannerExpiryAlias, integrationHealthVulnDefinitionsAlias, ]); - cy.get(scannerCertificateCardSelector).should('contain', disabledMessage); + cy.get(scannerCertificateCardSelector).should('not.exist'); }); - it('should show disabled feature page for Platform CVEs', () => { + it('should show generic 404 page for Platform CVEs', () => { visit('/main/vulnerabilities/platform-cves'); - cy.get('h1').should('contain', 'Kubernetes components'); - cy.get('body').should('contain', disabledMessage); - cy.get('a:contains("Go to Vulnerability Management")').should('exist'); + assertCannotFindThePage(); + cy.get('a:contains("Go to Vulnerability Management")').should('not.exist'); }); }); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/clusterDetailPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/clusterDetailPage.test.ts deleted file mode 100644 index 180dddf4c33d1..0000000000000 --- a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/clusterDetailPage.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import withAuth from '../../../helpers/basicAuth'; - -describe('Platform CVEs - Cluster Detail Page', () => { - withAuth(); - - it('should restrict access to users with insufficient permissions', () => { - // check that users without Cluster access cannot access the Cluster Detail page directly - }); - - it('should only show relevant filters for the Cluster Detail page', () => { - // check the advanced filters and ensure only the relevant filters are displayed - }); - - it('should link to the correct pages', () => { - // clicking the Cluster breadcrumb should navigate to the overview page with the Cluster tab selected - // clicking a CVE name in the list should navigate to the correct Platform CVE details page - }); - - it('should sort CVE table columns', () => { - // check sorting of CVE column - // check sorting of CVE status column - // check sorting of CVE type column - // check sorting of CVSS column - }); - - it('should filter the CVE table', () => { - // filtering by CVE name should only display rows with a matching name - // filtering by CVE Status should only display rows with a matching status - // filtering by CVE Type should only display rows with a matching type - // filtering by CVSS should only display rows with a CVSS in range - // clearing filters should remove all filter chips and filter from the URL - }); - - it('should correctly paginate the CVE table', () => { - // visit the location and save the list of CVE names with a default perPage - // visit the location with perPage=2 in the URL - // should only display the first 2 rows of the previous list - // paginating to the next page should display the following two rows - // go to page 1, then page 2 - // applying a filter should reset the page to 1 - // go to page 1, then page 2 - // go to next page, applying a sort should reset the page to 1 - }); - - it('should correctly update summary cards when a filter is applied', () => { - // apply a Fixable status filter and ensure that Not Fixable displays "Results hidden" - // clear filters - // apply a CVE Type filter and ensure that all other types read "Results hidden" in the card - }); -}); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/cveDetailPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/cveDetailPage.test.ts deleted file mode 100644 index cb91ef240b999..0000000000000 --- a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/cveDetailPage.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import withAuth from '../../../helpers/basicAuth'; - -// TODO - classic tests conditionally skip many of these checks when running on OpenShift, we -// need to verify if this is still necessary and if so, how to handle it in the new tests -describe('Platform CVEs - CVE Detail Page', () => { - withAuth(); - - it('should restrict access to users with insufficient permissions', () => { - // check that users without Cluster access cannot access the Platform CVE Detail page directly - }); - - it('should only show relevant filters for the page', () => { - // check the advanced filters and ensure only the relevant filters are displayed - }); - - it('should link to the overview page from the breadcrumbs', () => { - // clicking the Platform CVEs breadcrumb should navigate to the overview page with the CVE tab selected - }); - - it('should link to the cluster page from the name links in the table', () => { - // clicking a Cluster name in the list should navigate to the correct Cluster details page - }); - - it('should display the expected Cluster table columns', () => { - // check presence of Cluster column - // check presence of Cluster type column - // check presence of K8s version column - }); - - it('should sort Cluster table columns', () => { - // check sorting of Cluster column - // check sorting of Cluster type column - // check sorting of K8s version column - }); - - it('should filter the Cluster table', () => { - // filtering by Cluster name should only display rows with a matching name - // filtering by Cluster type should only display rows with a matching cluster type - // filtering by K8s version should only display rows matching the version - // clearing filters should remove all filter chips and filter from the URL - }); - - // Note: This might not be reliable in CI due to the low number of Clusters. We may need to mock and/or - // just test the logic of the pagination in the URL. - it('should paginate the Cluster table', () => { - // visit the location and save the list of Cluster names with a default perPage - // visit the location with perPage=2 in the URL - // should only display the first 2 rows of the previous list - // paginating to the next page should display the following two rows - // go to page 1, then page 2 - // applying a filter should reset the page to 1 - // go to page 1, then page 2 - // go to next page, applying a sort should reset the page to 1 - }); - - it('should update summary cards when a filter is applied', () => { - // apply a filter for a Cluster name and ensure that "affected clusters" contains the text: 1/n affected clusters - // clear filters - // apply a CVE status filter and ensure that the opposite status reads "Results hidden" in the card - }); -}); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesFeatureFlag.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesFeatureFlag.test.ts deleted file mode 100644 index 46208a73156d3..0000000000000 --- a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesFeatureFlag.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { RouteHandler, RouteMatcherOptions } from 'cypress/types/net-stubbing'; - -import withAuth from '../../../helpers/basicAuth'; -import { interceptAndOverrideFeatureFlags } from '../../../helpers/request'; -import { visit } from '../../../helpers/visit'; -import pf6 from '../../../selectors/pf6'; - -export function visitPlatformCvesOverviewPage( - routeMatcherMap?: Record, - staticResponseMap?: Record -) { - visit('/main/vulnerabilities/platform-cves', routeMatcherMap, staticResponseMap); -} - -describe('Platform CVEs - Feature Flag Gating', () => { - withAuth(); - - describe('when ROX_LEGACY_SCANNER is enabled', () => { - beforeEach(() => { - interceptAndOverrideFeatureFlags({ ROX_LEGACY_SCANNER: true }); - }); - - it('should show the "Kubernetes components" item in the More Views dropdown', () => { - visit('/main/vulnerabilities/user-workloads'); - cy.get(`${pf6.menuToggle}:contains("More Views")`).click(); - cy.get(`${pf6.dropdownItem}:contains("Kubernetes components")`).should('exist'); - }); - - it('should render the Kubernetes components page when navigated to directly', () => { - visitPlatformCvesOverviewPage(); - - cy.get('h1').should('contain', 'Kubernetes components'); - }); - }); - - describe('when ROX_LEGACY_SCANNER is disabled', () => { - beforeEach(() => { - interceptAndOverrideFeatureFlags({ ROX_LEGACY_SCANNER: false }); - }); - - it('should not show the "Kubernetes components" item in the More Views dropdown', () => { - visit('/main/vulnerabilities/user-workloads'); - cy.get(`${pf6.menuToggle}:contains("More Views")`).click(); - cy.get(`${pf6.dropdownItem}:contains("Kubernetes components")`).should('not.exist'); - }); - - it('should show a disabled feature page when navigated to directly', () => { - visitPlatformCvesOverviewPage(); - - cy.get('h1').should('contain', 'Kubernetes components'); - cy.get('body').should('contain', 'disabled by your administrator'); - cy.get('a:contains("Go to Vulnerability Management")').should('exist'); - }); - }); -}); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesOverviewPage.test.ts deleted file mode 100644 index e6582841ff4a3..0000000000000 --- a/ui/apps/platform/cypress/integration/vulnerabilities/platformCves/platformCvesOverviewPage.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import withAuth from '../../../helpers/basicAuth'; - -describe('Platform CVEs - Overview Page', () => { - withAuth(); - - it('should restrict access to users with insufficient permissions', () => { - // check that users without Cluster access do not see Platform CVEs in the navigation - // check that users without Cluster access cannot access the Platform CVEs page directly - }); - - it('should only show relevant filters for the Platform CVEs page', () => { - // check the advanced filters and ensure only the relevant filters are displayed for CVEs - // check the advanced filters and ensure only the relevant filters are displayed for Clusters - }); - - it('should link to the correct details pages', () => { - // clicking a CVE in the list should navigate to the correct CVE details page - // clicking a Cluster in the list should navigate to the correct Cluster details page - }); - - it('should sort CVE table columns', () => { - // check sorting of CVE column - // check sorting of CVE status column - // check sorting of CVE type column - // check sorting of CVSS column - // check sorting of Affected Clusters column - }); - - it('should sort Cluster table columns', () => { - // check sorting of Cluster column - // check sorting of CVE count column - // check sorting of Cluster type column - // check sorting of K8s version column - }); - - it('should filter the CVE table', () => { - // filtering by CVE name should only display rows with a matching name - // filtering by Status should only display rows with a matching status - // filtering by Type should only display rows with a matching type - // filtering by CVSS should only display rows with a CVSS in range - // clearing filters should remove all filter chips and filter from the URL - }); - - it('should filter the Cluster table', () => { - // filtering by Cluster name should only display rows with a matching name - // filtering by Cluster type should only display rows with a matching cluster type - // filtering by K8s version should only display rows matching the version - // clearing filters should remove all filter chips and filter from the URL - }); - - it('should correctly paginate the CVE table', () => { - // visit the location and save the list of CVE names with a default perPage - // visit the location with perPage=2 in the URL - // should only display the first 2 rows of the previous list - // paginating to the next page should display the following two rows - // go to page 1, then page 2 - // applying a filter should reset the page to 1 - // go to page 1, then page 2 - // go to next page, applying a sort should reset the page to 1 - // go to page 1, then page 2 - // click the "clusters" tab, then click back to the "cves" tab should reset the page to 1 - }); - - it('should correctly paginate the Cluster table', () => { - // visit the location and save the list of Cluster names with a default perPage - // visit the location with perPage=2 in the URL - // should only display the first 2 rows of the previous list - // paginating to the next page should display the following two rows - // go to page 1, then page 2 - // applying a filter should reset the page to 1 - // go to page 1, then page 2 - // go to next page, applying a sort should reset the page to 1 - // go to page 1, then page 2 - // click the "cves" tab, then click back to the "clusters" tab should reset the page to 1 - }); -}); diff --git a/ui/apps/platform/src/Containers/MainPage/Body.tsx b/ui/apps/platform/src/Containers/MainPage/Body.tsx index bb25fb03a5266..0e6c71de5979d 100644 --- a/ui/apps/platform/src/Containers/MainPage/Body.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Body.tsx @@ -58,7 +58,6 @@ import { } from 'routePaths'; import type { RouteKey } from 'routePaths'; -import FeatureDisabledMessage from 'Components/FeatureDisabledMessage'; import PageNotFound from 'Components/PageNotFound'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; @@ -383,22 +382,6 @@ function Body({ hasReadAccess, isFeatureFlagEnabled }: BodyProps): ReactElement const { component: Component, path } = routeComponentMap[routeKey]; return } />; })} - {!isFeatureFlagEnabled('ROX_LEGACY_SCANNER') && ( - - - - - } - /> - )} } /> {hasWriteAccessForInviting && showInviteModal && } diff --git a/ui/apps/platform/src/Containers/SystemHealth/SystemHealthPage.tsx b/ui/apps/platform/src/Containers/SystemHealth/SystemHealthPage.tsx index 35af7fe92200a..54b146c40e019 100644 --- a/ui/apps/platform/src/Containers/SystemHealth/SystemHealthPage.tsx +++ b/ui/apps/platform/src/Containers/SystemHealth/SystemHealthPage.tsx @@ -1,13 +1,5 @@ import { useState } from 'react'; -import { - Divider, - Flex, - FlexItem, - Grid, - GridItem, - PageSection, - Title, -} from '@patternfly/react-core'; +import { Flex, FlexItem, Grid, GridItem, PageSection, Title } from '@patternfly/react-core'; import useCentralCapabilities from 'hooks/useCentralCapabilities'; import useInterval from 'hooks/useInterval'; @@ -139,31 +131,6 @@ function SystemHealthPage() { /> )} - {!isLegacyScannerEnabled && ( - - - - )} - {!isLegacyScannerEnabled && hasReadAccessForAdministration && ( - - - - )} - {!isLegacyScannerEnabled && ( - - - - )} From 48907b1ed816a2fae58b61c25168b23ea945a24e Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 3 Sep 2026 09:09:40 -0400 Subject: [PATCH 187/638] ROX-36651: Add CreateReportDropdown for node vulnerability pages (#22563) --- .../Overview/NodeCvesOverviewPage.tsx | 38 +++++- .../components/CreateReportDropdown.tsx | 108 ++++++++++++++++++ .../nodeVulnerabilityReports.utils.ts | 13 +++ .../VulnReports/VulnReportingLayout.tsx | 7 +- .../Deployment/DeploymentPage.tsx | 6 +- .../WorkloadCves/Image/ImagePage.tsx | 6 +- .../Overview/WorkloadCvesOverviewPage.tsx | 6 +- .../components/CreateViewBasedReportModal.tsx | 13 ++- ui/apps/platform/src/routePaths.ts | 2 +- .../src/services/NodeReportsService.ts | 21 ++++ .../platform/src/services/ReportsService.ts | 2 +- .../src/services/ReportsService.types.ts | 14 ++- 12 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/components/CreateReportDropdown.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils.ts rename ui/apps/platform/src/Containers/Vulnerabilities/{WorkloadCves => }/components/CreateViewBasedReportModal.tsx (94%) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx index ce5c808743dfa..7ea63b0f3f033 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx @@ -1,4 +1,5 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom-v5-compat'; import { Alert, DropdownItem, @@ -26,12 +27,15 @@ import useAnalytics, { NODE_CVE_ENTITY_CONTEXT_VIEWED, NODE_CVE_FILTER_APPLIED, } from 'hooks/useAnalytics'; +import { runNodeViewBasedReport } from 'services/NodeReportsService'; import { getHasSearchApplied } from 'utils/searchUtils'; import { getVersionedDocs } from 'utils/versioning'; import { createFilterTracker } from 'utils/analyticsEventTracking'; +import { vulnerabilityNodeViewBasedJobsPath } from 'routePaths'; import { searchFilterConfigForNodeVulnerabilityResultsAndViewBasedReport } from '../../searchFilterConfig'; import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; +import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; import SnoozedCveToggleButton from '../../components/SnoozedCveToggleButton'; import SnoozeCvesModal from '../../components/SnoozeCvesModal/SnoozeCvesModal'; import useSnoozeCveModal from '../../components/SnoozeCvesModal/useSnoozeCveModal'; @@ -39,10 +43,12 @@ import useHasLegacySnoozeAbility from '../../hooks/useHasLegacySnoozeAbility'; import useSnoozedCveCount from '../../hooks/useSnoozedCveCount'; import TableEntityToolbar from '../../components/TableEntityToolbar'; import EntityTypeToggleGroup from '../../components/EntityTypeToggleGroup'; +import { createScheduledReportForNodeVulnerabilitiesURL } from '../../Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils'; import { nodeEntityTabValues } from '../../types'; import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; -import { parseQuerySearchFilter } from '../../utils/searchUtils'; +import { getRegexScopedQueryString, parseQuerySearchFilter } from '../../utils/searchUtils'; +import CreateReportDropdown from '../components/CreateReportDropdown'; import CVEsTable, { defaultSortOption as cveDefaultSortOption, sortFields as cveSortFields, @@ -82,6 +88,11 @@ function NodeCvesOverviewPage() { const { snoozeModalOptions, setSnoozeModalOptions, snoozeActionCreator } = useSnoozeCveModal(); const snoozedCveCount = useSnoozedCveCount('Node'); const { version } = useMetadata(); + const [isCreateViewBasedReportModalOpen, setIsCreateViewBasedReportModalOpen] = useState(false); + const navigate = useNavigate(); + + // Unlike WorkloadCves pages, 'CVE Snoozed' is in search filter of NodeCves page. + const viewBasedQueryString = getRegexScopedQueryString(querySearchFilter); function onEntityTabChange(entityTab: 'CVE' | 'Node') { pagination.setPage(1); @@ -133,7 +144,18 @@ function NodeCvesOverviewPage() { pagination.setPage(1); trackAppliedFilter(NODE_CVE_FILTER_APPLIED, searchPayload); }} - /> + > + {isFeatureFlagEnabled('ROX_NODE_VULNERABILITY_REPORTS') && ( + { + setIsCreateViewBasedReportModalOpen(true); + }} + onSelectCreateScheduledReport={() => { + navigate(createScheduledReportForNodeVulnerabilitiesURL(querySearchFilter)); + }} + /> + )} + ); const entityToggleGroup = ( @@ -264,6 +286,16 @@ function NodeCvesOverviewPage() { /> )} + {isFeatureFlagEnabled('ROX_NODE_VULNERABILITY_REPORTS') && ( + + )} ); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/components/CreateReportDropdown.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/components/CreateReportDropdown.tsx new file mode 100644 index 0000000000000..0b01da356feae --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/components/CreateReportDropdown.tsx @@ -0,0 +1,108 @@ +import { useState } from 'react'; +import type { MouseEvent as ReactMouseEvent, Ref } from 'react'; +import { Dropdown, DropdownItem, DropdownList, MenuToggle } from '@patternfly/react-core'; +import type { MenuToggleElement } from '@patternfly/react-core'; + +import useFeatureFlags from 'hooks/useFeatureFlags'; +import usePermissions from 'hooks/usePermissions'; +import type { FeatureFlagEnvVar } from 'types/featureFlag'; +import type { ResourceName } from 'types/roleResources'; +import { ensureExhaustive } from 'utils/type.utils'; + +// TODO move deduplicated file to Vulnerabilities/components folder after release. + +type DropdownItem = { + text: 'Export report as CSV' | 'Create scheduled report'; + description: string; + featureFlagDependency?: FeatureFlagEnvVar; + writeAccessRequirement?: ResourceName; +}; + +const dropdownItems: DropdownItem[] = [ + { + text: 'Export report as CSV', + description: + 'Export a view-based CSV report from this view using the filters you’ve applied.', + }, + { + text: 'Create scheduled report', + description: 'Create a scheduled report from this view using the filters you’ve applied.', + writeAccessRequirement: 'WorkflowAdministration', + }, +] as const; + +type DropdownItemText = (typeof dropdownItems)[number]['text']; + +export type CreateReportDropdownProps = { + onSelectExportReportAsCSV: () => void; + onSelectCreateScheduledReport: () => void; +}; + +function CreateReportDropdown({ + onSelectExportReportAsCSV, + onSelectCreateScheduledReport, +}: CreateReportDropdownProps) { + const [isOpen, setIsOpen] = useState(false); + const { isFeatureFlagEnabled } = useFeatureFlags(); + const { hasReadWriteAccess } = usePermissions(); + + const onToggleClick = () => { + setIsOpen((prev) => !prev); + }; + + const onSelectHandler = ( + _event: ReactMouseEvent | undefined, + value: DropdownItemText + ) => { + switch (value) { + case 'Export report as CSV': + onSelectExportReportAsCSV(); + break; + case 'Create scheduled report': + onSelectCreateScheduledReport(); + break; + default: + ensureExhaustive(value); + } + setIsOpen(false); + }; + + return ( + <> + setIsOpen(isOpen)} + toggle={(toggleRef: Ref) => ( + + Create report + + )} + shouldFocusToggleOnSelect + popperProps={{ position: 'right', appendTo: () => document.body }} + > + + {dropdownItems.map((dropdownItem) => { + const { description, text } = dropdownItem; + const isEnabled = + !('featureFlagDependency' in dropdownItem) || + !dropdownItem.featureFlagDependency || + isFeatureFlagEnabled(dropdownItem.featureFlagDependency); + const hasAccess = + !('writeAccessRequirement' in dropdownItem) || + !dropdownItem.writeAccessRequirement || + hasReadWriteAccess(dropdownItem.writeAccessRequirement); + + return isEnabled && hasAccess ? ( + + {text} + + ) : null; + })} + + + + ); +} + +export default CreateReportDropdown; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils.ts new file mode 100644 index 0000000000000..3ac1d2b19bb18 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils.ts @@ -0,0 +1,13 @@ +import type { SearchFilter } from 'types/search'; +import { getUrlQueryStringForSearchFilter } from 'utils/searchUtils'; +import { vulnerabilityNodeConfigurationsReportsPath } from 'routePaths'; + +// Unlike view-based report that receives request query directly, +// (create) scheduled report (from filter) receives it indirectly: +// 1. from search filter to URL query (navigate from results page to report wizard) +// 2. from URL query to search filter (initialize configuration in report wizard) +// 3. from search filter to request query (save report configuration) +export function createScheduledReportForNodeVulnerabilitiesURL(searchFilter: SearchFilter) { + const query = getUrlQueryStringForSearchFilter(searchFilter); + return `${vulnerabilityNodeConfigurationsReportsPath}?action=createFromFilters&${query}`; +} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx index 8aaf9773fed35..207571a810cdb 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx @@ -3,7 +3,10 @@ import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; -import { vulnerabilityConfigurationsReportsPath, vulnerabilityViewBasedJobsPath } from 'routePaths'; +import { + vulnerabilityConfigurationsReportsPath, + vulnerabilityImageViewBasedJobsPath, +} from 'routePaths'; import ConfigReportsTab from './ConfigReportsTab'; import ViewBasedReportsTab from './ViewBasedReportsTab'; @@ -29,7 +32,7 @@ function VulnReportingLayout() { { id: 'view-based-jobs', title: 'View-based jobs', - path: vulnerabilityViewBasedJobsPath, + path: vulnerabilityImageViewBasedJobsPath, content: , }, ]; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx index d5b5e5efaf2ef..781bc43717a38 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Deployment/DeploymentPage.tsx @@ -20,8 +20,10 @@ import TableErrorComponent from 'Components/PatternFly/TableErrorComponent'; import useURLStringUnion from 'hooks/useURLStringUnion'; import useURLPagination from 'hooks/useURLPagination'; import useURLSearch from 'hooks/useURLSearch'; +import { runImageViewBasedReport } from 'services/ReportsService'; import type { VulnerabilityState } from 'types/cve.proto'; import { wrapInQuotes } from 'utils/searchUtils'; +import { vulnerabilityImageViewBasedJobsPath } from 'routePaths'; import DeploymentPageHeader, { deploymentMetadataFragment } from './DeploymentPageHeader'; import type { DeploymentMetadata } from './DeploymentPageHeader'; @@ -34,7 +36,7 @@ import DeploymentPageDetails from './DeploymentPageDetails'; import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; import CreateReportDropdown from '../components/CreateReportDropdown'; -import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; +import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; const deploymentMetadataQuery = gql` ${deploymentMetadataFragment} @@ -194,6 +196,8 @@ function DeploymentPage({ showVulnerabilityStateTabs, vulnerabilityState }: Depl setIsOpen={setIsCreateViewBasedReportModalOpen} query={getRegexScopedQueryString(deploymentScopedSearchFilterForReport)} areaOfConcern={viewContext} + runViewBasedReport={runImageViewBasedReport} + vulnerabilityViewBasedJobsPath={vulnerabilityImageViewBasedJobsPath} /> )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx index 313d8edeedfc6..d3897e9ff80f5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx @@ -35,6 +35,8 @@ import useURLSearch from 'hooks/useURLSearch'; import type { ColumnConfigOverrides } from 'hooks/useManagedColumns'; import type { GenerateSbomImageParams } from 'services/ImageSbomService'; import type { VulnerabilityState } from 'types/cve.proto'; +import { runImageViewBasedReport } from 'services/ReportsService'; +import { vulnerabilityImageViewBasedJobsPath } from 'routePaths'; import HeaderLoadingSkeleton from '../../components/HeaderLoadingSkeleton'; import GenerateSbomModal, { @@ -58,7 +60,7 @@ import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; import type { defaultColumns as deploymentResourcesDefaultColumns } from './DeploymentResourceTable'; import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import CreateReportDropdown from '../components/CreateReportDropdown'; -import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; +import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; const imageDetailsQuery = gql` ${imageDetailsFragment} @@ -386,6 +388,8 @@ function ImagePage({ setIsOpen={setIsCreateViewBasedReportModalOpen} query={getRegexScopedQueryString(imageScopedSearchFilterForReport)} areaOfConcern={viewContext} + runViewBasedReport={runImageViewBasedReport} + vulnerabilityViewBasedJobsPath={vulnerabilityImageViewBasedJobsPath} /> )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx index 5ff471d3d1cd4..fa974e37a488d 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Overview/WorkloadCvesOverviewPage.tsx @@ -34,7 +34,9 @@ import type { SearchFilter } from 'types/search'; import { useIsFirstRender } from 'hooks/useIsFirstRender'; import { hideColumnIf } from 'hooks/useManagedColumns'; import useURLSort from 'hooks/useURLSort'; +import { runImageViewBasedReport } from 'services/ReportsService'; import type { VulnerabilityState } from 'types/cve.proto'; +import { vulnerabilityImageViewBasedJobsPath } from 'routePaths'; import { searchFilterConfigForWorkloadVulnerabilityResultsAndViewBasedReport } from '../../searchFilterConfig'; import { isVulnMgmtLocalStorage, workloadEntityTabValues } from '../../types'; @@ -63,7 +65,7 @@ import useVulnerabilityState from '../hooks/useVulnerabilityState'; import useWorkloadCveViewContext from '../hooks/useWorkloadCveViewContext'; import DefaultFilterModal from '../components/DefaultFilterModal'; import CreateReportDropdown from '../components/CreateReportDropdown'; -import CreateViewBasedReportModal from '../components/CreateViewBasedReportModal'; +import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; import { imageListQuery } from '../Tables/ImageOverviewTable'; import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils'; import useHasRequestExceptionsAbility from '../../hooks/useHasRequestExceptionsAbility'; @@ -483,6 +485,8 @@ function WorkloadCvesOverviewPage() { setIsOpen={setIsCreateViewBasedReportModalOpen} query={workloadCvesScopedQueryString} areaOfConcern={viewContext} + runViewBasedReport={runImageViewBasedReport} + vulnerabilityViewBasedJobsPath={vulnerabilityImageViewBasedJobsPath} /> )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/components/CreateViewBasedReportModal.tsx similarity index 94% rename from ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx rename to ui/apps/platform/src/Containers/Vulnerabilities/components/CreateViewBasedReportModal.tsx index 72c485cd823ef..7ea4e81375703 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/CreateViewBasedReportModal.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/components/CreateViewBasedReportModal.tsx @@ -3,9 +3,8 @@ import { Alert, Button, Flex, FlexItem } from '@patternfly/react-core'; import { Modal } from '@patternfly/react-core/deprecated'; import { Link } from 'react-router-dom'; -import { runViewBasedReport } from 'services/ReportsService'; -import { vulnerabilityViewBasedJobsPath } from 'routePaths'; import useAnalytics, { VIEW_BASED_REPORT_GENERATED } from 'hooks/useAnalytics'; +import type { RunReportResponseViewBased } from 'services/ReportsService.types'; type Message = | { @@ -27,6 +26,14 @@ export type CreateViewBasedReportModalProps = { setIsOpen: (value: boolean) => void; query: string; areaOfConcern: string; + runViewBasedReport: ({ + query, + areaOfConcern, + }: { + query: string; + areaOfConcern: string; + }) => Promise; + vulnerabilityViewBasedJobsPath: string; }; function CreateViewBasedReportModal({ @@ -34,6 +41,8 @@ function CreateViewBasedReportModal({ setIsOpen, query, areaOfConcern, + runViewBasedReport, + vulnerabilityViewBasedJobsPath, }: CreateViewBasedReportModalProps) { const { analyticsTrack } = useAnalytics(); const [isTriggeringReportGeneration, setIsTriggeringReportGeneration] = useState(false); diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index af9c27b3a5a77..8775d09796f03 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -101,7 +101,7 @@ export const vulnerabilitiesViewPath = `${vulnerabilitiesBasePath}/results/:view export const vulnerabilityReportsPath = `${vulnerabilitiesBasePath}/reports`; export const vulnerabilityImageReportsPath = `${vulnerabilityReportsPath}/images`; export const vulnerabilityConfigurationsReportsPath = `${vulnerabilityImageReportsPath}/configurations`; -export const vulnerabilityViewBasedJobsPath = `${vulnerabilityImageReportsPath}/view-based-jobs`; +export const vulnerabilityImageViewBasedJobsPath = `${vulnerabilityImageReportsPath}/view-based-jobs`; export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/nodes`; export const vulnerabilityNodeConfigurationsReportsPath = `${vulnerabilityNodeReportsPath}/configurations`; diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts index db29d908a34e8..336c72af8d32f 100644 --- a/ui/apps/platform/src/services/NodeReportsService.ts +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -13,6 +13,8 @@ import axios from './instance'; import type { NodeViewBasedReportSnapshot, NodeVulnerabilityReportConfiguration, + ReportRequestViewBased, + RunReportResponseViewBased, } from './ReportsService.types'; import type { Empty } from './types'; @@ -109,6 +111,25 @@ export function deleteNodeReportConfiguration(reportId: string): Promise // View-based jobs // PostViewBasedNodeReport +export function runNodeViewBasedReport({ + query, + areaOfConcern, +}: { + query: string; + areaOfConcern: string; +}): Promise { + const requestBody: ReportRequestViewBased = { + type: 'NODE_VULNERABILITY', + nodeVulnReportFilters: { + query, + }, + areaOfConcern, // 'Nodes' for analytics ony + }; + + return axios + .post('/v2/reports/node/view-based/run', requestBody) + .then((response) => response.data); +} // GetViewBasedNodeReportHistory export function getViewBasedNodeReportHistory({ diff --git a/ui/apps/platform/src/services/ReportsService.ts b/ui/apps/platform/src/services/ReportsService.ts index bb2180893c78c..d68fe3836b21a 100644 --- a/ui/apps/platform/src/services/ReportsService.ts +++ b/ui/apps/platform/src/services/ReportsService.ts @@ -215,7 +215,7 @@ export function fetchViewBasedReportHistory({ } // PostViewBasedReport -export function runViewBasedReport({ +export function runImageViewBasedReport({ query, areaOfConcern, }: { diff --git a/ui/apps/platform/src/services/ReportsService.types.ts b/ui/apps/platform/src/services/ReportsService.types.ts index 8c774d12a7d75..5c7fb837986e0 100644 --- a/ui/apps/platform/src/services/ReportsService.types.ts +++ b/ui/apps/platform/src/services/ReportsService.types.ts @@ -323,12 +323,22 @@ export type RunReportResponse = { reportId: string; }; -export type ReportRequestViewBased = { - type: ReportType; +export type ImageReportRequestViewBased = { + type: 'VULNERABILITY'; viewBasedVulnReportFilters: ViewBasedVulnerabilityReportFilters; areaOfConcern: string; }; +export type NodeReportRequestViewBased = { + type: 'NODE_VULNERABILITY'; + // Although backend proto has NodeVulnerabilityReportFilters type, + // only query is relevant for view-based report. + nodeVulnReportFilters: ViewBasedVulnerabilityReportFilters; + areaOfConcern: string; // 'Nodes' +}; + +export type ReportRequestViewBased = ImageReportRequestViewBased | NodeReportRequestViewBased; + export type RunReportResponseViewBased = { reportID: string; requestName: string; From 8c043fe7a5180c687ad26edcecd573bab7b88f12 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 3 Sep 2026 14:27:27 +0000 Subject: [PATCH 188/638] DPTP-5138: add DNS pre-flight check (#22423) Co-authored-by: Claude Sonnet 4.5 --- .openshift-ci/begin.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.openshift-ci/begin.sh b/.openshift-ci/begin.sh index fd9b736b76c6d..b5411a9922b01 100755 --- a/.openshift-ci/begin.sh +++ b/.openshift-ci/begin.sh @@ -22,6 +22,36 @@ openshift_ci_mods openshift_ci_import_creds setup_gcp + +# Pre-flight DNS check (mitigation for DPTP-5138) +info "Pre-flight DNS health check (DPTP-5138 mitigation)" + +_check_dns() { + local domain="$1" + getent hosts "$domain" >/dev/null 2>&1 +} + +dns_check_failed=false +for domain in oauth2.googleapis.com compute.googleapis.com storage.googleapis.com; do + if retry 5 true _check_dns "$domain"; then + info "✓ DNS OK for $domain" + else + warn "⚠️ DNS resolution FAILED for $domain after retries" + dns_check_failed=true + fi +done + +if [[ "$dns_check_failed" == "true" ]]; then + local error_msg="DNS is broken after retries (likely persistent node-resolver issue - see https://redhat.atlassian.net/browse/DPTP-5138). Failing fast to avoid wasted cluster creation time." + + # Generate structured JUnit report for better CI dashboard visibility + if [[ -n "${ARTIFACT_DIR:-}" ]] && command -v save_junit_failure >/dev/null 2>&1; then + save_junit_failure "DNS_Preflight_Check" "DNS resolution failed" "$error_msg" + fi + + die "$error_msg" +fi + set_ci_shared_export started_at "$(date -u +%s)" if [[ -z "${SHARED_DIR:-}" ]]; then From 8c5e3060c5f067ccd088408ad79496108697ef9e Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 3 Sep 2026 11:25:04 -0400 Subject: [PATCH 189/638] ROX-35778: Guard absence of resourceScope in reportSnapshots (#22600) --- .../View/ImageVulnerabilityResourcesView.tsx | 27 ++++++++++++++++--- .../imageVulnerabilityReports.utils.ts | 7 +++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx index 477a4f572047f..fab006fe1e464 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/View/ImageVulnerabilityResourcesView.tsx @@ -11,10 +11,21 @@ import { import type { BreakpointModifiers } from '@patternfly/react-core'; import EntityScopeDescriptionListGroups from 'Components/EntityScope/EntityScopeDescriptionListGroups'; -import type { ImageVulnerabilityReportResourcesConfiguration } from 'services/ReportsService.types'; +import type { + ConfiguredReportSnapshot, + ImageVulnerabilityReportResourcesConfiguration, +} from 'services/ReportsService.types'; import { imageTypeLabelMap } from '../imageVulnerabilityReports.utils'; +// Although migration might have moved collectionScope into resourceScope +// for report configurations, old snapshots might lack the resourceScope property. +// Therefore, resourceScope && … && (…) below. +// Return collection name from legacy property in report configuration of job snapshot. +function collectionSnapshotName(values: ConfiguredReportSnapshot): string | undefined { + return values.collectionSnapshot?.name; +} + export type ImageVulnerabilityResourcesViewProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; @@ -29,6 +40,8 @@ function ImageVulnerabilityResourcesView({ const { resourceScope, vulnReportFilters } = values; const { imageTypes } = vulnReportFilters; + const collectionName = collectionSnapshotName(values as unknown as ConfiguredReportSnapshot); + return ( @@ -56,7 +69,15 @@ function ImageVulnerabilityResourcesView({ )} - {'collectionScope' in resourceScope && ( + {!resourceScope && collectionName && ( + + Collection + + {collectionName} + + + )} + {resourceScope && 'collectionScope' in resourceScope && ( Collection @@ -64,7 +85,7 @@ function ImageVulnerabilityResourcesView({ )} - {'entityScope' in resourceScope && ( + {resourceScope && 'entityScope' in resourceScope && ( )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts index efaaa8866396e..fe724775e96d8 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts @@ -25,16 +25,19 @@ import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; // entityScope implies query but not fixability nore severities // collectionScope implies fixability and severities but not query +// Although migration might have moved collectionScope into resourceScope +// for report configurations, old snapshots might lack the resourceScope property. + export function hasConfigurationForCollection( values: ImageVulnerabilityReportConfiguration ): values is ImageVulnerabilityReportConfigurationForCollection { - return 'collectionScope' in values.resourceScope; + return Boolean(values.resourceScope) && 'collectionScope' in values.resourceScope; } export function hasConfigurationForEntity( values: ImageVulnerabilityReportConfiguration ): values is ImageVulnerabilityReportConfigurationForEntity { - return 'entityScope' in values.resourceScope; + return Boolean(values.resourceScope) && 'entityScope' in values.resourceScope; } export function getTextForCvesSince(vulnReportFilters: CvesSince) { From 78e385e9823f15294e7eeed861c3258058195b07 Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:34:40 -0400 Subject: [PATCH 190/638] ROX-36524: Add CISA KEV UI display and policy criteria (#22103) Co-authored-by: Claude Opus 4.6 (1M context) --- pkg/booleanpolicy/field_metadata.go | 10 +++++++++ pkg/booleanpolicy/fieldnames/list.go | 1 + .../querybuilders/special_cases.go | 11 ++++++++++ .../violationmessages/printer.go | 1 + .../Step3/policyCriteriaDescriptors.test.ts | 3 +++ .../Step3/policyCriteriaDescriptors.tsx | 22 +++++++++++++++++++ .../WorkloadCves/ImageCve/ImageCvePage.tsx | 3 +++ .../Tables/DeploymentVulnerabilitiesTable.tsx | 7 +++--- .../Tables/ImageVulnerabilitiesTable.tsx | 7 +++--- .../Tables/WorkloadCVEOverviewTable.tsx | 7 +++--- 10 files changed, 60 insertions(+), 12 deletions(-) diff --git a/pkg/booleanpolicy/field_metadata.go b/pkg/booleanpolicy/field_metadata.go index c6b3d88faa372..a25ee5c02a467 100644 --- a/pkg/booleanpolicy/field_metadata.go +++ b/pkg/booleanpolicy/field_metadata.go @@ -456,6 +456,16 @@ func initializeFieldMetadata() FieldMetadata { []RuntimeFieldType{}, negationForbidden, operatorsForbidden, imageEnrichmentRequired) + f.registerFieldMetadataRegex(fieldnames.CisaKev, + querybuilders.ForCisaKev(), + violationmessages.VulnContextFields, + func(*validateConfiguration) *regexp.Regexp { + return booleanValueRegex + }, + []storage.EventSource{storage.EventSource_NOT_APPLICABLE}, + []RuntimeFieldType{}, negationForbidden, operatorsForbidden, + imageEnrichmentRequired) + f.registerFieldMetadataRegex(fieldnames.FixedBy, querybuilders.ForFixedBy(), violationmessages.VulnContextFields, diff --git a/pkg/booleanpolicy/fieldnames/list.go b/pkg/booleanpolicy/fieldnames/list.go index 8d221a60af76d..1ed39f99083fb 100644 --- a/pkg/booleanpolicy/fieldnames/list.go +++ b/pkg/booleanpolicy/fieldnames/list.go @@ -13,6 +13,7 @@ var ( AutomountServiceAccountToken = newFieldName("Automount Service Account Token") CVE = newFieldName("CVE") CVSS = newFieldName("CVSS") + CisaKev = newFieldName("CISA KEV") ContainerCPULimit = newFieldName("Container CPU Limit") ContainerCPURequest = newFieldName("Container CPU Request") ContainerMemLimit = newFieldName("Container Memory Limit") diff --git a/pkg/booleanpolicy/querybuilders/special_cases.go b/pkg/booleanpolicy/querybuilders/special_cases.go index ae398dfefd805..a2ce136a87c1d 100644 --- a/pkg/booleanpolicy/querybuilders/special_cases.go +++ b/pkg/booleanpolicy/querybuilders/special_cases.go @@ -196,6 +196,17 @@ func mapFixedByValue(s string) string { return valueToStringRegex(s) } +// ForCisaKev returns a query builder for whether a CVE is in the CISA KEV catalog. +func ForCisaKev() QueryBuilder { + return wrapForVulnMgmt(func(group *storage.PolicyGroup) []*query.FieldQuery { + return []*query.FieldQuery{ + fieldQueryFromGroup(group, search.CisaKev, func(s string) string { + return s + }), + } + }) +} + // ForImageSignatureVerificationStatus returns a query builder for Image // Signature Verification Status. func ForImageSignatureVerificationStatus() QueryBuilder { diff --git a/pkg/booleanpolicy/violationmessages/printer.go b/pkg/booleanpolicy/violationmessages/printer.go index 34a72e30b3bb5..04b574754cebf 100644 --- a/pkg/booleanpolicy/violationmessages/printer.go +++ b/pkg/booleanpolicy/violationmessages/printer.go @@ -41,6 +41,7 @@ var ( fieldnames.EnvironmentVariable: {{required: set.NewStringSet(augmentedobjs.EnvironmentVarCustomTag), printerFuncKey: printer.EnvKey}}, fieldnames.ExposedNodePort: {{required: set.NewStringSet(search.ExposedNodePort.String()), printerFuncKey: printer.NodePortKey}}, fieldnames.ExposedPort: {{required: set.NewStringSet(search.Port.String()), printerFuncKey: printer.PortKey}}, + fieldnames.CisaKev: {{required: set.NewStringSet(search.CVE.String()), printerFuncKey: printer.CveKey}}, fieldnames.Fixable: {{required: set.NewStringSet(search.CVE.String()), printerFuncKey: printer.CveKey}}, fieldnames.FixedBy: {{required: set.NewStringSet(search.CVE.String()), printerFuncKey: printer.CveKey}}, fieldnames.HostIPC: {{required: set.NewStringSet(search.HostIPC.String()), printerFuncKey: printer.HostIPCKey}}, diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.test.ts b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.test.ts index cb4fc01e01c7c..7b7e942647672 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.test.ts +++ b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.test.ts @@ -15,12 +15,14 @@ import { // Items that are allowed independent of context. const allowListForItems = [ 'API', + 'CISA', 'CPU', 'CVE', 'CVSS', 'Dockerfile', 'IP', 'IPC', + 'KEV', 'Kubernetes', 'MUST', 'NOT', @@ -33,6 +35,7 @@ const allowListForItems = [ // Items that are allowed only in the content of an entire string. const allowListForNames = [ + 'CISA Known Exploited Vulnerabilities catalog', 'Common Vulnerability Scoring System (CVSS) score', 'Common Vulnerability Scoring System (CVSS) score from National Vulnerability Database (NVD)', 'Volume type (e.g. secret, configMap, hostPath) is', diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx index e6d2026729fc7..1cc6724e8141c 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx +++ b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx @@ -683,6 +683,28 @@ export const policyCriteriaDescriptors: Descriptor[] = [ canBooleanLogic: false, lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'], }, + { + label: 'CISA KEV', + name: 'CISA KEV', + shortName: 'CISA KEV', + longName: 'CISA Known Exploited Vulnerabilities catalog', + category: policyCriteriaCategories.IMAGE_SCANNING, + type: 'radioGroup', + radioButtons: [ + { + text: 'CVE is in CISA KEV catalog (known exploited)', + value: true, + }, + { + text: 'CVE is not in CISA KEV catalog', + value: false, + }, + ], + defaultValue: true, + canBooleanLogic: false, + lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'], + featureFlagDependency: ['ROX_CISA_KEV'], + }, { label: 'Fixed by', name: 'Fixed By', diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/ImageCve/ImageCvePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/ImageCve/ImageCvePage.tsx index 7e68292e107c9..789d6945d7e6a 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/ImageCve/ImageCvePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/ImageCve/ImageCvePage.tsx @@ -87,6 +87,9 @@ export const imageCveMetadataQuery = gql` epss { epssProbability } + exploit { + knownRansomwareCampaignUse + } } } } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx index 565e279f5996a..04d2e2b39b3be 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx @@ -92,6 +92,9 @@ export const deploymentWithVulnerabilitiesFragment = gql` epss { epssProbability } + exploit { + knownRansomwareCampaignUse + } } operatingSystem publishedOn @@ -202,10 +205,6 @@ function DeploymentVulnerabilitiesTable({ isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { - // Add in deploymentWithVulnerabilitiesFragment following epss: - // exploit { - // knownRansomwareCampaignUse - // } labels.push(); if (hasKnownRansomwareCampaignUse(cveBaseInfo?.exploit)) { labels.push( diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx index 052979790da06..0ea0997869e6f 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx @@ -137,6 +137,9 @@ export const imageVulnerabilitiesFragment = gql` epss { epssProbability } + exploit { + knownRansomwareCampaignUse + } } discoveredAtImage publishedOn @@ -293,10 +296,6 @@ function ImageVulnerabilitiesTable({ isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { - // Add in imageVulnerabilitiesFragment following epss: - // exploit { - // knownRansomwareCampaignUse - // } labels.push(); if (hasKnownRansomwareCampaignUse(cveBaseInfo?.exploit)) { labels.push( diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx index 888e0d91905d1..2152811ad30f7 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx @@ -152,6 +152,9 @@ export const cveListQuery = gql` epss { epssProbability } + exploit { + knownRansomwareCampaignUse + } } } pendingExceptionCount: exceptionCount(requestStatus: $statusesForExceptionCount) @@ -351,10 +354,6 @@ function WorkloadCVEOverviewTable({ isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { - // Add in cveListQuery following epss: - // exploit { - // knownRansomwareCampaignUse - // } labels.push(); if (hasKnownRansomwareCampaignUse(cveBaseInfo?.exploit)) { labels.push( From 575388b3b51fa340345e876527d267ed0eec8eaf Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:23:33 +0200 Subject: [PATCH 191/638] ROX-32849: Move VM fact and event helpers to common (#20300) --- sensor/common/virtualmachine/facts.go | 85 ++++ sensor/common/virtualmachine/facts_test.go | 172 ++++++++ .../virtualmachine/dispatcher/utils.go | 72 +--- .../virtualmachine/dispatcher/utils_test.go | 397 ------------------ .../dispatcher/virtualmachines_test.go | 6 + 5 files changed, 264 insertions(+), 468 deletions(-) create mode 100644 sensor/common/virtualmachine/facts.go create mode 100644 sensor/common/virtualmachine/facts_test.go delete mode 100644 sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils_test.go diff --git a/sensor/common/virtualmachine/facts.go b/sensor/common/virtualmachine/facts.go new file mode 100644 index 0000000000000..652a0e69708c8 --- /dev/null +++ b/sensor/common/virtualmachine/facts.go @@ -0,0 +1,85 @@ +package virtualmachine + +import ( + "strings" + + "github.com/stackrox/rox/generated/internalapi/central" + v1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" +) + +// Facts builds the VM facts map sent to Central. +func Facts(vm *Info) map[string]string { + if vm == nil { + return nil + } + + facts := map[string]string{ + pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, + } + if vm.GuestOS != "" { + facts[pkgVM.GuestOSKey] = vm.GuestOS + } + if vm.Description != "" { + facts[pkgVM.DescriptionKey] = vm.Description + } + if vm.NodeName != "" { + facts[pkgVM.NodeNameKey] = vm.NodeName + } + if len(vm.IPAddresses) > 0 { + facts[pkgVM.IPAddressesKey] = strings.Join(vm.IPAddresses, ", ") + } + if len(vm.ActivePods) > 0 { + facts[pkgVM.ActivePodsKey] = strings.Join(vm.ActivePods, ", ") + } + if len(vm.BootOrder) > 0 { + facts[pkgVM.BootOrderKey] = strings.Join(vm.BootOrder, ", ") + } + if len(vm.CDRomDisks) > 0 { + facts[pkgVM.CDRomDisksKey] = strings.Join(vm.CDRomDisks, ", ") + } + return facts +} + +// State is RUNNING when the VM instance is up, otherwise STOPPED. +func State(vm *Info) v1.VirtualMachine_State { + if vm == nil { + return v1.VirtualMachine_UNKNOWN + } + if vm.Running { + return v1.VirtualMachine_RUNNING + } + return v1.VirtualMachine_STOPPED +} + +// VSockCID returns the CID and whether it is set. +func VSockCID(vm *Info) (int32, bool) { + if vm == nil || vm.VSOCKCID == nil { + return 0, false + } + return int32(*vm.VSOCKCID), true +} + +// SensorEvent builds the VirtualMachine resource event sent to Central. +func SensorEvent(action central.ResourceAction, clusterID string, vm *Info) *central.SensorEvent { + if vm == nil { + return nil + } + vsockCID, vsockCIDSet := VSockCID(vm) + return ¢ral.SensorEvent{ + Id: string(vm.ID), + Action: action, + Resource: ¢ral.SensorEvent_VirtualMachine{ + VirtualMachine: &v1.VirtualMachine{ + Id: string(vm.ID), + Namespace: vm.Namespace, + Name: vm.Name, + ClusterId: clusterID, + VsockCid: vsockCID, + VsockCidSet: vsockCIDSet, + State: State(vm), + Facts: Facts(vm), + }, + }, + } +} diff --git a/sensor/common/virtualmachine/facts_test.go b/sensor/common/virtualmachine/facts_test.go new file mode 100644 index 0000000000000..6eefca29daf7e --- /dev/null +++ b/sensor/common/virtualmachine/facts_test.go @@ -0,0 +1,172 @@ +package virtualmachine + +import ( + "testing" + + "github.com/stackrox/rox/generated/internalapi/central" + v1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" + "github.com/stretchr/testify/assert" +) + +func TestFacts(t *testing.T) { + cases := map[string]struct { + input *Info + expected map[string]string + }{ + "nil Info returns nil": { + input: nil, + expected: nil, + }, + "empty Info returns default guestOS": { + input: &Info{}, + expected: map[string]string{ + pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, + }, + }, + "populated scalar fields": { + input: &Info{ + GuestOS: "Red Hat Enterprise Linux 9", + Description: "web server", + NodeName: "node-1", + }, + expected: map[string]string{ + pkgVM.GuestOSKey: "Red Hat Enterprise Linux 9", + pkgVM.DescriptionKey: "web server", + pkgVM.NodeNameKey: "node-1", + }, + }, + "populated slice fields are joined": { + input: &Info{ + IPAddresses: []string{"10.0.0.1", "10.0.0.2"}, + ActivePods: []string{"pod-a=node-1"}, + BootOrder: []string{"disk1=1", "disk2=2"}, + CDRomDisks: []string{"cdrom0"}, + }, + expected: map[string]string{ + pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, + pkgVM.IPAddressesKey: "10.0.0.1, 10.0.0.2", + pkgVM.ActivePodsKey: "pod-a=node-1", + pkgVM.BootOrderKey: "disk1=1, disk2=2", + pkgVM.CDRomDisksKey: "cdrom0", + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, Facts(tc.input)) + }) + } +} + +func TestState(t *testing.T) { + cases := map[string]struct { + input *Info + expected v1.VirtualMachine_State + }{ + "nil Info returns UNKNOWN": { + input: nil, + expected: v1.VirtualMachine_UNKNOWN, + }, + "running returns RUNNING": { + input: &Info{Running: true}, + expected: v1.VirtualMachine_RUNNING, + }, + "not running returns STOPPED": { + input: &Info{Running: false}, + expected: v1.VirtualMachine_STOPPED, + }, + "empty Info is treated as STOPPED": { + input: &Info{}, + expected: v1.VirtualMachine_STOPPED, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, State(tc.input)) + }) + } +} + +func TestVSockCID(t *testing.T) { + cases := map[string]struct { + input *Info + expected int32 + expectedSet bool + }{ + "nil Info is unset": { + input: nil, + expected: 0, + expectedSet: false, + }, + "missing VSOCKCID is unset": { + input: &Info{}, + expected: 0, + expectedSet: false, + }, + "CID 0 is set": { + input: &Info{VSOCKCID: new(uint32(0))}, + expected: 0, + expectedSet: true, + }, + "non-zero CID is set": { + input: &Info{VSOCKCID: new(uint32(0xca7d09))}, + expected: int32(0xca7d09), + expectedSet: true, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + cid, set := VSockCID(tc.input) + assert.Equal(t, tc.expected, cid) + assert.Equal(t, tc.expectedSet, set) + }) + } +} + +func TestSensorEvent(t *testing.T) { + t.Run("nil Info returns nil", func(t *testing.T) { + assert.Nil(t, SensorEvent(central.ResourceAction_UPDATE_RESOURCE, "cluster-1", nil)) + }) + + t.Run("running VM with VSOCK CID", func(t *testing.T) { + event := SensorEvent(central.ResourceAction_UPDATE_RESOURCE, "cluster-1", &Info{ + ID: "vm-1", + Name: "rhel9", + Namespace: "ns", + Running: true, + VSOCKCID: new(uint32(7)), + GuestOS: "Red Hat Enterprise Linux 9", + }) + assert.Equal(t, "vm-1", event.GetId()) + assert.Equal(t, central.ResourceAction_UPDATE_RESOURCE, event.GetAction()) + vm := event.GetVirtualMachine() + assert.Equal(t, "cluster-1", vm.GetClusterId()) + assert.Equal(t, int32(7), vm.GetVsockCid()) + assert.True(t, vm.GetVsockCidSet()) + assert.Equal(t, v1.VirtualMachine_RUNNING, vm.GetState()) + assert.Equal(t, map[string]string{ + pkgVM.GuestOSKey: "Red Hat Enterprise Linux 9", + }, vm.GetFacts()) + }) + + t.Run("stopped VM without VSOCK CID", func(t *testing.T) { + event := SensorEvent(central.ResourceAction_CREATE_RESOURCE, "cluster-2", &Info{ + ID: "vm-1", + Name: "rhel9", + Namespace: "ns", + Running: false, + }) + assert.Equal(t, "vm-1", event.GetId()) + assert.Equal(t, central.ResourceAction_CREATE_RESOURCE, event.GetAction()) + vm := event.GetVirtualMachine() + assert.Equal(t, "cluster-2", vm.GetClusterId()) + assert.Equal(t, int32(0), vm.GetVsockCid()) + assert.False(t, vm.GetVsockCidSet()) + assert.Equal(t, v1.VirtualMachine_STOPPED, vm.GetState()) + assert.Equal(t, map[string]string{ + pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, + }, vm.GetFacts()) + }) +} diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go index c7c576f93a67f..28f85afcfbb1b 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go @@ -1,10 +1,7 @@ package dispatcher import ( - "strings" - "github.com/stackrox/rox/generated/internalapi/central" - virtualMachineV1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" pkgVM "github.com/stackrox/rox/pkg/virtualmachine" sensorVirtualMachine "github.com/stackrox/rox/sensor/common/virtualmachine" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,73 +18,6 @@ func getVirtualMachineOwnerReference(owners []metav1.OwnerReference) (*metav1.Ow return nil, false } -func getVirtualMachineState(vm *sensorVirtualMachine.Info) virtualMachineV1.VirtualMachine_State { - if vm == nil { - return virtualMachineV1.VirtualMachine_UNKNOWN - } - if vm.Running { - return virtualMachineV1.VirtualMachine_RUNNING - } - return virtualMachineV1.VirtualMachine_STOPPED -} - -func getVirtualMachineVSockCID(vm *sensorVirtualMachine.Info) (int32, bool) { - if vm == nil { - return int32(0), false - } - if vm.VSOCKCID == nil { - return int32(0), false - } - return int32(*vm.VSOCKCID), true -} - -func getFacts(vm *sensorVirtualMachine.Info) map[string]string { - facts := map[string]string{ - pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, - } - if vm.GuestOS != "" { - facts[pkgVM.GuestOSKey] = vm.GuestOS - } - if vm.Description != "" { - facts[pkgVM.DescriptionKey] = vm.Description - } - if vm.NodeName != "" { - facts[pkgVM.NodeNameKey] = vm.NodeName - } - if len(vm.IPAddresses) > 0 { - facts[pkgVM.IPAddressesKey] = strings.Join(vm.IPAddresses, ", ") - } - if len(vm.ActivePods) > 0 { - facts[pkgVM.ActivePodsKey] = strings.Join(vm.ActivePods, ", ") - } - if len(vm.BootOrder) > 0 { - facts[pkgVM.BootOrderKey] = strings.Join(vm.BootOrder, ", ") - } - if len(vm.CDRomDisks) > 0 { - facts[pkgVM.CDRomDisksKey] = strings.Join(vm.CDRomDisks, ", ") - } - return facts -} - func createEvent(action central.ResourceAction, clusterID string, vm *sensorVirtualMachine.Info) *central.SensorEvent { - if vm == nil { - return nil - } - vSockCID, vSockCIDSet := getVirtualMachineVSockCID(vm) - return ¢ral.SensorEvent{ - Id: string(vm.ID), - Action: action, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: string(vm.ID), - Namespace: vm.Namespace, - Name: vm.Name, - ClusterId: clusterID, - VsockCid: vSockCID, - VsockCidSet: vSockCIDSet, - State: getVirtualMachineState(vm), - Facts: getFacts(vm), - }, - }, - } + return sensorVirtualMachine.SensorEvent(action, clusterID, vm) } diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils_test.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils_test.go deleted file mode 100644 index 56649d39911b5..0000000000000 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils_test.go +++ /dev/null @@ -1,397 +0,0 @@ -package dispatcher - -import ( - "testing" - - "github.com/stackrox/rox/generated/internalapi/central" - virtualMachineV1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" - "github.com/stackrox/rox/pkg/fixtures/fixtureconsts" - "github.com/stackrox/rox/pkg/protoassert" - "github.com/stackrox/rox/pkg/uuid" - pkgVM "github.com/stackrox/rox/pkg/virtualmachine" - sensorVirtualMachine "github.com/stackrox/rox/sensor/common/virtualmachine" - "github.com/stretchr/testify/assert" -) - -func TestGetVirtualMachineState(t *testing.T) { - tests := []struct { - name string - vm *sensorVirtualMachine.Info - expected virtualMachineV1.VirtualMachine_State - }{ - { - name: "nil input", - vm: nil, - expected: virtualMachineV1.VirtualMachine_UNKNOWN, - }, - { - name: "running machine", - vm: &sensorVirtualMachine.Info{ - Running: true, - }, - expected: virtualMachineV1.VirtualMachine_RUNNING, - }, - { - name: "stopped machine", - vm: &sensorVirtualMachine.Info{ - Running: false, - }, - expected: virtualMachineV1.VirtualMachine_STOPPED, - }, - { - name: "machine with partial data is assumed not running", - vm: &sensorVirtualMachine.Info{}, - expected: virtualMachineV1.VirtualMachine_STOPPED, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(it *testing.T) { - state := getVirtualMachineState(tc.vm) - assert.Equal(it, tc.expected, state) - }) - } -} - -func TestGetVirtualMachineVSockCID(t *testing.T) { - const expectedZero = int32(0) - zeroCID := uint32(0) - someCID := uint32(0xca7d09) - tests := []struct { - name string - vm *sensorVirtualMachine.Info - expected int32 - expectedSet bool - }{ - { - name: "nil input", - vm: nil, - expected: expectedZero, - expectedSet: false, - }, - { - name: "virtual machine with partial data is not assumed to have a VSock", - vm: &sensorVirtualMachine.Info{}, - expected: expectedZero, - expectedSet: false, - }, - { - name: "hypervisor", - vm: &sensorVirtualMachine.Info{ - VSOCKCID: &zeroCID, - }, - expected: expectedZero, - expectedSet: true, - }, - { - name: "virtual machine", - vm: &sensorVirtualMachine.Info{ - VSOCKCID: &someCID, - }, - expected: int32(someCID), - expectedSet: true, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(it *testing.T) { - vSockCID, vSockCIDSet := getVirtualMachineVSockCID(tc.vm) - assert.Equal(it, tc.expected, vSockCID) - assert.Equal(it, tc.expectedSet, vSockCIDSet) - }) - } -} - -func TestGetFacts(t *testing.T) { - tests := map[string]struct { - vm *sensorVirtualMachine.Info - expected map[string]string - }{ - "should include description and network facts when present": { - vm: &sensorVirtualMachine.Info{ - GuestOS: "Red Hat Enterprise Linux", - Description: "test description", - NodeName: "node-1", - IPAddresses: []string{"10.0.0.2", "10.0.0.1"}, - ActivePods: []string{"pod-2=node-b", "pod-1=node-a"}, - BootOrder: []string{"disk2=2", "disk1=1"}, - CDRomDisks: []string{"cd2", "cd1"}, - }, - expected: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - pkgVM.DescriptionKey: "test description", - pkgVM.NodeNameKey: "node-1", - pkgVM.IPAddressesKey: "10.0.0.2, 10.0.0.1", - pkgVM.ActivePodsKey: "pod-2=node-b, pod-1=node-a", - pkgVM.BootOrderKey: "disk2=2, disk1=1", - pkgVM.CDRomDisksKey: "cd2, cd1", - }, - }, - "should preserve boot order sequence": { - vm: &sensorVirtualMachine.Info{ - GuestOS: "Red Hat Enterprise Linux", - BootOrder: []string{"disk-b=1", "disk-a=1", "disk-c=2"}, - }, - expected: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - pkgVM.BootOrderKey: "disk-b=1, disk-a=1, disk-c=2", - }, - }, - "should return unknown guest os when optional data is missing": { - vm: &sensorVirtualMachine.Info{}, - expected: map[string]string{ - pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, - }, - }, - } - for name, tt := range tests { - t.Run(name, func(it *testing.T) { - facts := getFacts(tt.vm) - assert.Equal(it, tt.expected, facts) - }) - } -} - -func TestCreateEvent(t *testing.T) { - const ns1 = "namespace-1" - var vm1ID = uuid.NewTestUUID(1).String() - const vm1Name = "Test VM 1" - var vm2ID = uuid.NewTestUUID(2).String() - const vm2Name = "Test VM 2" - vm2VSockCID := uint32(0xd09ca7) - wireVM2VSockCID := int32(vm2VSockCID) - - tests := []struct { - name string - action central.ResourceAction - clusterID string - inputVM *sensorVirtualMachine.Info - expected *central.SensorEvent - }{ - { - name: "nil input", - action: central.ResourceAction_UPDATE_RESOURCE, - clusterID: fixtureconsts.Cluster1, - inputVM: nil, - expected: nil, - }, - { - name: "create stopped virtual machine", - action: central.ResourceAction_CREATE_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm1ID), - Name: vm1Name, - Namespace: ns1, - VSOCKCID: nil, - Running: false, - }, - expected: ¢ral.SensorEvent{ - Id: vm1ID, - Action: central.ResourceAction_CREATE_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm1ID, - Namespace: ns1, - Name: vm1Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: 0, - VsockCidSet: false, - State: virtualMachineV1.VirtualMachine_STOPPED, - Facts: getFactsForTest(t, pkgVM.UnknownGuestOS), - }, - }, - }, - }, - { - name: "create running virtual machine", - action: central.ResourceAction_CREATE_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm2ID), - Name: vm2Name, - Namespace: ns1, - VSOCKCID: &vm2VSockCID, - Running: true, - GuestOS: "Red Hat Enterprise Linux", - }, - expected: ¢ral.SensorEvent{ - Id: vm2ID, - Action: central.ResourceAction_CREATE_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm2ID, - Namespace: ns1, - Name: vm2Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: wireVM2VSockCID, - VsockCidSet: true, - State: virtualMachineV1.VirtualMachine_RUNNING, - Facts: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - }, - }, - }, - }, - }, - { - name: "sync stopped virtual machine", - action: central.ResourceAction_SYNC_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm1ID), - Name: vm1Name, - Namespace: ns1, - VSOCKCID: nil, - Running: false, - }, - expected: ¢ral.SensorEvent{ - Id: vm1ID, - Action: central.ResourceAction_SYNC_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm1ID, - Namespace: ns1, - Name: vm1Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: 0, - VsockCidSet: false, - State: virtualMachineV1.VirtualMachine_STOPPED, - Facts: getFactsForTest(t, pkgVM.UnknownGuestOS), - }, - }, - }, - }, - { - name: "sync running virtual machine", - action: central.ResourceAction_SYNC_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm2ID), - Name: vm2Name, - Namespace: ns1, - VSOCKCID: &vm2VSockCID, - Running: true, - GuestOS: "Red Hat Enterprise Linux", - }, - expected: ¢ral.SensorEvent{ - Id: vm2ID, - Action: central.ResourceAction_SYNC_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm2ID, - Namespace: ns1, - Name: vm2Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: wireVM2VSockCID, - VsockCidSet: true, - State: virtualMachineV1.VirtualMachine_RUNNING, - Facts: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - }, - }, - }, - }, - }, - { - name: "update running virtual machine", - action: central.ResourceAction_UPDATE_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm2ID), - Name: vm2Name, - Namespace: ns1, - VSOCKCID: &vm2VSockCID, - Running: true, - GuestOS: "Red Hat Enterprise Linux", - }, - expected: ¢ral.SensorEvent{ - Id: vm2ID, - Action: central.ResourceAction_UPDATE_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm2ID, - Namespace: ns1, - Name: vm2Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: wireVM2VSockCID, - VsockCidSet: true, - State: virtualMachineV1.VirtualMachine_RUNNING, - Facts: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - }, - }, - }, - }, - }, - { - name: "remove stopped virtual machine", - action: central.ResourceAction_REMOVE_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm1ID), - Name: vm1Name, - Namespace: ns1, - VSOCKCID: nil, - Running: false, - }, - expected: ¢ral.SensorEvent{ - Id: vm1ID, - Action: central.ResourceAction_REMOVE_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm1ID, - Namespace: ns1, - Name: vm1Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: 0, - VsockCidSet: false, - State: virtualMachineV1.VirtualMachine_STOPPED, - Facts: getFactsForTest(t, pkgVM.UnknownGuestOS), - }, - }, - }, - }, - { - name: "remove running virtual machine", - action: central.ResourceAction_REMOVE_RESOURCE, - clusterID: fixtureconsts.Cluster2, - inputVM: &sensorVirtualMachine.Info{ - ID: sensorVirtualMachine.VMID(vm2ID), - Name: vm2Name, - Namespace: ns1, - VSOCKCID: &vm2VSockCID, - Running: true, - GuestOS: "Red Hat Enterprise Linux", - }, - expected: ¢ral.SensorEvent{ - Id: vm2ID, - Action: central.ResourceAction_REMOVE_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachine{ - VirtualMachine: &virtualMachineV1.VirtualMachine{ - Id: vm2ID, - Namespace: ns1, - Name: vm2Name, - ClusterId: fixtureconsts.Cluster2, - VsockCid: wireVM2VSockCID, - VsockCidSet: true, - State: virtualMachineV1.VirtualMachine_RUNNING, - Facts: map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux", - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(it *testing.T) { - event := createEvent(tt.action, tt.clusterID, tt.inputVM) - protoassert.Equal(it, tt.expected, event) - }) - } -} - -func getFactsForTest(_ *testing.T, guestOS string) map[string]string { - return map[string]string{ - pkgVM.GuestOSKey: guestOS, - } -} diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachines_test.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachines_test.go index a39a4862562c7..cf7b63c18124b 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachines_test.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachines_test.go @@ -317,3 +317,9 @@ func newVirtualMachine(uid, name, namespace string, status v1.VirtualMachinePrin }, } } + +func getFactsForTest(_ *testing.T, guestOS string) map[string]string { + return map[string]string{ + pkgVM.GuestOSKey: guestOS, + } +} From b50b5f805b52be7502a8f2c6779412206c5a49a0 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 3 Sep 2026 14:27:27 -0400 Subject: [PATCH 192/638] ROX-35133: Replace button with tabs for CVE Snoozed in NodeCves (#21238) --- ui/apps/platform/cypress/helpers/request.js | 14 ++++ .../nodeCves/nodeCvesOverviewPage.test.ts | 33 +++++--- .../components/SelectExclusiveSingleTabs.tsx | 50 +++++++++++ .../Overview/NodeCvesOverviewPage.tsx | 43 ++++++---- .../Overview/PlatformCvesOverviewPage.tsx | 38 ++++++--- .../components/AdvancedFiltersToolbar.tsx | 3 +- .../components/SnoozedCveToggleButton.cy.jsx | 83 ------------------- .../components/SnoozedCveToggleButton.tsx | 38 --------- .../hooks/useSnoozedCveCount.ts | 21 ----- .../Vulnerabilities/searchFilterConfig.ts | 16 ++-- 10 files changed, 152 insertions(+), 187 deletions(-) create mode 100644 ui/apps/platform/src/Components/CompoundSearchFilter/components/SelectExclusiveSingleTabs.tsx delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.cy.jsx delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.tsx delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/hooks/useSnoozedCveCount.ts diff --git a/ui/apps/platform/cypress/helpers/request.js b/ui/apps/platform/cypress/helpers/request.js index 81e895d88fbc6..e39b5d643dabc 100644 --- a/ui/apps/platform/cypress/helpers/request.js +++ b/ui/apps/platform/cypress/helpers/request.js @@ -218,6 +218,20 @@ export function expectRequestedQuery(expectedQuery) { }; } +// Asserts one or more items to be independent +// of order in concatenated query string +// of absence, presence, or value of items outside scope of test (for example, CVE Snoozed) +export function expectRequestedQueryToIncludeSubstrings(expectedQuerySubstrings) { + return ({ query }) => { + expectedQuerySubstrings.forEach((expectedQuery) => { + expect(query).to.include( + expectedQuery, + `Expected query to include ${expectedQuery} but received ${query}` + ); + }); + }; +} + export function expectRequestedPagination(expectedPagination) { return (variables) => { const { pagination } = variables; diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts index 498f1cfd5a3ba..7b66a3da45aad 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts @@ -25,7 +25,7 @@ import { } from '../../../helpers/tableHelpers'; import { expectRequestedPagination, - expectRequestedQuery, + expectRequestedQueryToIncludeSubstrings, expectRequestedSort, interceptAndWatchRequests, } from '../../../helpers/request'; @@ -249,16 +249,20 @@ describe('Node CVEs - Overview Page', () => { }); it('should filter the CVE table', () => { + // expectRequestedQueryToIncludeSubstrings asserts items to be independent + // of order in concatenated query string + // of CVE Snoozed that is outside scope of test interceptAndWatchRequests(routeMatcherMapForNodeCves).then( ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { // Visit Node tab and wait for initial load visitNodeCveOverviewPage(); waitForRequests(); + // Observed tab // filtering by CVE name should only display rows with a matching name filterHelpers.addAutocompleteFilter('CVE', 'Name', 'CVE-2021-1234'); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery('CVE:r/CVE-2021-1234') + expectRequestedQueryToIncludeSubstrings(['CVE:r/CVE-2021-1234']) ); // Do not assert on cell contents as the filter value is mocked filterHelpers.clearFilters(); @@ -268,7 +272,7 @@ describe('Node CVEs - Overview Page', () => { // filtering by Severity should not report counts for hidden severities applyLocalSeverityFilters('Low'); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery('Severity:LOW_VULNERABILITY_SEVERITY') + expectRequestedQueryToIncludeSubstrings(['Severity:LOW_VULNERABILITY_SEVERITY']) ); assertOnEachRowForColumn('Nodes by severity', (_, cell) => { const { critical, important, moderate, low } = getSeverityLabelCounts(cell); @@ -282,7 +286,9 @@ describe('Node CVEs - Overview Page', () => { // filtering by CVSS should only display rows with a CVSS in range filterHelpers.addNumericFilter('CVE', 'CVSS', 'Is less than', 8); - waitAndYieldRequestBodyVariables().then(expectRequestedQuery('CVSS:<8')); + waitAndYieldRequestBodyVariables().then( + expectRequestedQueryToIncludeSubstrings(['CVSS:<8']) + ); assertOnEachRowForColumn('Top CVSS', (_, cell) => { const cvss = parseFloat(cell.innerText.replace(/[^0-9.]/g, '')); expect(cvss).to.be.lessThan(8); @@ -298,13 +304,16 @@ describe('Node CVEs - Overview Page', () => { waitForRequests(); filterHelpers.addNumericFilter('CVE', 'CVSS', 'Is less than', 8); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery('CVE:r/CVE-2021-1234+CVSS:<8') + expectRequestedQueryToIncludeSubstrings(['CVE:r/CVE-2021-1234', 'CVSS:<8']) ); } ); }); it('should filter the Node table', () => { + // expectRequestedQueryToIncludeSubstrings asserts items to be independent + // of order in concatenated query string + // of CVE Snoozed that is outside scope of test interceptAndWatchRequests(routeMatcherMapForNodes).then( ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { // Visit Node tab and wait for initial load @@ -312,11 +321,12 @@ describe('Node CVEs - Overview Page', () => { visitEntityTab('Node'); waitForRequests(); + // Observed tab // filtering by Node name should only display rows with a matching name const nodeNameFilter = 'a'; filterHelpers.addAutocompleteFilter('Node', 'Name', nodeNameFilter); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery(`Node:r/${nodeNameFilter}`) + expectRequestedQueryToIncludeSubstrings([`Node:r/${nodeNameFilter}`]) ); assertOnEachRowForColumn('Node', (_, cell) => { expect(cell.innerText).to.match(new RegExp(nodeNameFilter, 'i')); @@ -328,7 +338,7 @@ describe('Node CVEs - Overview Page', () => { // filtering by Severity should not report counts for hidden severities applyLocalSeverityFilters('Low'); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery('Severity:LOW_VULNERABILITY_SEVERITY') + expectRequestedQueryToIncludeSubstrings(['Severity:LOW_VULNERABILITY_SEVERITY']) ); assertOnEachRowForColumn('CVEs by severity', (_, cell) => { const { critical, important, moderate, low } = getSeverityLabelCounts(cell); @@ -344,7 +354,7 @@ describe('Node CVEs - Overview Page', () => { const clusterNameFilter = 'a'; filterHelpers.addAutocompleteFilter('Cluster', 'Name', clusterNameFilter); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery(`Cluster:r/${clusterNameFilter}`) + expectRequestedQueryToIncludeSubstrings([`Cluster:r/${clusterNameFilter}`]) ); assertOnEachRowForColumn('Cluster', (_, cell) => { expect(cell.innerText).to.match(new RegExp(clusterNameFilter, 'i')); @@ -356,7 +366,7 @@ describe('Node CVEs - Overview Page', () => { const osFilter = 'red hat'; filterHelpers.addPlainTextFilter('Node', 'Operating System', osFilter); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery(`Operating System:r/${osFilter}`) + expectRequestedQueryToIncludeSubstrings([`Operating System:r/${osFilter}`]) ); assertOnEachRowForColumn('Operating system', (_, cell) => { expect(cell.innerText).to.match(new RegExp(osFilter, 'i')); @@ -372,7 +382,10 @@ describe('Node CVEs - Overview Page', () => { waitForRequests(); filterHelpers.addAutocompleteFilter('Cluster', 'Name', clusterNameFilter); waitAndYieldRequestBodyVariables().then( - expectRequestedQuery(`Node:r/${nodeNameFilter}+Cluster:r/${clusterNameFilter}`) + expectRequestedQueryToIncludeSubstrings([ + `Node:r/${nodeNameFilter}`, + `Cluster:r/${clusterNameFilter}`, + ]) ); } ); diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/components/SelectExclusiveSingleTabs.tsx b/ui/apps/platform/src/Components/CompoundSearchFilter/components/SelectExclusiveSingleTabs.tsx new file mode 100644 index 0000000000000..98946d153b16d --- /dev/null +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/components/SelectExclusiveSingleTabs.tsx @@ -0,0 +1,50 @@ +import type { ReactElement } from 'react'; +import { Tab, TabTitleText, Tabs } from '@patternfly/react-core'; + +import type { SearchFilter } from 'types/search'; +import { searchValueAsArray } from 'utils/searchUtils'; + +import type { OnSearchPayload, SelectExclusiveSingleSearchFilterAttribute } from '../types'; + +export type SelectExclusiveSingleTabsProps = { + attribute: SelectExclusiveSingleSearchFilterAttribute; + onSelectTab: (payload: OnSearchPayload) => void; + searchFilter: SearchFilter; + tabContentId: string; + usePageInsets?: boolean; +}; + +// Generic presentation of search filter attribute as tabs. +// For example, CVE Snoozed for Nodes Results pages. +// Callback function might need to reset pagination in addition to update search filter. +function SelectExclusiveSingleTabs({ + attribute, + onSelectTab, + searchFilter, + tabContentId, + usePageInsets = true, +}: SelectExclusiveSingleTabsProps): ReactElement { + const { inputProps, searchTerm } = attribute; + const { options } = inputProps; + + const activeKey = searchValueAsArray(searchFilter[searchTerm])[0] ?? options[0].value; + + function onSelect(_, eventKey) { + onSelectTab([{ action: 'SELECT_EXCLUSIVE', category: searchTerm, value: eventKey }]); + } + + return ( + + {options.map(({ label, value }) => ( + {label}} + /> + ))} + + ); +} + +export default SelectExclusiveSingleTabs; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx index 7ea63b0f3f033..7a58cf3a96787 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx @@ -11,7 +11,12 @@ import { } from '@patternfly/react-core'; import { useApolloClient } from '@apollo/client'; -import { getSearchFilterConfigWithFeatureFlagDependency } from 'Components/CompoundSearchFilter/utils/utils'; +import SelectExclusiveSingleTabs from 'Components/CompoundSearchFilter/components/SelectExclusiveSingleTabs'; +import type { OnSearchPayload } from 'Components/CompoundSearchFilter/types'; +import { + getSearchFilterConfigWithFeatureFlagDependency, + updateSearchFilter, +} from 'Components/CompoundSearchFilter/utils/utils'; import PageTitle from 'Components/PageTitle'; import ExternalLink from 'Components/PatternFly/IconText/ExternalLink'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; @@ -33,14 +38,15 @@ import { getVersionedDocs } from 'utils/versioning'; import { createFilterTracker } from 'utils/analyticsEventTracking'; import { vulnerabilityNodeViewBasedJobsPath } from 'routePaths'; -import { searchFilterConfigForNodeVulnerabilityResultsAndViewBasedReport } from '../../searchFilterConfig'; +import { + attributeForSnoozed, + searchFilterConfigForNodeVulnerabilityResultsAndViewBasedReport, +} from '../../searchFilterConfig'; import AdvancedFiltersToolbar from '../../components/AdvancedFiltersToolbar'; import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; -import SnoozedCveToggleButton from '../../components/SnoozedCveToggleButton'; import SnoozeCvesModal from '../../components/SnoozeCvesModal/SnoozeCvesModal'; import useSnoozeCveModal from '../../components/SnoozeCvesModal/useSnoozeCveModal'; import useHasLegacySnoozeAbility from '../../hooks/useHasLegacySnoozeAbility'; -import useSnoozedCveCount from '../../hooks/useSnoozedCveCount'; import TableEntityToolbar from '../../components/TableEntityToolbar'; import EntityTypeToggleGroup from '../../components/EntityTypeToggleGroup'; import { createScheduledReportForNodeVulnerabilitiesURL } from '../../Reports/NodeVulnerabilityReports/nodeVulnerabilityReports.utils'; @@ -79,14 +85,17 @@ function NodeCvesOverviewPage() { onSort: () => pagination.setPage(1), }); - const querySearchFilter = parseQuerySearchFilter(searchFilter); + // Default to Observed tab in UI and corresponding search filter in API when unspecified. + const querySearchFilter = parseQuerySearchFilter({ + [attributeForSnoozed.searchTerm]: attributeForSnoozed.inputProps.options[0].value, + ...searchFilter, + }); const isFiltered = getHasSearchApplied(querySearchFilter); const isViewingSnoozedCves = querySearchFilter['CVE Snoozed']?.[0] === 'true'; const hasLegacySnoozeAbility = useHasLegacySnoozeAbility(); const selectedCves = useMap(); const { snoozeModalOptions, setSnoozeModalOptions, snoozeActionCreator } = useSnoozeCveModal(); - const snoozedCveCount = useSnoozedCveCount('Node'); const { version } = useMetadata(); const [isCreateViewBasedReportModalOpen, setIsCreateViewBasedReportModalOpen] = useState(false); const navigate = useNavigate(); @@ -121,6 +130,11 @@ function NodeCvesOverviewPage() { pagination.setPage(1); } + function onSelectSnoozedTab(payload: OnSearchPayload) { + setSearchFilter(updateSearchFilter(searchFilter, payload)); + pagination.setPage(1); + } + const { data } = useNodeCveEntityCounts(querySearchFilter); const entityCounts = { @@ -194,13 +208,6 @@ function NodeCvesOverviewPage() { Node CVEs Prioritize and manage scanned CVEs across nodes - - - {showScannerV4NodeScannerInfoAlert && ( @@ -227,7 +234,15 @@ function NodeCvesOverviewPage() { )} - + + + + pagination.setPage(1), }); - const querySearchFilter = parseQuerySearchFilter(searchFilter); + // Default to Observed tab in UI and corresponding search filter in API when unspecified. + const querySearchFilter = parseQuerySearchFilter({ + [attributeForSnoozed.searchTerm]: attributeForSnoozed.inputProps.options[0].value, + ...searchFilter, + }); const isFiltered = getHasSearchApplied(querySearchFilter); const isViewingSnoozedCves = querySearchFilter['CVE Snoozed']?.[0] === 'true'; const hasLegacySnoozeAbility = useHasLegacySnoozeAbility(); const selectedCves = useMap(); const { snoozeModalOptions, setSnoozeModalOptions, snoozeActionCreator } = useSnoozeCveModal(); - const snoozedCveCount = useSnoozedCveCount('Platform'); function onEntityTabChange(entityTab: 'CVE' | 'Cluster') { pagination.setPage(1); @@ -108,6 +116,11 @@ function PlatformCvesOverviewPage() { pagination.setPage(1); } + function onSelectSnoozedTab(payload: OnSearchPayload) { + setSearchFilter(updateSearchFilter(searchFilter, payload)); + pagination.setPage(1); + } + const filterToolbar = ( Kubernetes components Prioritize and manage scanned CVEs across clusters - - - + + + normalizeSearchFilterKeys(searchFilter), [searchFilter]); - const attributesSeparateFromConfig: CompoundSearchFilterAttribute[] = [attributeForSnoozed]; + const attributesSeparateFromConfig: CompoundSearchFilterAttribute[] = []; if (includeCveSeverityFilters) { attributesSeparateFromConfig.push(attributeForSeverityInFrontendAndLocalStorage); } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.cy.jsx b/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.cy.jsx deleted file mode 100644 index 89bf49b8b0d55..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.cy.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useState } from 'react'; -import SnoozedCveToggleButton from './SnoozedCveToggleButton'; - -function Wrapper({ startingSearchFilter = {}, snoozedCveCount }) { - const [searchFilter, setSearchFilter] = useState(startingSearchFilter); - - return ( - <> -
    -

    Filters

    - {Object.entries(searchFilter).map(([key, values]) => ( -
    - {key}:{values.join(',')} -
    - ))} -
    - - - ); -} - -const snoozedFilterSelector = - 'div:has(h1:contains("Filters")) div:contains("CVE Snoozed:true") div'; - -const severityFilterSelector = - 'div:has(h1:contains("Filters")) div:contains("Severity:Critical,Important") div'; - -describe(Cypress.spec.relative, () => { - it('should manage the toggling of the snoozed CVE filter', () => { - cy.mount(); - - // Default is off - cy.get(snoozedFilterSelector).should('not.exist'); - - // Toggle on - cy.findByText('Show snoozed CVEs').click(); - cy.get(snoozedFilterSelector).should('exist'); - - // Toggle off - cy.findByText('Show observed CVEs').click(); - cy.get(snoozedFilterSelector).should('not.exist'); - }); - - it('should not change the state of existing filters when toggling the snoozed CVE filter', () => { - cy.mount(); - - // Default is off - cy.get(snoozedFilterSelector).should('not.exist'); - cy.get(severityFilterSelector).should('exist'); - - // Toggle on - cy.findByText('Show snoozed CVEs').click(); - cy.get(snoozedFilterSelector).should('exist'); - cy.get(severityFilterSelector).should('exist'); - - // Toggle off - cy.findByText('Show observed CVEs').click(); - cy.get(snoozedFilterSelector).should('not.exist'); - cy.get(severityFilterSelector).should('exist'); - }); - - it('should correctly display the current snoozed CVE count', () => { - // Badge should not show when no count is provided - cy.mount(); - cy.get('button .pf-v6-c-badge').should('not.exist'); - - // Badge should not show when count is 0 - cy.mount(); - cy.get('button .pf-v6-c-badge').should('not.exist'); - - // Badge should show when count is > 0 - cy.mount(); - cy.get('button .pf-v6-c-badge').contains('1'); - - // Badge should not show when viewing snoozed CVEs - cy.findByText('Show snoozed CVEs').click(); - cy.get('button .pf-v6-c-badge').should('not.exist'); - }); -}); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.tsx deleted file mode 100644 index 189fcae3d7116..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/components/SnoozedCveToggleButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Button } from '@patternfly/react-core'; -import type { SearchFilter } from 'types/search'; - -export type SnoozedCveToggleButtonProps = { - searchFilter: SearchFilter; - setSearchFilter: (searchFilter: SearchFilter) => void; - snoozedCveCount: number | undefined; -}; - -function SnoozedCveToggleButton({ - searchFilter, - setSearchFilter, - snoozedCveCount, -}: SnoozedCveToggleButtonProps) { - const isSnoozeFilterActive = searchFilter['CVE Snoozed']?.[0] === 'true'; - const buttonText = isSnoozeFilterActive ? 'Show observed CVEs' : 'Show snoozed CVEs'; - const showCountBadge = - typeof snoozedCveCount === 'number' && snoozedCveCount > 0 && !isSnoozeFilterActive; - const badgeCount = showCountBadge ? { isRead: true, count: snoozedCveCount } : undefined; - - function toggleSnoozeFilter() { - const nextFilter = { ...searchFilter }; - if (isSnoozeFilterActive) { - delete nextFilter['CVE Snoozed']; - } else { - nextFilter['CVE Snoozed'] = ['true']; - } - setSearchFilter(nextFilter); - } - - return ( - - ); -} - -export default SnoozedCveToggleButton; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useSnoozedCveCount.ts b/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useSnoozedCveCount.ts deleted file mode 100644 index 2c98d59ab0e33..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useSnoozedCveCount.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { gql, useQuery } from '@apollo/client'; - -const snoozedNodeCveCountQuery = gql` - query getSnoozedNodeCveCount { - count: nodeCVECount(query: "CVE Snoozed:true") - } -`; - -const snoozedPlatformCveCountQuery = gql` - query getSnoozedPlatformCveCount { - count: platformCVECount(query: "CVE Snoozed:true") - } -`; - -export default function useSnoozedCveCount(cveType: 'Node' | 'Platform'): number | undefined { - const totalNodeCountRequest = useQuery<{ count: number }>( - cveType === 'Node' ? snoozedNodeCveCountQuery : snoozedPlatformCveCountQuery - ); - - return totalNodeCountRequest.data?.count; -} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts index 72ba3bb7f3539..8faf9e5cede98 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/searchFilterConfig.ts @@ -180,14 +180,14 @@ export const virtualMachinesNamespaceSearchFilterConfig: CompoundSearchFilterEnt // attributes for separate search filter elements in AdvancedFiltersToolbar.tsx file export const attributeForSnoozed: SelectExclusiveSingleSearchFilterAttribute = { - displayName: 'CVE snoozed', // corresponds to Show snoozed CVEs - filterChipLabel: 'CVE snoozed', + displayName: 'Vulnerability state', + filterChipLabel: 'Vulnerability state', searchTerm: 'CVE Snoozed', - inputType: 'select-exclusive-single', // placeholder because interaction is Show snoozed CVEs button + inputType: 'select-exclusive-single', inputProps: { options: [ - { label: 'true', value: 'true' }, // Snoozed - { label: 'false', value: 'false' }, // Observed + { label: 'Observed', value: 'false' }, + { label: 'Snoozed', value: 'true' }, ], }, }; @@ -245,6 +245,7 @@ export const attributeForSeverityInFrontendAndLocalStorage: SelectSearchFilterAt ], }, }; + export const attributeForSeverityInBackendAndViewBasedReport: SelectSearchFilterAttribute = { displayName: 'CVE severity', filterChipLabel: 'CVE severity', @@ -264,7 +265,7 @@ export const searchFilterConfigForImageVulnerabilityReport = [ ...imageCVESearchFilterConfig, attributes: [ ...imageCVESearchFilterConfig.attributes.filter( - ({ searchTerm }) => searchTerm !== 'CVE Created Time' + ({ searchTerm }) => searchTerm !== 'CVE Created Time' // created in **system** is confusing for report ), Origin, // CVE origin filter is scoped to reports (plus single image/deployment pages) ], @@ -287,7 +288,7 @@ export const searchFilterConfigForNodeVulnerabilityReport = [ { ...nodeCVESearchFilterConfig, attributes: nodeCVESearchFilterConfig.attributes.filter( - ({ searchTerm }) => searchTerm !== 'CVE Created Time' + ({ searchTerm }) => searchTerm !== 'CVE Created Time' // created in **system** is confusing for report ), }, nodeSearchFilterConfig, @@ -330,6 +331,7 @@ export const attributeForVulnerabilityState: SelectSearchFilterAttribute = { }; // For scheduled and view-based report. + export const attributesSeparateFromConfigForImageVulnerabilityReport = [ attributeForPlatformComponent, attributeForVulnerabilityState, From d1addf3974fa37b3cddb85286003be29e5f22d1c Mon Sep 17 00:00:00 2001 From: Vickey Brown <44439595+vickeybrown@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:32:27 -0500 Subject: [PATCH 193/638] ROX-36447: Enable both clair-scan and roxctl-scan (#22452) --- .tekton/basic-component-pipeline.yaml | 25 ++++++++++++++++++++----- .tekton/main-pipeline.yaml | 25 ++++++++++++++++++++----- .tekton/operator-bundle-pipeline.yaml | 20 ++++++++++++++++++++ .tekton/scanner-v4-pipeline.yaml | 25 ++++++++++++++++++++----- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index dabb87caba9b1..ac257eacdd4bf 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -380,11 +380,6 @@ spec: values: ["false"] - name: roxctl-scan - matrix: - params: - - name: image-platform - value: - - $(params.build-platforms) params: - name: image-digest value: $(tasks.build-image-index.results.IMAGE_DIGEST) @@ -404,6 +399,26 @@ spec: operator: in values: ["false"] + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-image-index.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-image-index.results.IMAGE_URL) + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: ["false"] + - name: ecosystem-cert-preflight-checks matrix: params: diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 85571e4dbacc4..1ea5413bb3c0a 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -402,11 +402,6 @@ spec: values: ["false"] - name: roxctl-scan - matrix: - params: - - name: image-platform - value: - - $(params.build-platforms) params: - name: image-digest value: $(tasks.build-image-index.results.IMAGE_DIGEST) @@ -426,6 +421,26 @@ spec: operator: in values: ["false"] + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-image-index.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-image-index.results.IMAGE_URL) + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: ["false"] + - name: ecosystem-cert-preflight-checks matrix: params: diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 3cd54dd6501a9..6fff04f4bf73a 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -605,6 +605,26 @@ spec: operator: in values: ["false"] + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: ["false"] + - name: fips-operator-bundle-check-oci-ta params: - name: image-digest diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index f865e34956ed1..c5be5abdec5b2 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -400,11 +400,6 @@ spec: values: ["false"] - name: roxctl-scan - matrix: - params: - - name: image-platform - value: - - $(params.build-platforms) params: - name: image-digest value: $(tasks.build-image-index.results.IMAGE_DIGEST) @@ -424,6 +419,26 @@ spec: operator: in values: ["false"] + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-image-index.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-image-index.results.IMAGE_URL) + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: ["false"] + - name: ecosystem-cert-preflight-checks matrix: params: From f35e28d513fddc9d5520714220ee7f44cffb61e7 Mon Sep 17 00:00:00 2001 From: Aleksandr Kurlov Date: Thu, 3 Sep 2026 22:54:03 +0200 Subject: [PATCH 194/638] ROX-36534: Add roxctl tar.gz (#22434) --- image/rhel/konflux.Dockerfile | 9 ++- rpms.in.yaml | 1 + rpms.lock.yaml | 132 ++++++++++++++++++++-------------- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 05832d6b6ca37..34683030b7a61 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -3,7 +3,7 @@ ARG PG_VERSION=15 FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS go-builder -RUN dnf -y install --allowerasing jq +RUN dnf -y install --allowerasing jq pigz WORKDIR /go/src/github.com/stackrox/rox/app @@ -36,6 +36,13 @@ RUN mkdir -p image/rhel/docs/api/v1 && \ RUN make copy-go-binaries-to-image-dir +RUN cd /go/src/github.com/stackrox/rox/app/image/rhel/bin && \ + shopt -s nullglob && \ + for f in roxctl-*; do \ + [[ -f "$f" && -x "$f" ]] || continue; \ + tar -I pigz -cvf "${f%.exe}.tar.gz" "$f"; \ + done + FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:7679e533a1b91b206351b2b0b574f23de0697de57e98244cafbd30ed6879a336 as ui-builder diff --git a/rpms.in.yaml b/rpms.in.yaml index 70e8fdcaa730b..8df6cca8fdb6a 100644 --- a/rpms.in.yaml +++ b/rpms.in.yaml @@ -4,6 +4,7 @@ packages: # builder stage in image/rhel/konflux.Dockerfile - jq +- pigz # final stage in image/rhel/konflux.Dockerfile - findutils - postgresql diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 8aed50cd25edf..71a6d84f3be39 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2874189 - checksum: sha256:4eaed7563988ff556b2c52e8e90195f42dadeaae5c5c7e849af2d3f5a87afdd0 + size: 2878589 + checksum: sha256:87eed21015538c9fade652055715872c8a1d7fc5d38df349a3c44e6f27530804 name: kernel-headers - evr: 5.14.0-687.42.1.el9_8 - sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm + evr: 5.14.0-687.44.1.el9_8 + sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -879,6 +879,13 @@ arches: name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pigz-2.8-1.el9.aarch64.rpm + repoid: rhel-9-for-aarch64-baseos-rpms + size: 93525 + checksum: sha256:159d67ec9f3dd3925521b92907b450e27a3932f770a48a3702b67888519ef4e9 + name: pigz + evr: 2.8-1.el9 + sourcerpm: pigz-2.8-1.el9.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 45196 @@ -1279,12 +1286,6 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm - repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152411531 - checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 - name: kernel - evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1531,6 +1532,12 @@ arches: checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a name: pcre2 evr: 10.40-6.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm + repoid: rhel-9-for-aarch64-baseos-source-rpms + size: 131701 + checksum: sha256:2eb6030553235feec36ab1406d80066f60ca545bf5a073a43a2a841a70892d2a + name: pigz + evr: 2.8-1.el9 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pkgconf-1.7.3-10.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 310904 @@ -1622,10 +1629,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/1d3cc796cab3c0dd44dada85781f30cba28ce9786bee64eed403cf9e6005e153-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0f4d325d39208aaf50aeec23a12b0a49fa86588b9c3e291429f39252b895b605-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms size: 65846 - checksum: sha256:1d3cc796cab3c0dd44dada85781f30cba28ce9786bee64eed403cf9e6005e153 + checksum: sha256:0f4d325d39208aaf50aeec23a12b0a49fa86588b9c3e291429f39252b895b605 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1698,13 +1705,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2898017 - checksum: sha256:57297ffdb162abd9cd49914972a2d43bda711f9d4ead2a90cfc9331f93daf0cf + size: 2902293 + checksum: sha256:0784f765e44b42d297bc6cbcdaa2e9a8970b7d1e909d0e2aa87c75f8c65197c3 name: kernel-headers - evr: 5.14.0-687.42.1.el9_8 - sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm + evr: 5.14.0-687.44.1.el9_8 + sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -2510,6 +2517,13 @@ arches: name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pigz-2.8-1.el9.ppc64le.rpm + repoid: rhel-9-for-ppc64le-baseos-rpms + size: 101362 + checksum: sha256:6488d453e558f9418cb83b605894f79839977315c5689275dd26674c3da73275 + name: pigz + evr: 2.8-1.el9 + sourcerpm: pigz-2.8-1.el9.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 46315 @@ -2910,12 +2924,6 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm - repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152411531 - checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 - name: kernel - evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3168,6 +3176,12 @@ arches: checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a name: pcre2 evr: 10.40-6.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm + repoid: rhel-9-for-ppc64le-baseos-source-rpms + size: 131701 + checksum: sha256:2eb6030553235feec36ab1406d80066f60ca545bf5a073a43a2a841a70892d2a + name: pigz + evr: 2.8-1.el9 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pkgconf-1.7.3-10.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 310904 @@ -3259,10 +3273,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/6df8b63607e968c2033b6ac4bfbdc54ba278886844e7c040e50ab62a3dd831cb-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/955a1862a29b3f1fc04251f0e8cdf847e6549cde0f8c2866af52281b5685635e-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms size: 65342 - checksum: sha256:6df8b63607e968c2033b6ac4bfbdc54ba278886844e7c040e50ab62a3dd831cb + checksum: sha256:955a1862a29b3f1fc04251f0e8cdf847e6549cde0f8c2866af52281b5685635e - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3335,13 +3349,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2904169 - checksum: sha256:1ea7397bda094b9a2848af9b91e46731863b4e7e820c9c1fffa9e7a4fa07672d + size: 2908493 + checksum: sha256:e2b6ad52e6746e5a0311be5cdd15298888d1f5af72088b724db7303657c08b72 name: kernel-headers - evr: 5.14.0-687.42.1.el9_8 - sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm + evr: 5.14.0-687.44.1.el9_8 + sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -4140,6 +4154,13 @@ arches: name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pigz-2.8-1.el9.s390x.rpm + repoid: rhel-9-for-s390x-baseos-rpms + size: 96630 + checksum: sha256:773b977c92e2b5c15ca319ab8392e7a0722dc7cd65434ad93f11269528066037 + name: pigz + evr: 2.8-1.el9 + sourcerpm: pigz-2.8-1.el9.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 45258 @@ -4534,12 +4555,6 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm - repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152411531 - checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 - name: kernel - evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4786,6 +4801,12 @@ arches: checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a name: pcre2 evr: 10.40-6.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm + repoid: rhel-9-for-s390x-baseos-source-rpms + size: 131701 + checksum: sha256:2eb6030553235feec36ab1406d80066f60ca545bf5a073a43a2a841a70892d2a + name: pigz + evr: 2.8-1.el9 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pkgconf-1.7.3-10.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 310904 @@ -4877,10 +4898,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/58ca23e61b6e5f0529bb37677aad2f0c9de777270b2d77b91d0fbfb3665db082-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/c6082334ed48bc1d5349e998e1c23b9b71a58734ea71d2ebfe84f2334335dba7-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms size: 63808 - checksum: sha256:58ca23e61b6e5f0529bb37677aad2f0c9de777270b2d77b91d0fbfb3665db082 + checksum: sha256:c6082334ed48bc1d5349e998e1c23b9b71a58734ea71d2ebfe84f2334335dba7 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4960,13 +4981,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.42.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2913473 - checksum: sha256:6a2a1bdb7d755358f35a283de880abb48b359aedd5fdfcd2da80bbe03541688c + size: 2917773 + checksum: sha256:e97b12ed737d41e2e34db2d3f3659a5f250ef940fcc9c4dd97235009c8706e7f name: kernel-headers - evr: 5.14.0-687.42.1.el9_8 - sourcerpm: kernel-5.14.0-687.42.1.el9_8.src.rpm + evr: 5.14.0-687.44.1.el9_8 + sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5744,6 +5765,13 @@ arches: name: pcre2-syntax evr: 10.40-6.el9 sourcerpm: pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pigz-2.8-1.el9.x86_64.rpm + repoid: rhel-9-for-x86_64-baseos-rpms + size: 99148 + checksum: sha256:3d468520034285d071ad3ae5cdf8726d88e66a24c41aa6d4bd7919a3e32a0cd3 + name: pigz + evr: 2.8-1.el9 + sourcerpm: pigz-2.8-1.el9.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pkgconf-1.7.3-10.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 45675 @@ -6144,12 +6172,6 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.42.1.el9_8.src.rpm - repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152411531 - checksum: sha256:bd857807b86ea70d861a17f740fba6892f3a98b7a6cd4ac6e7c6f9ebea1f6ed2 - name: kernel - evr: 5.14.0-687.42.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6396,6 +6418,12 @@ arches: checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a name: pcre2 evr: 10.40-6.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm + repoid: rhel-9-for-x86_64-baseos-source-rpms + size: 131701 + checksum: sha256:2eb6030553235feec36ab1406d80066f60ca545bf5a073a43a2a841a70892d2a + name: pigz + evr: 2.8-1.el9 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pkgconf-1.7.3-10.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 310904 @@ -6487,7 +6515,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/4922fd428e4deac3257e65043e322f514d7300a15d85cdbb9df469db892f88b1-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/a495c1c5432347fd4d0a098d82615cb3ec1f2b993088ff6b8f59f4fed922a288-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms size: 68056 - checksum: sha256:4922fd428e4deac3257e65043e322f514d7300a15d85cdbb9df469db892f88b1 + checksum: sha256:a495c1c5432347fd4d0a098d82615cb3ec1f2b993088ff6b8f59f4fed922a288 From 15f285268fd16ae8f3d57c2a8df213a18fb66497 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:05:08 +0000 Subject: [PATCH 195/638] Update all dependencies (#22642) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/operator-index-build.yaml | 2 +- image/rhel/konflux.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 788a72b6a9844..dc554300a0c3a 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1788245275@sha256:8cf89835994846ca0dffb9078e3a5638c57ec6175750f0af02fbe9c9942696d3 + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1788409979@sha256:5e68f09a652ac6627a83c57655e42e24575efb278b54336039c9308607fc6b21 imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 34683030b7a61..50beb06e86deb 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -44,7 +44,7 @@ RUN cd /go/src/github.com/stackrox/rox/app/image/rhel/bin && \ done -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:7679e533a1b91b206351b2b0b574f23de0697de57e98244cafbd30ed6879a336 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:a38a749f3a37a1c033932b4c13f3052f4958aa0eb7dbb0761cb3b5536ffe6878 as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app From b6b848d377bd468b9201a35c3b5d1de4630798d2 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 04:14:27 +0000 Subject: [PATCH 196/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 49b5585 (#22641) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index ac257eacdd4bf..17f5798fbf40e 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 205ab4e53feb6..325d6f2d4453f 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 3fcc1d2413b33..f8dd80df45c2f 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 1ea5413bb3c0a..b5200a3ec85a3 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 6fff04f4bf73a..77f181df728db 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index dc554300a0c3a..b0dabcda71d7c 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 4a7a7603b37f6..ea8b5e386632d 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index c5be5abdec5b2..a81a4ddaa3c0a 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:60dd9e07df4866b3e0ae5785e3f612cfd641771018238c6354f648ae477adc49 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d - name: kind value: task resolver: bundles From 25df4613613a0b307fb439456906e8286ff86b05 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Fri, 4 Sep 2026 09:30:33 +0200 Subject: [PATCH 197/638] fix(migrator): use proto getters in policy migration test helper (#22589) --- .../policymigrationhelper/postgres_policy_migrator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go b/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go index ea24aabc2dd05..c6b0cf1c6bd6f 100644 --- a/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go +++ b/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go @@ -357,7 +357,7 @@ func (s *postgresPolicyMigratorTestSuite) getAllCategories(ctx context.Context) } categoryMap := make(map[string]string, len(categories)) for _, c := range categories { - categoryMap[c.Name] = c.Id + categoryMap[c.GetName()] = c.GetId() } return categoryMap, nil } From 9620a6b2d1ca22eeabdc080c93e5f740cd904fdd Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:38:28 +0200 Subject: [PATCH 198/638] ROX-36616: Cover VirtualMachineV2Service for VM in e2e test (#22516) --- tests/e2e/run-vm-scanning.sh | 11 +- tests/e2e/vm-scanning-lib.sh | 119 +++++++-- tests/vm_scanning_suite_test.go | 155 +++++++++++- tests/vm_scanning_test.go | 194 ++++++++++++-- tests/vmhelpers/central_v2.go | 306 +++++++++++++++++++++++ tests/vmhelpers/central_v2_test.go | 288 +++++++++++++++++++++ tests/vmhelpers/roxagent_quadlet.go | 64 ++++- tests/vmhelpers/roxagent_quadlet_test.go | 98 ++++++++ tests/vmhelpers/wait_test.go | 1 + 9 files changed, 1182 insertions(+), 54 deletions(-) create mode 100644 tests/vmhelpers/central_v2.go create mode 100644 tests/vmhelpers/central_v2_test.go diff --git a/tests/e2e/run-vm-scanning.sh b/tests/e2e/run-vm-scanning.sh index 6a2f4158bf308..2587aeb93f57f 100755 --- a/tests/e2e/run-vm-scanning.sh +++ b/tests/e2e/run-vm-scanning.sh @@ -30,8 +30,15 @@ test_vm_scanning_e2e() { if ! ensure_virtctl_binary; then if is_CI; then - warn "Secure virtctl download failed. Falling back to insecure curl -k download path in CI." - ensure_virtctl_binary_insecure + if [[ "${_VIRTCTL_CLUSTER_DOWNLOAD_NONGZIP:-0}" != 1 ]]; then + warn "Secure virtctl download failed. Falling back to insecure curl -k download path in CI." + ensure_virtctl_binary_insecure || true + fi + if ! command -v virtctl >/dev/null; then + warn "Cluster ConsoleCLIDownload did not serve virtctl. Falling back to the matching KubeVirt GitHub release." + _install_virtctl_from_kubevirt_release \ + || die "Failed to install virtctl from cluster ConsoleCLIDownload and KubeVirt GitHub release" + fi else die "Secure virtctl download failed. Refusing insecure curl -k fallback outside CI. Set VIRTCTL_PATH or fix cluster ingress trust material." fi diff --git a/tests/e2e/vm-scanning-lib.sh b/tests/e2e/vm-scanning-lib.sh index 2b8bb13c81ff0..37d75054a3283 100755 --- a/tests/e2e/vm-scanning-lib.sh +++ b/tests/e2e/vm-scanning-lib.sh @@ -87,44 +87,131 @@ _fetch_cluster_ingress_ca() { printf '%s\n' "$ca_bundle" } +# Directory to install a downloaded virtctl binary into. +# PATH is not updated here: callers invoke this via command substitution, which +# would discard an export. Call _virtctl_add_install_dir_to_path after assigning dest. +_virtctl_install_dir() { + local dest="/usr/local/bin" + if [[ ! -w "$dest" ]]; then + dest="$(mktemp -d)" + fi + printf '%s\n' "$dest" +} + +_virtctl_add_install_dir_to_path() { + local dest="$1" + [[ "$dest" == "/usr/local/bin" ]] || export PATH="${dest}:${PATH}" +} + +# Set to 1 when ConsoleCLIDownload returned a body that was not a gzip tarball. +# TLS retry (-k) cannot help in that case; callers should skip it. +_VIRTCTL_CLUSTER_DOWNLOAD_NONGZIP=0 + # Downloads and installs virtctl using the provided curl TLS arguments. +# Returns 1 on failure (does not die) so callers can fall back. # Usage: _download_and_install_virtctl [curl_tls_args...] # Example: _download_and_install_virtctl --cacert /path/to/ca.pem # _download_and_install_virtctl -k _download_and_install_virtctl() { # CI Prow workers are always Linux x86_64 (n2-standard-8 machine type). - local download_url - download_url="$(oc get consoleclidownload virtctl-clidownloads-kubevirt-hyperconverged \ - -o jsonpath='{.spec.links[?(@.text=="Download virtctl for Linux for x86_64")].href}' 2>/dev/null || true)" + local dest + dest="$(_virtctl_install_dir)" + _virtctl_add_install_dir_to_path "$dest" + _VIRTCTL_CLUSTER_DOWNLOAD_NONGZIP=0 + + # ConsoleCLIDownload can exist while the ingress backend still serves HTML. + # Wait for the href, then retry until the body is a gzip tarball. + local download_url="" + local attempt=0 + while (( attempt < 30 )); do + attempt=$((attempt + 1)) + download_url="$(oc get consoleclidownload virtctl-clidownloads-kubevirt-hyperconverged \ + -o jsonpath='{.spec.links[?(@.text=="Download virtctl for Linux for x86_64")].href}' 2>/dev/null || true)" + if [[ -n "$download_url" ]]; then + break + fi + info "Waiting for ConsoleCLIDownload virtctl href (${attempt}/30)" + sleep 10 + done if [[ -z "$download_url" ]]; then - die "virtctl not found on PATH and ConsoleCLIDownload resource not available" + warn "virtctl not found on PATH and ConsoleCLIDownload resource not available" + return 1 fi - local dest="/usr/local/bin" - if [[ ! -w "$dest" ]]; then - dest="$(mktemp -d)" - export PATH="${dest}:${PATH}" + local cli_deploy="hyperconverged-cluster-cli-download" + if oc get deploy -n openshift-cnv "$cli_deploy" >/dev/null 2>&1; then + info "Waiting for ${cli_deploy} rollout..." + oc rollout status "deploy/${cli_deploy}" -n openshift-cnv --timeout=300s || true fi + local archive saw_nongzip=0 + archive="$(mktemp)" info "Downloading virtctl from ${download_url}" - if ! curl -sSL "$@" "$download_url" | tar xz -C "$dest" virtctl; then - die "Failed to download virtctl from ${download_url}" + attempt=0 + while (( attempt < 30 )); do + attempt=$((attempt + 1)) + if curl -sSL --connect-timeout 30 --max-time 120 "$@" -o "$archive" "$download_url"; then + if gzip -t "$archive" 2>/dev/null \ + && tar xz -C "$dest" virtctl -f "$archive" \ + && [[ -f "${dest}/virtctl" ]]; then + rm -f "$archive" + chmod +x "${dest}/virtctl" + info "virtctl installed at ${dest}/virtctl" + return 0 + fi + saw_nongzip=1 + fi + info "virtctl tarball not ready yet (attempt ${attempt}/30), retrying in 10s" + sleep 10 + done + rm -f "$archive" + _VIRTCTL_CLUSTER_DOWNLOAD_NONGZIP=$saw_nongzip + warn "Failed to download virtctl from ${download_url}" + return 1 +} + +# Installs virtctl from the KubeVirt GitHub release matching the cluster's observed version. +# ConsoleCLIDownload can stay on HTML while CNV's CSV is Installing or Failed. +_install_virtctl_from_kubevirt_release() { + local version dest url bin + version="$(oc get kubevirt -n openshift-cnv -o jsonpath='{.items[0].status.observedKubeVirtVersion}' 2>/dev/null || true)" + if [[ -z "$version" ]]; then + version="$(oc get kubevirt -n openshift-cnv -o jsonpath='{.items[0].status.targetKubeVirtVersion}' 2>/dev/null || true)" + fi + if [[ -z "$version" ]]; then + warn "No observedKubeVirtVersion on kubevirt CRs; cannot download virtctl from GitHub" + return 1 fi - if [[ ! -f "${dest}/virtctl" ]]; then - die "Downloaded archive from ${download_url} does not contain virtctl" + + dest="$(_virtctl_install_dir)" + _virtctl_add_install_dir_to_path "$dest" + url="https://github.com/kubevirt/kubevirt/releases/download/${version}/virtctl-${version}-linux-amd64" + bin="${dest}/virtctl" + info "Downloading virtctl ${version} from ${url}" + if ! curl -fsSL --connect-timeout 30 --max-time 120 --retry 5 --retry-delay 5 -o "$bin" "$url"; then + warn "GitHub virtctl download failed from ${url}" + rm -f "$bin" + return 1 fi - chmod +x "${dest}/virtctl" - info "virtctl installed at ${dest}/virtctl" + if [[ "$(head -c 4 "$bin")" != $'\x7fELF' ]]; then + warn "GitHub virtctl download was not an ELF binary" + rm -f "$bin" + return 1 + fi + chmod +x "$bin" + info "virtctl installed at ${bin}" + return 0 } # Downloads virtctl from ConsoleCLIDownload using verified TLS only. ensure_virtctl_binary() { _use_existing_virtctl_binary_if_available && return - local ca_bundle + local ca_bundle rc=0 ca_bundle="$(_fetch_cluster_ingress_ca)" - _download_and_install_virtctl --cacert "$ca_bundle" + _download_and_install_virtctl --cacert "$ca_bundle" || rc=$? rm -f "$ca_bundle" + return "$rc" } # Downloads virtctl from ConsoleCLIDownload with curl -k. diff --git a/tests/vm_scanning_suite_test.go b/tests/vm_scanning_suite_test.go index c37da24f99308..c2266513aa0cd 100644 --- a/tests/vm_scanning_suite_test.go +++ b/tests/vm_scanning_suite_test.go @@ -108,8 +108,12 @@ type VMScanningSuite struct { dynamicClient dynamic.Interface namespace string - conn *grpc.ClientConn - vmClient v2.VirtualMachineServiceClient + conn *grpc.ClientConn + vmClient v2.VirtualMachineServiceClient + vmV2Client v2.VirtualMachineV2ServiceClient + // enhancedVMModel follows Central's ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL. + // That flag selects VirtualMachineV2Service vs VirtualMachineService. + enhancedVMModel bool virtctl vmhelpers.Virtctl @@ -158,10 +162,12 @@ func (s *VMScanningSuite) SetupSuite() { s.logf("VM scanning setup: connect to Central gRPC") s.conn = centralgrpc.GRPCConnectionToCentral(t) s.vmClient = v2.NewVirtualMachineServiceClient(s.conn) + s.vmV2Client = v2.NewVirtualMachineV2ServiceClient(s.conn) s.logf("VM scanning setup: verify central/sensor connectivity and feature gates") s.mustWaitForHealthyCentralSensorConnection() s.mustVerifyVirtualMachinesFeatureEnabled() + s.resolveVMAPI() s.logf("VM scanning setup: verify cluster VSOCK readiness") mustVerifyClusterVSOCKReady(t, s.ctx, s.k8sClient, s.dynamicClient) @@ -313,6 +319,57 @@ func (s *VMScanningSuite) mustWaitForHealthyCentralSensorConnection() { waitUntilCentralSensorConnectionIs(s.T(), s.ctx, storage.ClusterHealthStatus_HEALTHY) } +// resolveVMAPI reads ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL from Central and +// checks that the matching gRPC service is registered. +func (s *VMScanningSuite) resolveVMAPI() { + t := s.T() + t.Helper() + + flag := features.VirtualMachinesEnhancedDataModel + s.enhancedVMModel = s.centralFeatureEnabled(flag) + s.logf("VM scanning setup: Central %s=%v", flag.EnvVar(), s.enhancedVMModel) + + if s.enhancedVMModel { + _, err := s.vmV2Client.ListVMs(s.ctx, &v2.ListVMsRequest{}) + require.NoError(t, err, "VirtualMachineV2Service must be registered when %s is enabled", flag.EnvVar()) + return + } + _, err := s.vmClient.ListVirtualMachines(s.ctx, &v2.ListVirtualMachinesRequest{}) + require.NoError(t, err, "VirtualMachineService must be registered when %s is disabled", flag.EnvVar()) +} + +// centralFeatureEnabled reports whether flag is on in the Central deployment, +// matching features.Enabled: explicit true/false, otherwise the flag default. +func (s *VMScanningSuite) centralFeatureEnabled(flag features.FeatureFlag) bool { + t := s.T() + t.Helper() + + obj, err := s.k8sClient.AppsV1().Deployments(namespaces.StackRox).Get(s.ctx, "central", metaV1.GetOptions{}) + require.NoError(t, err, "get Deployment %s/central", namespaces.StackRox) + for _, c := range obj.Spec.Template.Spec.Containers { + if c.Name != "central" { + continue + } + for _, e := range c.Env { + if e.Name != flag.EnvVar() { + continue + } + switch strings.ToLower(strings.TrimSpace(e.Value)) { + case "false": + return false + case "true": + return true + default: + return flag.Default() + } + } + return flag.Default() + } + require.FailNowf(t, "container central not found in Deployment stackrox/central", + "available containers: %s", formatContainerNames(obj.Spec.Template.Spec.Containers)) + return false +} + func (s *VMScanningSuite) mustVerifyVirtualMachinesFeatureEnabled() { ctx, cancel := context.WithTimeout(s.ctx, featureGateVerifyTimeout) defer cancel() @@ -678,6 +735,20 @@ func (s *VMScanningSuite) vmSpecToRequest(sp vmhelpers.VMSpec) vmhelpers.VMReque } } +func (s *VMScanningSuite) skipUnlessLegacyVMAPI(t *testing.T) { + t.Helper() + if s.enhancedVMModel { + t.Skip("VirtualMachineService is not used when ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL is enabled") + } +} + +func (s *VMScanningSuite) skipUnlessV2VMAPI(t *testing.T) { + t.Helper() + if !s.enhancedVMModel { + t.Skip("VirtualMachineV2Service is not used when ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL is disabled") + } +} + func (s *VMScanningSuite) mustListVMByNamespaceAndName(namespace, name string) *v2.VirtualMachine { t := s.T() t.Helper() @@ -696,6 +767,24 @@ func (s *VMScanningSuite) mustGetVM(id string) *v2.VirtualMachine { return resp } +func (s *VMScanningSuite) mustListV2VMByNamespaceAndName(namespace, name string) *v2.VMListItem { + t := s.T() + t.Helper() + vm, err := vmhelpers.ListV2VMByNamespaceName(s.ctx, s.vmV2Client, namespace, name) + require.NoError(t, err) + require.NotNil(t, vm, "ListVMs: no VM for namespace=%q name=%q", namespace, name) + return vm +} + +func (s *VMScanningSuite) mustGetVMV2(id string) *v2.VMDetail { + t := s.T() + t.Helper() + resp, err := s.vmV2Client.GetVM(s.ctx, &v2.GetVMRequest{Id: id}) + require.NoError(t, err) + require.NotNil(t, resp) + return resp +} + // waitForScannerV4Initialized blocks until the Scanner V4 matcher deployment // is K8s-ready. When SCANNER_V4_MATCHER_READINESS=vulnerability (the default // in CI via lib.sh), the K8s readiness probe already gates on the vuln DB @@ -727,15 +816,40 @@ func (s *VMScanningSuite) ensureRoxagentServing(ctx context.Context, vm *VMHandl return vmhelpers.EnsureRoxagentServing(ctx, virt, vm.Namespace, vm.Name) } -// waitForScan polls Central in order until scan data is visible. -func (s *VMScanningSuite) waitForScan(ctx context.Context, vm *VMHandle) (*v2.VirtualMachine, error) { +// centralScanSnapshot is the scan-ready view from the VM API selected by Central's flag. +type centralScanSnapshot struct { + ID string + Legacy *v2.VirtualMachine + Detail *v2.VMDetail +} + +// waitForScan polls the VM API selected by ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL. +func (s *VMScanningSuite) waitForScan(ctx context.Context, vm *VMHandle) (*centralScanSnapshot, error) { if vm == nil { return nil, errors.New("waitForScan: nil VM handle") } - s.logf("scan wait %s/%s: start (timeout=%v poll=%v)", vm.Namespace, vm.Name, s.cfg.ScanTimeout, s.cfg.ScanPollInterval) + s.logf("scan wait %s/%s: start (timeout=%v poll=%v enhancedVMModel=%v)", + vm.Namespace, vm.Name, s.cfg.ScanTimeout, s.cfg.ScanPollInterval, s.enhancedVMModel) waitCtx, cancel := context.WithTimeout(ctx, s.cfg.ScanTimeout) defer cancel() + if s.enhancedVMModel { + detail, err := s.waitForV2Scan(waitCtx, vm) + if err != nil { + return nil, err + } + vm.ID = detail.GetId() + return ¢ralScanSnapshot{ID: vm.ID, Detail: detail}, nil + } + legacy, err := s.waitForLegacyScan(waitCtx, vm) + if err != nil { + return nil, err + } + vm.ID = legacy.GetId() + return ¢ralScanSnapshot{ID: vm.ID, Legacy: legacy}, nil +} + +func (s *VMScanningSuite) waitForLegacyScan(waitCtx context.Context, vm *VMHandle) (*v2.VirtualMachine, error) { baseOpts := vmhelpers.WaitOptions{ Timeout: s.cfg.ScanTimeout, PollInterval: s.cfg.ScanPollInterval, @@ -748,7 +862,7 @@ func (s *VMScanningSuite) waitForScan(ctx context.Context, vm *VMHandle) (*v2.Vi } vm.ID = present.GetId() - s.logf("%s/%s: VM appeared in Central (id=%q), waiting for namespace/name fields to be populated", vm.Namespace, vm.Name, vm.ID) + s.logf("%s/%s: VM appeared in Central via VirtualMachineService (id=%q), waiting for namespace/name fields", vm.Namespace, vm.Name, vm.ID) if _, err := vmhelpers.WaitForVMIdentityFields(waitCtx, s.vmClient, baseOpts, present.GetId(), vm.Namespace, vm.Name); err != nil { return nil, err } @@ -768,6 +882,35 @@ func (s *VMScanningSuite) waitForScan(ctx context.Context, vm *VMHandle) (*v2.Vi return vmhelpers.WaitForScanReady(waitCtx, s.vmClient, baseOpts, present.GetId()) } +func (s *VMScanningSuite) waitForV2Scan(waitCtx context.Context, vm *VMHandle) (*v2.VMDetail, error) { + baseOpts := vmhelpers.WaitOptions{ + Timeout: s.cfg.ScanTimeout, + PollInterval: s.cfg.ScanPollInterval, + Logf: s.logf, + } + + present, err := vmhelpers.WaitForV2VMPresentInCentral(waitCtx, s.vmV2Client, baseOpts, vm.Namespace, vm.Name) + if err != nil { + return nil, err + } + vm.ID = present.GetId() + + s.logf("%s/%s: VM appeared in Central via VirtualMachineV2Service (id=%q), waiting for namespace/name fields", vm.Namespace, vm.Name, vm.ID) + if _, err := vmhelpers.WaitForV2VMIdentityFields(waitCtx, s.vmV2Client, baseOpts, present.GetId(), vm.Namespace, vm.Name); err != nil { + return nil, err + } + s.logf("%s/%s: waiting for Central to report VM as VM_STATE_RUNNING", vm.Namespace, vm.Name) + if _, err := vmhelpers.WaitForV2VMRunningInCentral(waitCtx, s.vmV2Client, baseOpts, present.GetId()); err != nil { + return nil, err + } + s.logf("%s/%s: waiting for latest_scan to arrive in Central", vm.Namespace, vm.Name) + if _, err := vmhelpers.WaitForV2VMLatestScan(waitCtx, s.vmV2Client, baseOpts, present.GetId()); err != nil { + return nil, err + } + s.logf("%s/%s: waiting for all v2 components to be vulnerability-matched (no NOT_SCANNED)", vm.Namespace, vm.Name) + return vmhelpers.WaitForV2ScanReady(waitCtx, s.vmV2Client, baseOpts, present.GetId()) +} + func (s *VMScanningSuite) resourceDeleteTimeout() time.Duration { if s.cfg != nil && s.cfg.DeleteTimeout > 0 { return s.cfg.DeleteTimeout diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index 9e991c5054172..14e6252436093 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -28,7 +28,7 @@ func (s *VMScanningSuite) TestScanPipeline() { } s.T().Run(vm.Name, func(t *testing.T) { - var first *v2.VirtualMachine + var snapshot *centralScanSnapshot roxagentOK := false t.Run("EnsureRoxagentServing", func(t *testing.T) { @@ -45,16 +45,20 @@ func (s *VMScanningSuite) TestScanPipeline() { t.Run("WaitForScan", func(t *testing.T) { var err error - first, err = s.waitForScan(s.ctx, vm) + snapshot, err = s.waitForScan(s.ctx, vm) require.NoError(t, err) - vm.ID = first.GetId() + require.NotEmpty(t, snapshot.ID) + vm.ID = snapshot.ID }) - if first == nil { + if snapshot == nil { t.Log("skipping remaining subtests: scan did not appear in Central") return } t.Run("CentralVMMetadata", func(t *testing.T) { + s.skipUnlessLegacyVMAPI(t) + first := snapshot.Legacy + require.NotNil(t, first, "WaitForScan returned no VirtualMachine") listed := s.mustListVMByNamespaceAndName(vm.Namespace, vm.Name) require.Equal(t, listed.GetId(), first.GetId()) require.Equal(t, vm.Name, first.GetName()) @@ -67,6 +71,9 @@ func (s *VMScanningSuite) TestScanPipeline() { }) t.Run("CentralScanComponents", func(t *testing.T) { + s.skipUnlessLegacyVMAPI(t) + first := snapshot.Legacy + require.NotNil(t, first) for _, component := range first.GetScan().GetComponents() { require.NotContains(t, component.GetNotes(), v2.ScanComponent_UNSCANNED) } @@ -74,28 +81,108 @@ func (s *VMScanningSuite) TestScanPipeline() { }) t.Run("CentralScanOperatingSystem", func(t *testing.T) { - os := first.GetScan().GetOperatingSystem() + s.skipUnlessLegacyVMAPI(t) + os := snapshot.Legacy.GetScan().GetOperatingSystem() require.NotEmpty(t, os, "scan.operating_system should be populated via Sensor DiscoveredData") }) t.Run("ConsistencyCheck", func(t *testing.T) { - fetched := s.mustGetVM(first.GetId()) - require.Equal(t, first.GetId(), fetched.GetId(), + s.skipUnlessLegacyVMAPI(t) + fetched := s.mustGetVM(snapshot.ID) + require.Equal(t, snapshot.ID, fetched.GetId(), "VM ID should remain stable after pull-mode scan") }) + t.Run("VirtualMachineV2GetVM", func(t *testing.T) { + s.skipUnlessV2VMAPI(t) + detail := s.mustGetVMV2(snapshot.ID) + require.Equal(t, snapshot.ID, detail.GetId()) + require.Equal(t, vm.Name, detail.GetName()) + require.Equal(t, vm.Namespace, detail.GetNamespace()) + require.NotEmpty(t, detail.GetClusterId()) + require.NotEmpty(t, detail.GetClusterName()) + require.Equal(t, v2.VirtualMachineV2State_VM_STATE_RUNNING, detail.GetState()) + require.NotNil(t, detail.GetLatestScan()) + require.NotNil(t, detail.GetLatestScan().GetScanTime()) + }) + + t.Run("VirtualMachineV2ListVMs", func(t *testing.T) { + s.skipUnlessV2VMAPI(t) + listed := s.mustListV2VMByNamespaceAndName(vm.Namespace, vm.Name) + require.Equal(t, snapshot.ID, listed.GetId()) + require.Equal(t, vm.Name, listed.GetName()) + require.Equal(t, vm.Namespace, listed.GetNamespace()) + require.NotEmpty(t, listed.GetClusterId()) + require.NotEmpty(t, listed.GetClusterName()) + require.Equal(t, v2.VirtualMachineV2State_VM_STATE_RUNNING, listed.GetState()) + + cves, total, err := vmhelpers.ListAllVMCVEsByVM(s.ctx, s.vmV2Client, snapshot.ID) + require.NoError(t, err) + require.Greater(t, total, int32(0), "scanned RHEL guest should report CVEs via ListVMCVEsByVM") + require.Equal(t, int(total), len(cves), "paginated CVE rows should cover total_count") + for _, row := range cves { + require.NotEmpty(t, row.GetCve()) + } + + distinct := distinctCVEIDs(cves) + require.Equal(t, int32(len(distinct)), vmhelpers.VulnCountBySeverityTotal(listed.GetCveSeverityCounts()), + "ListVMs.cveSeverityCounts totals must match distinct CVEs from ListVMCVEsByVM") + + summary, err := s.vmV2Client.GetVMVulnSummary(s.ctx, &v2.GetVMVulnSummaryRequest{Id: snapshot.ID}) + require.NoError(t, err) + require.Equal(t, int32(len(distinct)), vmhelpers.VulnCountBySeverityTotal(summary.GetSeverityCounts()), + "GetVMVulnSummary severity totals must match distinct CVEs from ListVMCVEsByVM") + }) + + t.Run("VirtualMachineV2ListVMCVEsByVM", func(t *testing.T) { + s.skipUnlessV2VMAPI(t) + cves, total, err := vmhelpers.ListAllVMCVEsByVM(s.ctx, s.vmV2Client, snapshot.ID) + require.NoError(t, err) + require.Greater(t, total, int32(0)) + require.NotEmpty(t, cves) + for _, row := range cves { + require.NotEmpty(t, row.GetCve()) + } + require.Equal(t, len(cves), len(distinctCVEIDs(cves)), "ListVMCVEsByVM CVE IDs must be unique") + + var found bool + for _, row := range cves { + comps, err := s.vmV2Client.GetVMCVEComponents(s.ctx, &v2.GetVMCVEComponentsRequest{ + VmId: snapshot.ID, + CveId: row.GetCve(), + }) + require.NoError(t, err) + if len(comps.GetComponents()) == 0 { + continue + } + require.GreaterOrEqual(t, row.GetAffectedComponentCount(), int32(1), + "CVE %q has %d components from GetVMCVEComponents but affected_component_count=%d", + row.GetCve(), len(comps.GetComponents()), row.GetAffectedComponentCount()) + found = true + break + } + require.True(t, found, "at least one ListVMCVEsByVM row should have components from GetVMCVEComponents") + }) + + t.Run("VirtualMachineV2ListVMComponents", func(t *testing.T) { + s.skipUnlessV2VMAPI(t) + comps, total, err := vmhelpers.ListAllVMComponents(s.ctx, s.vmV2Client, snapshot.ID) + require.NoError(t, err) + require.Greater(t, total, int32(0), "completed scan should list components") + require.NotEmpty(t, comps) + require.Equal(t, int(total), len(comps), "paginated component rows should cover total_count") + for _, c := range comps { + require.NotEqual(t, v2.ScanStatus_NOT_SCANNED, c.GetScanStatus()) + require.NotEqual(t, v2.ScanStatus_SCAN_PENDING, c.GetScanStatus()) + } + }) + // Regression test for ROX-36273: after a change to the RPM database (package removal), the agent should // detect the change on a later periodic rescan without being restarted. t.Run("Changes to RPM DB are detected by periodic rescan", func(t *testing.T) { - require.NotNil(t, first.GetScan().GetScanTime()) - baselineScanTime := first.GetScan().GetScanTime().AsTime() - baselineCount := len(first.GetScan().GetComponents()) - // bc is installed by stackrox/vm-images into every VM - // container-disk so this test has a known removable probe package. removed := vmhelpers.VMImageProbePackage - require.Contains(t, scanComponentNames(first), removed, - "baseline scan must include %q from the VM image build", removed) + baselineCount := s.requireProbePackagePresent(t, snapshot, removed) beforeInvocationID, err := vmhelpers.RoxagentServeInvocationID(s.ctx, virt, vm.Namespace, vm.Name) require.NoError(t, err) @@ -107,19 +194,32 @@ func (s *VMScanningSuite) TestScanPipeline() { // Central moves scan_time. const minScanAdvances = 2 waitTimeout := max(s.cfg.ScanTimeout, 2*vmhelpers.E2ERescanInterval+2*vmhelpers.E2EScraperPollInterval+3*time.Minute) - t.Logf("removed package %q; waiting for %d scan_time advances (rescan=%s scraper=%s timeout=%s; baseline components=%d scan_time=%s)", + t.Logf("removed package %q; waiting for %d scan_time advances (rescan=%s scraper=%s timeout=%s; baseline components=%d)", removed, minScanAdvances, vmhelpers.E2ERescanInterval, vmhelpers.E2EScraperPollInterval, waitTimeout, - baselineCount, baselineScanTime.UTC().Format(time.RFC3339)) - - updated, err := vmhelpers.WaitForScanMissingComponent( - s.ctx, s.vmClient, vmhelpers.WaitOptions{ - Timeout: waitTimeout, - PollInterval: s.cfg.ScanPollInterval, - Logf: s.logf, - }, first.GetId(), removed, baselineScanTime, minScanAdvances) - require.NoError(t, err) - require.Equal(t, baselineCount-1, len(updated.GetScan().GetComponents()), - "exactly one component should disappear after removing %q", removed) + baselineCount) + + opts := vmhelpers.WaitOptions{ + Timeout: waitTimeout, + PollInterval: s.cfg.ScanPollInterval, + Logf: s.logf, + } + if s.enhancedVMModel { + require.NotNil(t, snapshot.Detail.GetLatestScan().GetScanTime()) + baselineScanTime := snapshot.Detail.GetLatestScan().GetScanTime().AsTime() + updated, err := vmhelpers.WaitForV2ScanMissingComponent( + s.ctx, s.vmV2Client, opts, snapshot.ID, removed, baselineScanTime, minScanAdvances) + require.NoError(t, err) + require.Equal(t, baselineCount-1, len(updated), + "exactly one component should disappear after removing %q", removed) + } else { + require.NotNil(t, snapshot.Legacy.GetScan().GetScanTime()) + baselineScanTime := snapshot.Legacy.GetScan().GetScanTime().AsTime() + updated, err := vmhelpers.WaitForScanMissingComponent( + s.ctx, s.vmClient, opts, snapshot.ID, removed, baselineScanTime, minScanAdvances) + require.NoError(t, err) + require.Equal(t, baselineCount-1, len(updated.GetScan().GetComponents()), + "exactly one component should disappear after removing %q", removed) + } require.NoError(t, vmhelpers.RoxagentServeDidNotRestart( s.ctx, virt, vm.Namespace, vm.Name, beforeInvocationID), @@ -129,6 +229,21 @@ func (s *VMScanningSuite) TestScanPipeline() { } } +func (s *VMScanningSuite) requireProbePackagePresent(t *testing.T, snapshot *centralScanSnapshot, pkg string) int { + t.Helper() + if s.enhancedVMModel { + comps, _, err := vmhelpers.ListAllVMComponents(s.ctx, s.vmV2Client, snapshot.ID) + require.NoError(t, err) + require.Contains(t, componentRowNames(comps), pkg, + "baseline v2 component list must include %q from the VM image build", pkg) + return len(comps) + } + require.NotNil(t, snapshot.Legacy) + require.Contains(t, scanComponentNames(snapshot.Legacy), pkg, + "baseline scan must include %q from the VM image build", pkg) + return len(snapshot.Legacy.GetScan().GetComponents()) +} + func scanComponentNames(vm *v2.VirtualMachine) []string { comps := vm.GetScan().GetComponents() names := make([]string, 0, len(comps)) @@ -139,3 +254,30 @@ func scanComponentNames(vm *v2.VirtualMachine) []string { } return names } + +func componentRowNames(comps []*v2.VMComponentRow) []string { + names := make([]string, 0, len(comps)) + for _, c := range comps { + if name := c.GetName(); name != "" { + names = append(names, name) + } + } + return names +} + +func distinctCVEIDs(cves []*v2.VMCVERow) []string { + seen := make(map[string]struct{}, len(cves)) + ids := make([]string, 0, len(cves)) + for _, row := range cves { + id := row.GetCve() + if id == "" { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + ids = append(ids, id) + } + return ids +} diff --git a/tests/vmhelpers/central_v2.go b/tests/vmhelpers/central_v2.go new file mode 100644 index 0000000000000..a464951f2535e --- /dev/null +++ b/tests/vmhelpers/central_v2.go @@ -0,0 +1,306 @@ +package vmhelpers + +import ( + "context" + "fmt" + "slices" + "strings" + "time" + + v2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/search" +) + +// v2ListPageSize matches VirtualMachineV2Service's server-side defaultPageSize. +// Requests above that are clamped, so callers must page. +const ( + v2ListPageSize = 100 + v2ListMaxPages = 200 +) + +// ListV2VMByNamespaceName returns the first VM matching namespace and name. +// Returns (nil, nil) when no match is found. +func ListV2VMByNamespaceName(ctx context.Context, client v2.VirtualMachineV2ServiceClient, namespace, name string) (*v2.VMListItem, error) { + resp, err := client.ListVMs(ctx, &v2.ListVMsRequest{ + Query: &v2.RawQuery{ + Query: rawListQueryNamespaceAndName(namespace, name), + }, + }) + if err != nil { + return nil, err + } + if vms := resp.GetVms(); len(vms) > 0 { + return vms[0], nil + } + return nil, nil +} + +// WaitForV2VMPresentInCentral polls ListVMs until a VM matches namespace and name. +func WaitForV2VMPresentInCentral(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, namespace, name string) (*v2.VMListItem, error) { + var found *v2.VMListItem + err := pollUntil(ctx, opts, fmt.Sprintf("V2 VM present in Central (namespace=%q name=%q)", namespace, name), func(ctx context.Context) (bool, string, error) { + vm, err := ListV2VMByNamespaceName(ctx, client, namespace, name) + if err != nil { + return false, "", err + } + if vm == nil { + return false, "list returned no matching virtual machine", nil + } + found = vm + return true, fmt.Sprintf("id=%s", vm.GetId()), nil + }) + if err != nil { + return nil, err + } + return found, nil +} + +type vmV2ConditionCheck func(vm *v2.VMDetail) (done bool, detail string) + +func waitForV2VMCondition(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, id, desc string, check vmV2ConditionCheck) (*v2.VMDetail, error) { + var vm *v2.VMDetail + err := pollUntil(ctx, opts, desc, func(ctx context.Context) (bool, string, error) { + cur, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) + if err != nil { + return false, "", err + } + done, detail := check(cur) + if done { + vm = cur + } + return done, detail, nil + }) + if err != nil { + return nil, err + } + return vm, nil +} + +// WaitForV2VMIdentityFields polls GetVM until id maps to the expected namespace and name. +func WaitForV2VMIdentityFields(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, id, expectedNamespace, expectedName string) (*v2.VMDetail, error) { + return waitForV2VMCondition(ctx, client, opts, id, fmt.Sprintf("V2 VM identity fields (id=%q)", id), func(vm *v2.VMDetail) (bool, string) { + detail := fmt.Sprintf("namespace=%q name=%q", vm.GetNamespace(), vm.GetName()) + return vm.GetNamespace() == expectedNamespace && vm.GetName() == expectedName, detail + }) +} + +// WaitForV2VMRunningInCentral polls until the VM state is VM_STATE_RUNNING. +func WaitForV2VMRunningInCentral(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, id string) (*v2.VMDetail, error) { + return waitForV2VMCondition(ctx, client, opts, id, fmt.Sprintf("V2 VM RUNNING (id=%q)", id), func(vm *v2.VMDetail) (bool, string) { + st := vm.GetState() + return st == v2.VirtualMachineV2State_VM_STATE_RUNNING, fmt.Sprintf("state=%s", st) + }) +} + +// WaitForV2VMLatestScan polls until latest_scan is present with scan_time set. +func WaitForV2VMLatestScan(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, id string) (*v2.VMDetail, error) { + return waitForV2VMCondition(ctx, client, opts, id, fmt.Sprintf("V2 VM latest_scan (id=%q)", id), func(vm *v2.VMDetail) (bool, string) { + scan := vm.GetLatestScan() + if scan == nil { + return false, "latest_scan is nil" + } + if scan.GetScanTime() == nil { + return false, "latest_scan.scan_time is nil" + } + return true, "latest_scan present" + }) +} + +// WaitForV2ScanReady polls until the VM has scanned components and none are NOT_SCANNED. +func WaitForV2ScanReady(ctx context.Context, client v2.VirtualMachineV2ServiceClient, opts WaitOptions, id string) (*v2.VMDetail, error) { + var detail *v2.VMDetail + err := pollUntil(ctx, opts, fmt.Sprintf("V2 scan ready (id=%q)", id), func(ctx context.Context) (bool, string, error) { + vm, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) + if err != nil { + return false, "", err + } + scan := vm.GetLatestScan() + if scan == nil { + return false, "latest_scan is nil", nil + } + + comps, total, err := ListAllVMComponents(ctx, client, id) + if err != nil { + return false, "", err + } + + var ready, pending []string + if total > 0 { + ready = append(ready, fmt.Sprintf("components=%d", total)) + } else { + pending = append(pending, "components") + } + + if total > 0 && !slices.ContainsFunc(comps, func(c *v2.VMComponentRow) bool { + return c.GetScanStatus() == v2.ScanStatus_NOT_SCANNED || c.GetScanStatus() == v2.ScanStatus_SCAN_PENDING + }) { + ready = append(ready, "all-scanned") + } else { + pending = append(pending, "all-scanned") + } + + if os := scan.GetScanOs(); os != "" { + ready = append(ready, fmt.Sprintf("os=%q", os)) + } else if guestOS := vm.GetGuestOs(); guestOS != "" { + ready = append(ready, fmt.Sprintf("guest_os=%q", guestOS)) + } else if len(pending) == 0 { + ready = append(ready, "os=") + } else { + pending = append(pending, "os") + } + + pollDetail := fmt.Sprintf("ready:[%s] waiting:[%s]", strings.Join(ready, ","), strings.Join(pending, ",")) + if len(pending) == 0 { + detail = vm + } + return len(pending) == 0, pollDetail, nil + }) + if err != nil { + return nil, err + } + return detail, nil +} + +// ListAllVMComponents pages through ListVMComponents until every component is collected. +func ListAllVMComponents(ctx context.Context, client v2.VirtualMachineV2ServiceClient, vmID string) ([]*v2.VMComponentRow, int32, error) { + var all []*v2.VMComponentRow + var total int32 + for page := range v2ListMaxPages { + offset := int32(page) * v2ListPageSize + resp, err := client.ListVMComponents(ctx, &v2.ListVMComponentsRequest{ + VmId: vmID, + Query: &v2.RawQuery{ + Pagination: &v2.Pagination{ + Limit: v2ListPageSize, + Offset: offset, + SortOption: &v2.SortOption{Field: search.Component.String()}, + }, + }, + }) + if err != nil { + return nil, 0, err + } + total = resp.GetTotalCount() + all = append(all, resp.GetComponents()...) + if int32(len(all)) >= total || len(resp.GetComponents()) == 0 { + return all, total, nil + } + } + return nil, 0, fmt.Errorf("ListVMComponents: exceeded %d pages for vm %s (collected %d, total_count=%d)", v2ListMaxPages, vmID, len(all), total) +} + +// ListAllVMCVEsByVM pages through ListVMCVEsByVM until every CVE row is collected. +func ListAllVMCVEsByVM(ctx context.Context, client v2.VirtualMachineV2ServiceClient, vmID string) ([]*v2.VMCVERow, int32, error) { + var all []*v2.VMCVERow + var total int32 + for page := range v2ListMaxPages { + offset := int32(page) * v2ListPageSize + resp, err := client.ListVMCVEsByVM(ctx, &v2.ListVMCVEsByVMRequest{ + VmId: vmID, + Query: &v2.RawQuery{ + Pagination: &v2.Pagination{ + Limit: v2ListPageSize, + Offset: offset, + SortOption: &v2.SortOption{Field: search.CVE.String()}, + }, + }, + }) + if err != nil { + return nil, 0, err + } + total = resp.GetTotalCount() + all = append(all, resp.GetCves()...) + if int32(len(all)) >= total || len(resp.GetCves()) == 0 { + return all, total, nil + } + } + return nil, 0, fmt.Errorf("ListVMCVEsByVM: exceeded %d pages for vm %s (collected %d, total_count=%d)", v2ListMaxPages, vmID, len(all), total) +} + +// VulnCountBySeverityTotal sums the per-severity total fields. +func VulnCountBySeverityTotal(c *v2.VulnCountBySeverity) int32 { + if c == nil { + return 0 + } + var n int32 + for _, sev := range []*v2.VulnFixableCount{ + c.GetCritical(), + c.GetImportant(), + c.GetModerate(), + c.GetLow(), + c.GetUnknown(), + } { + n += sev.GetTotal() + } + return n +} + +// WaitForV2ScanMissingComponent polls until packageName is absent from a scan +// whose latest_scan.scan_time has advanced at least minScanAdvances times past after. +func WaitForV2ScanMissingComponent( + ctx context.Context, + client v2.VirtualMachineV2ServiceClient, + opts WaitOptions, + id, packageName string, + after time.Time, + minScanAdvances int, +) ([]*v2.VMComponentRow, error) { + if minScanAdvances < 1 { + minScanAdvances = 1 + } + advances := 0 + lastSeen := after + var components []*v2.VMComponentRow + err := pollUntil(ctx, opts, + fmt.Sprintf("V2 scan missing package %q after %d scan_time advance(s) past %s (id=%q)", + packageName, minScanAdvances, after.UTC().Format(time.RFC3339), id), + func(ctx context.Context) (bool, string, error) { + vm, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) + if err != nil { + return false, "", err + } + scan := vm.GetLatestScan() + if scan == nil { + return false, "latest_scan is nil", nil + } + st := scan.GetScanTime() + if st == nil { + return false, "latest_scan.scan_time is nil", nil + } + scanTime := st.AsTime() + if scanTime.After(lastSeen) { + advances++ + lastSeen = scanTime + } + if advances < minScanAdvances { + return false, fmt.Sprintf("scan_time advances=%d/%d last=%s", + advances, minScanAdvances, scanTime.UTC().Format(time.RFC3339)), nil + } + + filtered, err := client.ListVMComponents(ctx, &v2.ListVMComponentsRequest{ + VmId: id, + Query: &v2.RawQuery{ + Query: fmt.Sprintf("%s:%q", search.Component, packageName), + }, + }) + if err != nil { + return false, "", err + } + if filtered.GetTotalCount() > 0 { + return false, fmt.Sprintf("scan_time advances=%d but package %q still present (matches=%d)", + advances, packageName, filtered.GetTotalCount()), nil + } + + comps, total, err := ListAllVMComponents(ctx, client, id) + if err != nil { + return false, "", err + } + components = comps + return true, fmt.Sprintf("scan_time advances=%d package %q gone (components=%d)", + advances, packageName, total), nil + }) + if err != nil { + return nil, err + } + return components, nil +} diff --git a/tests/vmhelpers/central_v2_test.go b/tests/vmhelpers/central_v2_test.go new file mode 100644 index 0000000000000..396b43438644b --- /dev/null +++ b/tests/vmhelpers/central_v2_test.go @@ -0,0 +1,288 @@ +//go:build test && !test_e2e && !test_e2e_vm + +package vmhelpers + +import ( + "context" + "errors" + "testing" + "time" + + v2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type stubV2Client struct { + listVMsFn func(ctx context.Context, req *v2.ListVMsRequest) (*v2.ListVMsResponse, error) + getVMFn func(ctx context.Context, req *v2.GetVMRequest) (*v2.VMDetail, error) + listVMComponentsFn func(ctx context.Context, req *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) + listVMCVEsByVMFn func(ctx context.Context, req *v2.ListVMCVEsByVMRequest) (*v2.ListVMCVEsByVMResponse, error) + getVMCVEComponentsFn func(ctx context.Context, req *v2.GetVMCVEComponentsRequest) (*v2.GetVMCVEComponentsResponse, error) + getVMVulnSummaryFn func(ctx context.Context, req *v2.GetVMVulnSummaryRequest) (*v2.VMVulnSummary, error) +} + +func (s *stubV2Client) GetVM(ctx context.Context, in *v2.GetVMRequest, _ ...grpc.CallOption) (*v2.VMDetail, error) { + if s.getVMFn == nil { + return nil, errors.New("GetVM not stubbed") + } + return s.getVMFn(ctx, in) +} + +func (s *stubV2Client) GetVMVulnSummary(ctx context.Context, in *v2.GetVMVulnSummaryRequest, _ ...grpc.CallOption) (*v2.VMVulnSummary, error) { + if s.getVMVulnSummaryFn == nil { + return &v2.VMVulnSummary{}, nil + } + return s.getVMVulnSummaryFn(ctx, in) +} + +func (s *stubV2Client) ListVMCVEsByVM(ctx context.Context, in *v2.ListVMCVEsByVMRequest, _ ...grpc.CallOption) (*v2.ListVMCVEsByVMResponse, error) { + if s.listVMCVEsByVMFn == nil { + return &v2.ListVMCVEsByVMResponse{}, nil + } + return s.listVMCVEsByVMFn(ctx, in) +} + +func (s *stubV2Client) GetVMCVEComponents(ctx context.Context, in *v2.GetVMCVEComponentsRequest, _ ...grpc.CallOption) (*v2.GetVMCVEComponentsResponse, error) { + if s.getVMCVEComponentsFn == nil { + return &v2.GetVMCVEComponentsResponse{}, nil + } + return s.getVMCVEComponentsFn(ctx, in) +} + +func (s *stubV2Client) ListVMComponents(ctx context.Context, in *v2.ListVMComponentsRequest, _ ...grpc.CallOption) (*v2.ListVMComponentsResponse, error) { + if s.listVMComponentsFn == nil { + return &v2.ListVMComponentsResponse{}, nil + } + return s.listVMComponentsFn(ctx, in) +} + +func (s *stubV2Client) ListVMs(ctx context.Context, in *v2.ListVMsRequest, _ ...grpc.CallOption) (*v2.ListVMsResponse, error) { + if s.listVMsFn == nil { + return &v2.ListVMsResponse{}, nil + } + return s.listVMsFn(ctx, in) +} + +func (s *stubV2Client) ListVMCVEs(context.Context, *v2.ListVMCVEsRequest, ...grpc.CallOption) (*v2.ListVMCVEsResponse, error) { + return &v2.ListVMCVEsResponse{}, nil +} + +func (s *stubV2Client) GetVMDashboardCounts(context.Context, *v2.VMDashboardCountsRequest, ...grpc.CallOption) (*v2.VMDashboardCountsResponse, error) { + return &v2.VMDashboardCountsResponse{}, nil +} + +func (s *stubV2Client) GetVMCVEDetail(context.Context, *v2.GetVMCVEDetailRequest, ...grpc.CallOption) (*v2.VMCVEDetail, error) { + return &v2.VMCVEDetail{}, nil +} + +func (s *stubV2Client) ListVMCVEAffectedVMs(context.Context, *v2.ListVMCVEAffectedVMsRequest, ...grpc.CallOption) (*v2.ListVMCVEAffectedVMsResponse, error) { + return &v2.ListVMCVEAffectedVMsResponse{}, nil +} + +func TestWaitForV2VMPresentInCentral_UsesListVMs(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 200 * time.Millisecond, PollInterval: 5 * time.Millisecond} + var sawQuery string + client := &stubV2Client{ + listVMsFn: func(_ context.Context, req *v2.ListVMsRequest) (*v2.ListVMsResponse, error) { + sawQuery = req.GetQuery().GetQuery() + return &v2.ListVMsResponse{ + Vms: []*v2.VMListItem{ + {Id: "id-1", Namespace: "ns1", Name: "vm1"}, + }, + }, nil + }, + } + vm, err := WaitForV2VMPresentInCentral(ctx, client, opts, "ns1", "vm1") + require.NoError(t, err) + require.Equal(t, "id-1", vm.GetId()) + require.Contains(t, sawQuery, `Namespace:"ns1"`) + require.Contains(t, sawQuery, `Virtual Machine Name:"vm1"`) +} + +func TestWaitForV2VMLatestScan(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 150 * time.Millisecond, PollInterval: 5 * time.Millisecond} + var calls int + client := &stubV2Client{ + getVMFn: func(_ context.Context, req *v2.GetVMRequest) (*v2.VMDetail, error) { + calls++ + if calls < 3 { + return &v2.VMDetail{Id: req.GetId()}, nil + } + return &v2.VMDetail{ + Id: req.GetId(), + LatestScan: &v2.VMScanInfo{ + ScanTime: timestamppb.Now(), + }, + }, nil + }, + } + vm, err := WaitForV2VMLatestScan(ctx, client, opts, "vid") + require.NoError(t, err) + require.NotNil(t, vm.GetLatestScan().GetScanTime()) + require.GreaterOrEqual(t, calls, 3) +} + +func TestWaitForV2VMRunningInCentral(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 150 * time.Millisecond, PollInterval: 5 * time.Millisecond} + var calls int + client := &stubV2Client{ + getVMFn: func(_ context.Context, _ *v2.GetVMRequest) (*v2.VMDetail, error) { + calls++ + if calls < 2 { + return &v2.VMDetail{Id: "r1", State: v2.VirtualMachineV2State_VM_STATE_STOPPED}, nil + } + return &v2.VMDetail{Id: "r1", State: v2.VirtualMachineV2State_VM_STATE_RUNNING}, nil + }, + } + vm, err := WaitForV2VMRunningInCentral(ctx, client, opts, "r1") + require.NoError(t, err) + require.Equal(t, v2.VirtualMachineV2State_VM_STATE_RUNNING, vm.GetState()) +} + +func TestListAllVMCVEsByVM_Pages(t *testing.T) { + ctx := t.Context() + client := &stubV2Client{ + listVMCVEsByVMFn: func(_ context.Context, req *v2.ListVMCVEsByVMRequest) (*v2.ListVMCVEsByVMResponse, error) { + switch req.GetQuery().GetPagination().GetOffset() { + case 0: + return &v2.ListVMCVEsByVMResponse{ + TotalCount: 2, + Cves: []*v2.VMCVERow{{Cve: "CVE-1"}, {Cve: "CVE-2"}}, + }, nil + default: + return &v2.ListVMCVEsByVMResponse{TotalCount: 2}, nil + } + }, + } + cves, total, err := ListAllVMCVEsByVM(ctx, client, "vid") + require.NoError(t, err) + require.Equal(t, int32(2), total) + require.Equal(t, []string{"CVE-1", "CVE-2"}, []string{cves[0].GetCve(), cves[1].GetCve()}) +} + +func TestListAllVMComponents_Pages(t *testing.T) { + ctx := t.Context() + page0 := make([]*v2.VMComponentRow, v2ListPageSize) + for i := range page0 { + page0[i] = &v2.VMComponentRow{Name: "pkg"} + } + client := &stubV2Client{ + listVMComponentsFn: func(_ context.Context, req *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { + switch req.GetQuery().GetPagination().GetOffset() { + case 0: + return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1), Components: page0}, nil + case v2ListPageSize: + return &v2.ListVMComponentsResponse{ + TotalCount: int32(v2ListPageSize + 1), + Components: []*v2.VMComponentRow{{Name: "last"}}, + }, nil + default: + return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1)}, nil + } + }, + } + comps, total, err := ListAllVMComponents(ctx, client, "vid") + require.NoError(t, err) + require.Equal(t, int32(v2ListPageSize+1), total) + require.Len(t, comps, v2ListPageSize+1) + require.Equal(t, "last", comps[len(comps)-1].GetName()) +} + +func TestVulnCountBySeverityTotal(t *testing.T) { + tests := map[string]struct { + in *v2.VulnCountBySeverity + want int32 + }{ + "nil": {in: nil, want: 0}, + "empty": {in: &v2.VulnCountBySeverity{}, want: 0}, + "sums": {in: &v2.VulnCountBySeverity{ + Critical: &v2.VulnFixableCount{Total: 3}, + Important: &v2.VulnFixableCount{Total: 2}, + Moderate: &v2.VulnFixableCount{Total: 1}, + Low: &v2.VulnFixableCount{Total: 4}, + Unknown: &v2.VulnFixableCount{Total: 0}, + }, want: 10}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.want, VulnCountBySeverityTotal(tc.in)) + }) + } +} + +func TestWaitForV2ScanMissingComponent(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 400 * time.Millisecond, PollInterval: 5 * time.Millisecond} + baseline := time.Now().Add(-time.Minute) + var calls int + client := &stubV2Client{ + getVMFn: func(_ context.Context, req *v2.GetVMRequest) (*v2.VMDetail, error) { + calls++ + scanTime := baseline.Add(time.Duration(calls) * time.Second) + return &v2.VMDetail{ + Id: req.GetId(), + LatestScan: &v2.VMScanInfo{ + ScanTime: timestamppb.New(scanTime), + }, + }, nil + }, + listVMComponentsFn: func(_ context.Context, req *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { + if req.GetQuery().GetQuery() != "" { + if calls < 3 { + return &v2.ListVMComponentsResponse{ + TotalCount: 1, + Components: []*v2.VMComponentRow{{Name: "bc"}}, + }, nil + } + return &v2.ListVMComponentsResponse{}, nil + } + return &v2.ListVMComponentsResponse{ + TotalCount: 2, + Components: []*v2.VMComponentRow{{Name: "bash"}, {Name: "coreutils"}}, + }, nil + }, + } + comps, err := WaitForV2ScanMissingComponent(ctx, client, opts, "vid", "bc", baseline, 2) + require.NoError(t, err) + require.Len(t, comps, 2) +} + +func TestWaitForV2ScanReady(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 200 * time.Millisecond, PollInterval: 5 * time.Millisecond} + var calls int + client := &stubV2Client{ + getVMFn: func(_ context.Context, req *v2.GetVMRequest) (*v2.VMDetail, error) { + return &v2.VMDetail{ + Id: req.GetId(), + GuestOs: "rhel", + LatestScan: &v2.VMScanInfo{ + ScanTime: timestamppb.Now(), + ScanOs: "rhel", + }, + }, nil + }, + listVMComponentsFn: func(context.Context, *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { + calls++ + if calls == 1 { + return &v2.ListVMComponentsResponse{ + TotalCount: 1, + Components: []*v2.VMComponentRow{{Name: "bash", ScanStatus: v2.ScanStatus_NOT_SCANNED}}, + }, nil + } + return &v2.ListVMComponentsResponse{ + TotalCount: 1, + Components: []*v2.VMComponentRow{{Name: "bash", ScanStatus: v2.ScanStatus_SCANNED}}, + }, nil + }, + } + vm, err := WaitForV2ScanReady(ctx, client, opts, "vid") + require.NoError(t, err) + require.Equal(t, "vid", vm.GetId()) + require.GreaterOrEqual(t, calls, 2) +} diff --git a/tests/vmhelpers/roxagent_quadlet.go b/tests/vmhelpers/roxagent_quadlet.go index 10e2123c869ae..cece26b627b31 100644 --- a/tests/vmhelpers/roxagent_quadlet.go +++ b/tests/vmhelpers/roxagent_quadlet.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strings" + "time" ) const ( @@ -129,11 +130,66 @@ func installGuestPodmanAuth(ctx context.Context, virt Virtctl, namespace, vm, po } func scpToGuest(ctx context.Context, virt Virtctl, namespace, vm, src, dst string) error { - stderr, err := virt.SCPTo(ctx, namespace, vm, src, dst) - if err != nil { - return fmt.Errorf("virtctl scp %s -> %s: %w: %s", src, dst, err, strings.TrimSpace(stderr)) + return retryCopyToGuest(ctx, virt.Logf, src, dst, defaultSSHTransportRetryAttempts, defaultSSHTransportRetryInterval, func() (string, error) { + return virt.SCPTo(ctx, namespace, vm, src, dst) + }) +} + +// wrapSCPError attaches errSSHTransport only for retryable SSH failures so +// retryOnSSHTransport does not spin on terminal auth errors. +func wrapSCPError(src, dst, stderr string, err error) error { + trimmed := strings.TrimSpace(stderr) + isSSH, retryable, category := classifySSHFailure(stderr, err) + if !isSSH { + return fmt.Errorf("virtctl scp %s -> %s: %w: %s", src, dst, err, trimmed) + } + wrapped := err + if isSSHBannerTimeoutFailure(stderr) { + wrapped = errors.Join(err, ErrSSHConnectivityStalled) + } + if isSSHAuthenticationFailure(stderr) { + wrapped = errors.Join(wrapped, ErrSSHAuthenticationFailed) + } + if retryable { + return fmt.Errorf("%w: virtctl scp %s -> %s: retryable SSH %s failure: %w: %s", + errSSHTransport, src, dst, category, wrapped, trimmed) + } + return fmt.Errorf("virtctl scp %s -> %s: terminal SSH %s failure: %w: %s", + src, dst, category, wrapped, trimmed) +} + +func retryCopyToGuest(ctx context.Context, logf func(string, ...any), src, dst string, attempts int, interval time.Duration, copyFn func() (string, error)) error { + if attempts <= 0 { + attempts = defaultSSHTransportRetryAttempts + } + if interval <= 0 { + interval = defaultSSHTransportRetryInterval + } + + var lastErr error + for attempt := 1; attempt <= attempts; attempt++ { + stderr, err := copyFn() + if err == nil { + return nil + } + lastErr = wrapSCPError(src, dst, stderr, err) + isSSH, retryable, category := classifySSHFailure(stderr, err) + if !isSSH || !retryable || attempt >= attempts { + return lastErr + } + if logf != nil { + logf("virtctl scp %s -> %s: retryable SSH %s (attempt %d/%d)", src, dst, category, attempt, attempts) + } + timer := time.NewTimer(interval) + select { + case <-ctx.Done(): + timer.Stop() + return fmt.Errorf("%w: virtctl scp %s -> %s: context done during retry backoff: %w", + errSSHTransport, src, dst, ctx.Err()) + case <-timer.C: + } } - return nil + return lastErr } func stageQuadletInstall(image, repo2cpeURL, podmanAuthPath string) (string, error) { diff --git a/tests/vmhelpers/roxagent_quadlet_test.go b/tests/vmhelpers/roxagent_quadlet_test.go index cee1931789e4d..206c9fa5e5062 100644 --- a/tests/vmhelpers/roxagent_quadlet_test.go +++ b/tests/vmhelpers/roxagent_quadlet_test.go @@ -3,10 +3,12 @@ package vmhelpers import ( + "errors" "os" "path/filepath" "strings" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -134,3 +136,99 @@ func TestIsVsockUnavailableOutput(t *testing.T) { }) } } + +const scpBannerTimeoutStderr = "Connection timed out during banner exchange\nConnection to UNKNOWN port 65535 timed out\nscp: Connection closed\nexit status 255" + +func TestWrapSCPError(t *testing.T) { + t.Parallel() + copyErr := errors.New("exit status 1") + tests := map[string]struct { + stderr string + wantTransport bool + wantStalled bool + wantAuth bool + }{ + "banner timeout is SSH transport": { + stderr: scpBannerTimeoutStderr, + wantTransport: true, + wantStalled: true, + }, + "auth failure is terminal not transport": { + stderr: "Permission denied (publickey).\r\n", + wantAuth: true, + }, + "remote command failure is not SSH transport": { + stderr: "install.sh: no such file\nexit status 1", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + err := wrapSCPError("/tmp/install.sh", "/guest/install.sh", tc.stderr, copyErr) + require.Error(t, err) + require.Equal(t, tc.wantTransport, errors.Is(err, errSSHTransport)) + require.Equal(t, tc.wantStalled, errors.Is(err, ErrSSHConnectivityStalled)) + require.Equal(t, tc.wantAuth, errors.Is(err, ErrSSHAuthenticationFailed)) + }) + } +} + +func TestRetryCopyToGuest(t *testing.T) { + t.Parallel() + copyErr := errors.New("exit status 1") + tests := map[string]struct { + stderr string + succeedOnAttempt int + wantAttempts int + wantErr bool + wantTransport bool + wantStalled bool + wantAuth bool + }{ + "retries banner timeout then succeeds": { + stderr: scpBannerTimeoutStderr, + succeedOnAttempt: 3, + wantAttempts: 3, + }, + "gives up on persistent banner timeout": { + stderr: scpBannerTimeoutStderr, + wantAttempts: 3, + wantErr: true, + wantTransport: true, + wantStalled: true, + }, + "does not retry terminal SSH auth": { + stderr: "Permission denied (publickey).\r\n", + wantAttempts: 1, + wantErr: true, + wantAuth: true, + }, + "does not retry remote command failure": { + stderr: "install.sh: permission denied", + wantAttempts: 1, + wantErr: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + attempts := 0 + err := retryCopyToGuest(t.Context(), nil, "/tmp/install.sh", "/guest/install.sh", 3, time.Millisecond, func() (string, error) { + attempts++ + if tc.succeedOnAttempt > 0 && attempts >= tc.succeedOnAttempt { + return "", nil + } + return tc.stderr, copyErr + }) + require.Equal(t, tc.wantAttempts, attempts) + if !tc.wantErr { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Equal(t, tc.wantTransport, errors.Is(err, errSSHTransport)) + require.Equal(t, tc.wantStalled, errors.Is(err, ErrSSHConnectivityStalled)) + require.Equal(t, tc.wantAuth, errors.Is(err, ErrSSHAuthenticationFailed)) + }) + } +} diff --git a/tests/vmhelpers/wait_test.go b/tests/vmhelpers/wait_test.go index f56dc9ff86a74..2870bda9e0b68 100644 --- a/tests/vmhelpers/wait_test.go +++ b/tests/vmhelpers/wait_test.go @@ -66,6 +66,7 @@ func TestIsAuthenticationExpired(t *testing.T) { "wrapped ErrAuthenticationExpired": {err: fmt.Errorf("op: %w", ErrAuthenticationExpired), want: true}, "unrelated error": {err: errors.New("connection refused"), want: false}, "gRPC unavailable (not auth)": {err: status.Error(codes.Unavailable, "service down"), want: false}, + "gRPC Unimplemented": {err: status.Error(codes.Unimplemented, "not registered"), want: false}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { From 579d1b6efbe8b7312bb9d0e8bd0d89067dd837a2 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:49:05 +0200 Subject: [PATCH 199/638] ROX-35685: push Sensor's repo-to-CPE mapping to roxagent (#22144) Co-authored-by: Cursor --- .../roxagent/cmd/serve_test.go | 13 + .../roxagent/vsockserver/sensor_updater.go | 4 +- .../vsockserver/sensor_updater_test.go | 4 +- pkg/env/virtualmachine.go | 4 +- .../common/virtualmachine/metrics/metrics.go | 36 +- .../virtualmachine/vmscraper/scraper.go | 143 ++++++- .../virtualmachine/vmscraper/scraper_test.go | 385 +++++++++++++++++- .../virtualmachine/vsockclient/client.go | 57 ++- .../vsockclient/client_integration_test.go | 92 ++++- .../virtualmachine/vsockclient/client_test.go | 86 ++++ sensor/kubernetes/sensor/sensor.go | 17 +- tools/allowed-large-files | 1 + 12 files changed, 802 insertions(+), 40 deletions(-) diff --git a/compliance/virtualmachines/roxagent/cmd/serve_test.go b/compliance/virtualmachines/roxagent/cmd/serve_test.go index 12f8e4c720de2..5585594363f96 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve_test.go +++ b/compliance/virtualmachines/roxagent/cmd/serve_test.go @@ -48,6 +48,16 @@ func withMappingCachePath(t *testing.T) string { return path } +// registerSensorPersistDrain waits for SensorUpdater's fire-and-forget cache +// writes before t.TempDir() cleanup, so AtomicWriteFile's sibling .tmp files +// cannot race directory removal. +func registerSensorPersistDrain(t *testing.T, updater vsockserver.MappingUpdater) { + t.Helper() + u, ok := updater.(*vsockserver.SensorUpdater) + require.True(t, ok, "registerSensorPersistDrain requires a *SensorUpdater") + t.Cleanup(u.WaitPersist) +} + func TestNewRescannerAndProvider_URLSelection(t *testing.T) { withMappingCachePath(t) @@ -130,6 +140,7 @@ func TestNewRescannerAndProvider_SyncKicksFirstScan(t *testing.T) { vmRescanner, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{rescanInterval: time.Hour}) require.False(t, provider.Ready()) require.NotNil(t, updater, "Sensor path must produce a non-nil updater") + registerSensorPersistDrain(t, updater) var mu sync.Mutex var calls int @@ -312,6 +323,7 @@ func TestNewRescannerAndProvider_SensorSync_DefersUnderScanThenApplies(t *testin _, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{}) require.True(t, provider.Ready(), "a pre-seeded Sensor cache must bootstrap the provider Ready") require.Equal(t, cpemapping.HashMapping([]byte(validMappingJSON)), provider.Hash()) + registerSensorPersistDrain(t, updater) gate, ok := updater.(vsockserver.ScanBusyGate) require.True(t, ok, "the Sensor updater must implement ScanBusyGate") @@ -344,6 +356,7 @@ func TestNewRescannerAndProvider_SensorSync_DefersUnderScanThenApplies(t *testin func TestNewRescannerAndProvider_SensorUpdate_InvalidContentKeepsLastGood(t *testing.T) { cachePath := withMappingCachePath(t) _, provider, updater, _ := newRescannerAndProvider(&vsockserver.ReportCache{}, serveConfig{}) + registerSensorPersistDrain(t, updater) updated, err := updater.Update([]byte(validMappingJSON)) require.NoError(t, err) diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go index ead8678b1eb03..d3fc73c20b2f6 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater.go @@ -203,10 +203,10 @@ func (u *SensorUpdater) persistAndNotify() { } } -// waitPersist blocks until every persistAndNotify goroutine has finished +// WaitPersist blocks until every persistAndNotify goroutine has finished // writing cachePath. Tests register it with t.Cleanup so TempDir removal // cannot race AtomicWriteFile's sibling .tmp files. -func (u *SensorUpdater) waitPersist() { +func (u *SensorUpdater) WaitPersist() { u.persistWG.Wait() } diff --git a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go index 294e2b1898ef1..c58a2e803f8c4 100644 --- a/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go +++ b/compliance/virtualmachines/roxagent/vsockserver/sensor_updater_test.go @@ -31,7 +31,7 @@ func writeFile(t *testing.T, path, content string) { func newSensorUpdater(t *testing.T, cachePath, bundledPath string, onChange func()) *SensorUpdater { t.Helper() u := NewSensorUpdater(cachePath, bundledPath, onChange) - t.Cleanup(u.waitPersist) + t.Cleanup(u.WaitPersist) return u } @@ -39,7 +39,7 @@ func newSensorUpdater(t *testing.T, cachePath, bundledPath string, onChange func // checks cachePath, so the assertion cannot race AtomicWriteFile. func waitForCacheContent(t *testing.T, u *SensorUpdater, cachePath, want string) { t.Helper() - u.waitPersist() + u.WaitPersist() b, err := os.ReadFile(cachePath) require.NoError(t, err) assert.Equal(t, want, string(b), "expected %q to be persisted to %s", want, cachePath) diff --git a/pkg/env/virtualmachine.go b/pkg/env/virtualmachine.go index ef4e34dc71b26..d36db89a105cd 100644 --- a/pkg/env/virtualmachine.go +++ b/pkg/env/virtualmachine.go @@ -70,8 +70,8 @@ var ( // min(poll interval, 30m). VirtualMachinesScraperInitialBackoff = registerDurationSetting("ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF", 10*time.Second) - // VirtualMachinesScraperPerVMTimeout defines the per-VM deadline for dialing - // and pulling a report in a single scrape cycle. + // VirtualMachinesScraperPerVMTimeout is the deadline for one Sensor-to-agent + // call (GetReport or a mapping sync). Doing both can take twice as long. VirtualMachinesScraperPerVMTimeout = registerDurationSetting("ROX_VIRTUAL_MACHINES_SCRAPER_PER_VM_TIMEOUT", 30*time.Second) // VirtualMachinesScraperMandatoryRefreshInterval defines the maximum age of a VM's last forwarded diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index 15e1fdf723a2c..9adaf0f8d974e 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -105,10 +105,10 @@ var IndexReportAcksReceived = prometheus.NewCounterVec( []string{"action"}, // "ACK" or "NACK" ) -// Pull-mode outcome labels are split across three counters so transport, -// GetReport protocol, and scrape-pipeline results stay distinct as more -// VSOCK RPC methods are added later. Each per-VM pull attempt increments -// exactly one of these counters (mutually exclusive partition). +// Pull-mode GetReport outcomes are split across three counters so transport, +// GetReport protocol, and scrape-pipeline results stay distinct. Each +// GetReport attempt increments exactly one of those. PullSyncTotal counts +// the optional second VSOCK RPC that pushes a repo-to-CPE mapping. // Transport-layer status values for PullTransportTotal. const ( @@ -139,6 +139,20 @@ const ( PullScrapeSendError = "send_error" ) +// Sync-RPC status values for PullSyncTotal. +const ( + // PullSyncSuccess counts an accepted push; Updated true/false both count. + PullSyncSuccess = "success" + // PullSyncError is a sync dial failure or a rejection other than not-Sensor-managed. + PullSyncError = "error" + // PullSyncNotManaged is a rejection Sensor's own gate should have prevented. + PullSyncNotManaged = "not_managed" + // PullSyncURLHashMismatch is a URL-managed agent whose hash differs from Sensor's cache; Sensor never dials. + PullSyncURLHashMismatch = "url_hash_mismatch" + // PullSyncTimeout is a skipped sync because GetReport already consumed the per-VM deadline. + PullSyncTimeout = "timeout" +) + // PullDialDurationSeconds measures time to establish a websocket connection per VM. var PullDialDurationSeconds = prometheus.NewHistogram( prometheus.HistogramOpts{ @@ -249,6 +263,19 @@ var PullScrapeTotal = prometheus.NewCounterVec( []string{"status"}, ) +// PullSyncTotal counts the optional second VSOCK RPC that pushes a +// repo-to-CPE mapping after GetReport. It is not part of the GetReport +// transport/protocol/scrape partition. +var PullSyncTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_sync_total", + Help: "Per-VM repo-to-CPE mapping sync outcomes (second VSOCK RPC after GetReport)", + }, + []string{"status"}, +) + // PullTicksTotal counts scraper ticks executed. var PullTicksTotal = prometheus.NewCounter( prometheus.CounterOpts{ @@ -341,6 +368,7 @@ func init() { PullTransportTotal, PullGetReportTotal, PullScrapeTotal, + PullSyncTotal, PullTicksTotal, PullTrackedVMs, PullDueVMs, diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 8b2de094f5cf9..6af54dfb81f59 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -11,6 +11,7 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/env" @@ -74,6 +75,13 @@ type IndexReportSender interface { // ProtocolClient performs the request/response protocol over a stream. type ProtocolClient interface { GetReport(ctx context.Context, stream io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) + SyncRepoCPEMapping(ctx context.Context, stream io.ReadWriteCloser, mapping []byte) (updated bool, meta *pb.ResponseMeta, err error) +} + +// Repo2CPEFetcher supplies Sensor's cached repo-to-CPE mapping so maybeSyncRepoCPEMapping +// can tell whether a VM's agent needs a pushed update. +type Repo2CPEFetcher interface { + FetchRepo2CPE(ctx context.Context) (mapping []byte, hash string, ok bool) } // closeCoder is satisfied by transport errors carrying a structured close @@ -90,6 +98,7 @@ type VMScraper struct { sender IndexReportSender dialer VMDialer client ProtocolClient + repo2CPEFetcher Repo2CPEFetcher interval time.Duration tickInterval time.Duration initialBackoff time.Duration @@ -127,14 +136,16 @@ type vmState struct { var _ common.SensorComponent = (*VMScraper)(nil) -// New creates a VMScraper with production defaults. -func New(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) *VMScraper { +// New creates a VMScraper with production defaults. A nil repo2CPEFetcher leaves +// maybeSyncRepoCPEMapping a no-op, so pull still works if the repo-to-CPE cache was not built. +func New(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient, repo2CPEFetcher Repo2CPEFetcher) *VMScraper { interval := clampPollInterval(env.VirtualMachinesScraperPollInterval.DurationSetting()) return &VMScraper{ store: store, sender: sender, dialer: dialer, client: client, + repo2CPEFetcher: repo2CPEFetcher, interval: interval, tickInterval: env.VirtualMachinesScraperTickInterval.DurationSetting(), initialBackoff: env.VirtualMachinesScraperInitialBackoff.DurationSetting(), @@ -440,9 +451,6 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } log.Infof("VMScraper: scraping %q (reason=%s backoff=%s)", key, reason, snap.backoff) - vmCtx, cancel := context.WithTimeout(ctx, s.perVMTimeout) - defer cancel() - totalStart := s.now() port := getVsockPort() @@ -456,7 +464,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } log.Debugf("VMScraper: dialing roxagent on %q with TLS", key) - result, outcome := s.dialAndGetReport(vmCtx, vm, key, port, lastKnownToken) + result, outcome := s.dialAndGetReport(ctx, vm, key, port, lastKnownToken) if outcome != scrapeOK { next := s.scheduleAfterAttempt(key, vm.ID, outcome) kind := "retryable" @@ -491,7 +499,8 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool metrics.PullReportPackages.Observe(float64(len(result.IndexReport.GetContents().GetPackages()))) logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) - if err := s.sender.Send(vmCtx, vm, result.IndexReport); err != nil { + // Mapping sync has its own deadline, so Send uses the scrape parent. + if err := s.sender.Send(ctx, vm, result.IndexReport); err != nil { metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeSendError).Inc() outcome := scrapeRetryable if errors.Is(err, errox.NotImplemented) { @@ -576,17 +585,20 @@ func (s *VMScraper) scheduleAfterAttempt(key string, vmID virtualmachine.VMID, o }) } -// dialAndGetReport dials the VM and issues a single GetReport request, -// recording dial/read latency metrics and classifying failures. +// dialAndGetReport uses a separate perVMTimeout for GetReport and for the +// mapping push so a slow report cannot abort the push. func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, lastKnownToken string) (*vsockclient.GetReportResult, scrapeOutcome) { + reportCtx, cancel := context.WithTimeout(ctx, s.perVMTimeout) + defer cancel() + dialStart := s.now() - stream, err := s.dialer.Dial(ctx, vm.Namespace, vm.Name, port, true) + stream, err := s.dialer.Dial(reportCtx, vm.Namespace, vm.Name, port, true) metrics.PullDialDurationSeconds.Observe(s.now().Sub(dialStart).Seconds()) if err != nil { - if ctx.Err() != nil { + if reportCtx.Err() != nil { log.Warnf("VMScraper: dialing roxagent on %q timed out: %v", key, err) metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout).Inc() - if errors.Is(ctx.Err(), context.Canceled) { + if errors.Is(reportCtx.Err(), context.Canceled) { // Parent cancel (Sensor stop): do not keep retrying on the short tick. return nil, scrapeNonRetryable } @@ -599,14 +611,40 @@ func (s *VMScraper) dialAndGetReport(ctx context.Context, vm *virtualmachine.Inf defer func() { _ = stream.Close() }() readStart := s.now() - result, err := s.client.GetReport(ctx, stream, lastKnownToken) + result, err := s.client.GetReport(reportCtx, stream, lastKnownToken) metrics.PullReadDurationSeconds.Observe(s.now().Sub(readStart).Seconds()) if err != nil { - return nil, s.handleGetReportError(ctx, key, err) + // Classify before cancel(): cancel() looks like Sensor stop to + // handleGetReportError. + outcome := s.handleGetReportError(reportCtx, key, err) + // MAPPING_REQUIRED is the only error whose Meta still needs + // inspecting: a VM with no mapping at all has nothing to report + // except this error, and it's the only way Sensor learns to push one. + if errors.Is(err, vsockclient.ErrMappingRequired) { + // maybeSyncRepoCPEMapping dials a second connection to the same agent, which + // enforces at most one connection at a time: this one must be + // closed first, not left to the deferred close on return. + _ = stream.Close() + cancel() + s.maybeSyncRepoCPEMapping(ctx, vm, key, port, resultMeta(result)) + } + return nil, outcome } + _ = stream.Close() + cancel() + s.maybeSyncRepoCPEMapping(ctx, vm, key, port, result.Meta) return result, scrapeOK } +// resultMeta returns result.Meta, tolerating a nil result: ProtocolClient +// implementations are not required to populate a result on every error. +func resultMeta(result *vsockclient.GetReportResult) *pb.ResponseMeta { + if result == nil { + return nil + } + return result.Meta +} + func (s *VMScraper) handleGetReportError(ctx context.Context, key string, err error) scrapeOutcome { // Timed-out or cancelled reads surface here after Dial's deadline // and/or GetReport's close-on-cancel. Prefer ctx.Err() so those land @@ -687,6 +725,83 @@ func isAbnormalClose(err error, target *closeCoder) bool { return code != 0 && code != closeCodeNormalClosure } +// maybeSyncRepoCPEMapping pushes a fresh mapping when the agent is +// Sensor-managed and its reported hash is stale. A URL-managed agent with +// a stale hash is only logged and counted, since Sensor doesn't own it. +func (s *VMScraper) maybeSyncRepoCPEMapping(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, meta *pb.ResponseMeta) { + updatePath := meta.GetRepoCpeMappingUpdatePath() + if updatePath == pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED { + return + } + if s.repo2CPEFetcher == nil { + return + } + mapping, sensorHash, ok := s.repo2CPEFetcher.FetchRepo2CPE(ctx) + if !ok { + return + } + if sensorHash == meta.GetRepoCpeMappingHash() { + log.Debugf("VMScraper: repo-to-CPE mapping on %q is up to date (hash=%s)", key, sensorHash) + return + } + + switch updatePath { + case pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR: + s.syncRepoCPEMapping(ctx, vm, key, port, mapping) + case pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL: + log.Warnf("VMScraper: roxagent on %q reports a URL-managed repo-to-CPE mapping (hash=%s) that differs from Sensor's own cache (hash=%s)", + key, meta.GetRepoCpeMappingHash(), sensorHash) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncURLHashMismatch).Inc() + } +} + +// syncRepoCPEMapping pushes mapping on a new connection (one request per +// connection). The deadline is independent of GetReport's. +func (s *VMScraper) syncRepoCPEMapping(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, mapping []byte) { + syncCtx, cancel := context.WithTimeout(ctx, s.perVMTimeout) + defer cancel() + if syncCtx.Err() != nil { + log.Debugf("VMScraper: skipping repo-to-CPE mapping sync for %q: %v", key, syncCtx.Err()) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout).Inc() + return + } + stream, err := s.dialer.Dial(syncCtx, vm.Namespace, vm.Name, port, true) + if err != nil { + if syncCtx.Err() != nil { + log.Warnf("VMScraper: dialing roxagent on %q for repo-to-CPE mapping sync timed out: %v", key, err) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout).Inc() + return + } + log.Warnf("VMScraper: dialing roxagent on %q for repo-to-CPE mapping sync failed: %v", key, err) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError).Inc() + return + } + defer func() { _ = stream.Close() }() + + updated, _, err := s.client.SyncRepoCPEMapping(syncCtx, stream, mapping) + if err != nil { + // NOT_SENSOR_MANAGED should never happen here: maybeSyncRepoCPEMapping only takes + // the SENSOR path for agents it believes accept a push. Counting it + // (without retrying) turns a gating bug into a visible signal. + if errors.Is(err, vsockclient.ErrMappingNotSensorManaged) { + log.Warnf("VMScraper: roxagent on %q rejected repo-to-CPE mapping sync as not Sensor-managed", key) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncNotManaged).Inc() + return + } + if syncCtx.Err() != nil { + log.Warnf("VMScraper: repo-to-CPE mapping sync to %q timed out: %v", key, err) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout).Inc() + return + } + log.Warnf("VMScraper: repo-to-CPE mapping sync to %q failed: %v", key, err) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError).Inc() + return + } + + log.Infof("VMScraper: synced repo-to-CPE mapping to %q (updated=%t)", key, updated) + metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncSuccess).Inc() +} + type vmStateSnapshot struct { lastToken string lastForwardedAt time.Time diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index a69c203d6ed0b..05f34d222d72b 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -102,12 +102,50 @@ type fakeCloseCoder struct { func (e *fakeCloseCoder) Error() string { return "connection closed" } func (e *fakeCloseCoder) CloseCode() (int, string) { return e.code, e.reason } +// singleConnDialer rejects a Dial while a previously dialed stream is still +// open, mirroring roxagent's real maxConcurrentConns=1 semaphore, so a +// caller that dials again before closing its current connection sees the +// same failure it would against the real agent. +type singleConnDialer struct { + mu sync.Mutex + open bool +} + +func (d *singleConnDialer) Dial(_ context.Context, _, _ string, _ uint32, _ bool) (io.ReadWriteCloser, error) { + return concurrency.WithLock2(&d.mu, func() (io.ReadWriteCloser, error) { + if d.open { + return nil, errors.New("agent is already serving another request; retry after a backoff") + } + d.open = true + return &trackedStream{dialer: d}, nil + }) +} + +type trackedStream struct { + dialer *singleConnDialer +} + +func (t *trackedStream) Read([]byte) (int, error) { return 0, io.EOF } +func (t *trackedStream) Write([]byte) (int, error) { return 0, nil } +func (t *trackedStream) Close() error { + concurrency.WithLock(&t.dialer.mu, func() { t.dialer.open = false }) + return nil +} + type mockProtocolClient struct { mu sync.Mutex resultQueue []*vsockclient.GetReportResult errQueue []error calls []protocolCall callIdx int + + getReportDelay time.Duration + syncDelay time.Duration + syncBlocksOnCtx bool + syncCalls [][]byte + syncUpdated bool + syncMeta *pb.ResponseMeta + syncErr error } type protocolCall struct { @@ -115,13 +153,22 @@ type protocolCall struct { } func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) { + if m.getReportDelay > 0 { + time.Sleep(m.getReportDelay) + } m.mu.Lock() defer m.mu.Unlock() m.calls = append(m.calls, protocolCall{lastKnownToken: lastKnownToken}) idx := m.callIdx m.callIdx++ if idx < len(m.errQueue) && m.errQueue[idx] != nil { - return nil, m.errQueue[idx] + // Some errors (e.g. MAPPING_REQUIRED) still carry a queued result + // with Meta, mirroring the real client's error-case behavior. + var result *vsockclient.GetReportResult + if idx < len(m.resultQueue) { + result = m.resultQueue[idx] + } + return result, m.errQueue[idx] } if idx < len(m.resultQueue) { return m.resultQueue[idx], nil @@ -129,11 +176,38 @@ func (m *mockProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, return nil, errors.New("unexpected call: no more queued results") } +func (m *mockProtocolClient) SyncRepoCPEMapping(ctx context.Context, _ io.ReadWriteCloser, mapping []byte) (bool, *pb.ResponseMeta, error) { + if m.syncBlocksOnCtx { + m.syncCalls = append(m.syncCalls, mapping) + <-ctx.Done() + return false, nil, ctx.Err() + } + if m.syncDelay > 0 { + time.Sleep(m.syncDelay) + } + m.syncCalls = append(m.syncCalls, mapping) + return m.syncUpdated, m.syncMeta, m.syncErr +} + func (m *mockProtocolClient) reset() { m.mu.Lock() defer m.mu.Unlock() m.calls = nil m.callIdx = 0 + m.syncCalls = nil +} + +// fakeFetcher is a Repo2CPEFetcher test double. +type fakeFetcher struct { + mapping []byte + hash string + ok bool + calls int +} + +func (f *fakeFetcher) FetchRepo2CPE(_ context.Context) ([]byte, string, bool) { + f.calls++ + return f.mapping, f.hash, f.ok } type mockSender struct { @@ -142,9 +216,12 @@ type mockSender struct { err error } -func (m *mockSender) Send(_ context.Context, _ *virtualmachine.Info, report *v4.IndexReport) error { +func (m *mockSender) Send(ctx context.Context, _ *virtualmachine.Info, report *v4.IndexReport) error { m.mu.Lock() defer m.mu.Unlock() + if err := ctx.Err(); err != nil { + return err + } if m.err != nil { return m.err } @@ -209,6 +286,13 @@ func unchangedResult() *vsockclient.GetReportResult { } } +func metaWithMapping(hash string, path pb.RepoCPEMappingUpdatePath) *pb.ResponseMeta { + return &pb.ResponseMeta{ + RepoCpeMappingHash: new(hash), + RepoCpeMappingUpdatePath: path.Enum(), + } +} + // --- Tests --- func TestVMScraper_PollsRunningVMs(t *testing.T) { @@ -1137,6 +1221,10 @@ func (c *safeProtocolClient) GetReport(_ context.Context, _ io.ReadWriteCloser, return makeReport(c.token), nil } +func (c *safeProtocolClient) SyncRepoCPEMapping(_ context.Context, _ io.ReadWriteCloser, _ []byte) (bool, *pb.ResponseMeta, error) { + return false, nil, nil +} + type safeSender struct { mu sync.Mutex sent int @@ -1317,6 +1405,87 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { } } +func TestVMScraper_MaybeSyncRepoCPEMapping(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + + cases := map[string]struct { + meta *pb.ResponseMeta + fetcher *fakeFetcher + noFetcher bool + syncErr error + wantSyncCalls int + wantMetric string + }{ + "hash match on the SENSOR path should not dial a second time": { + meta: metaWithMapping("same-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + fetcher: &fakeFetcher{ok: true, hash: "same-hash"}, + }, + "SENSOR mismatch should dial and sync": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + fetcher: &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")}, + wantSyncCalls: 1, + wantMetric: metrics.PullSyncSuccess, + }, + "URL mismatch should log and count but never dial": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL), + fetcher: &fakeFetcher{ok: true, hash: "new-hash"}, + wantMetric: metrics.PullSyncURLHashMismatch, + }, + "UNSPECIFIED update path should never sync": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED), + fetcher: &fakeFetcher{ok: true, hash: "new-hash"}, + }, + "nil meta (agent predates this feature) should never sync": { + meta: nil, + fetcher: &fakeFetcher{ok: true, hash: "new-hash"}, + }, + "fetcher not ok should never sync even on a SENSOR mismatch": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + fetcher: &fakeFetcher{ok: false}, + }, + "no fetcher ever set should be a no-op": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + noFetcher: true, + }, + "NOT_SENSOR_MANAGED on the sync dial should log and count, not retry": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + fetcher: &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")}, + syncErr: vsockclient.ErrMappingNotSensorManaged, + wantSyncCalls: 1, + wantMetric: metrics.PullSyncNotManaged, + }, + "a generic sync failure should log and count": { + meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + fetcher: &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")}, + syncErr: errors.New("connection reset"), + wantSyncCalls: 1, + wantMetric: metrics.PullSyncError, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + client := &mockProtocolClient{syncErr: tc.syncErr} + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + if !tc.noFetcher { + s.repo2CPEFetcher = tc.fetcher + } + + var before float64 + if tc.wantMetric != "" { + before = testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(tc.wantMetric)) + } + + s.maybeSyncRepoCPEMapping(context.Background(), vm, "ns1/vm-a", 9999, tc.meta) + + assert.Len(t, client.syncCalls, tc.wantSyncCalls) + if tc.wantMetric != "" { + assert.Equal(t, before+1, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(tc.wantMetric))) + } + }) + } +} + // TestVMScraper_NonForcedTickSkipsReconcileWhenNotDue verifies that the // production ticker's non-forced tick only reconciles (calling ListRunning) // once lastReconcile is at least reconcileEvery old, keeping ListRunning off @@ -1358,6 +1527,218 @@ func cachedNextAttemptAt(t *testing.T, s *VMScraper, key string) time.Time { }) } +// TestVMScraper_DialAndGetReport_SyncTriggering exercises maybeSyncRepoCPEMapping's +// integration point inside dialAndGetReport: only a successful exchange or +// MAPPING_REQUIRED carries usable Meta, so only those should ever reach the +// fetcher/second dial. +func TestVMScraper_DialAndGetReport_SyncTriggering(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + staleFetcher := func() *fakeFetcher { + return &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} + } + + cases := map[string]struct { + resultQueue []*vsockclient.GetReportResult + errQueue []error + wantOutcome scrapeOutcome + wantSyncCalls int + }{ + "MAPPING_REQUIRED error triggers a sync attempt": { + resultQueue: []*vsockclient.GetReportResult{{Meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)}}, + errQueue: []error{vsockclient.ErrMappingRequired}, + wantOutcome: scrapeRetryable, + wantSyncCalls: 1, + }, + "NOT_READY never triggers a sync attempt": { + errQueue: []error{vsockclient.ErrNotReady}, + wantOutcome: scrapeRetryable, + }, + "a generic protocol error never triggers a sync attempt": { + errQueue: []error{errors.New("connection refused")}, + wantOutcome: scrapeRetryable, + }, + "a successful report with a stale Sensor-managed hash also triggers a sync": { + resultQueue: []*vsockclient.GetReportResult{{ + IndexReport: &v4.IndexReport{State: "IndexFinished"}, + Meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + }}, + wantOutcome: scrapeOK, + wantSyncCalls: 1, + }, + "an Unchanged report with a matching hash never triggers a sync": { + resultQueue: []*vsockclient.GetReportResult{{ + Unchanged: true, + Meta: metaWithMapping("new-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + }}, + wantOutcome: scrapeOK, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + client := &mockProtocolClient{resultQueue: tc.resultQueue, errQueue: tc.errQueue} + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + s.repo2CPEFetcher = staleFetcher() + + _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") + + assert.Equal(t, tc.wantOutcome, outcome) + assert.Len(t, client.syncCalls, tc.wantSyncCalls) + }) + } +} + +// TestVMScraper_SyncRepoCPEMapping_CanceledParent_ClassifiedAsTimeout covers +// Sensor stop counting as PullSyncTimeout, not PullSyncError. +func TestVMScraper_SyncRepoCPEMapping_CanceledParent_ClassifiedAsTimeout(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + dialer := &mockDialer{err: errors.New("dial must not be attempted")} + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, &mockProtocolClient{}) + + timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) + syncErrBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + s.syncRepoCPEMapping(ctx, vm, "ns1/vm-a", 1, []byte("payload")) + + assert.Zero(t, dialer.callIdx.Load(), "a canceled parent must not be dialed at all") + assert.Equal(t, timeoutBefore+1, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout))) + assert.Equal(t, syncErrBefore, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)), + "a canceled parent must not be counted as a sync error") +} + +// TestVMScraper_SyncRepoCPEMapping_DialTimeout_ClassifiedAsTimeout covers +// perVMTimeout expiring during Dial counting as PullSyncTimeout, not +// PullSyncError (same rule as dialAndGetReport). +func TestVMScraper_SyncRepoCPEMapping_DialTimeout_ClassifiedAsTimeout(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, blockingDialer{}, &mockProtocolClient{}) + s.perVMTimeout = 20 * time.Millisecond + + timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) + syncErrBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)) + + s.syncRepoCPEMapping(context.Background(), vm, "ns1/vm-a", 1, []byte("payload")) + + assert.Equal(t, timeoutBefore+1, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout))) + assert.Equal(t, syncErrBefore, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)), + "a dial timeout must not be counted as a sync error") +} + +// TestVMScraper_SyncRepoCPEMapping_SyncTimeout_ClassifiedAsTimeout covers +// perVMTimeout expiring during SyncRepoCPEMapping counting as +// PullSyncTimeout, not PullSyncError (same rule as handleGetReportError). +func TestVMScraper_SyncRepoCPEMapping_SyncTimeout_ClassifiedAsTimeout(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + client := &mockProtocolClient{syncBlocksOnCtx: true} + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + s.perVMTimeout = 20 * time.Millisecond + + timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) + syncErrBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)) + + s.syncRepoCPEMapping(context.Background(), vm, "ns1/vm-a", 1, []byte("payload")) + + require.Len(t, client.syncCalls, 1) + assert.Equal(t, timeoutBefore+1, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout))) + assert.Equal(t, syncErrBefore, testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)), + "a sync timeout must not be counted as a sync error") +} + +// TestVMScraper_SendSucceedsAfterSyncOverrunsGetReportDeadline covers Send +// succeeding after GetReport and mapping sync each consume a full timeout. +func TestVMScraper_SendSucceedsAfterSyncOverrunsGetReportDeadline(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + rep := makeReport("1") + rep.Meta.RepoCpeMappingHash = new("old-hash") + rep.Meta.RepoCpeMappingUpdatePath = pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR.Enum() + sender := &mockSender{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{rep}, + getReportDelay: 15 * time.Millisecond, + syncDelay: 15 * time.Millisecond, + } + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, sender, &mockDialer{}, client) + s.perVMTimeout = 20 * time.Millisecond + s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} + + s.pollOnce(t.Context()) + + require.Len(t, sender.sent, 1, "GetReport's deadline must not fail Send after a mapping sync") + require.Len(t, client.syncCalls, 1) + }) +} + +// TestVMScraper_MappingRequiredClassifiedDespiteSlowSync covers MAPPING_REQUIRED +// staying mapping_required when mapping sync overruns GetReport's deadline. +func TestVMScraper_MappingRequiredClassifiedDespiteSlowSync(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{{ + Meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + }}, + errQueue: []error{vsockclient.ErrMappingRequired}, + getReportDelay: 15 * time.Millisecond, + syncDelay: 15 * time.Millisecond, + } + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s.perVMTimeout = 20 * time.Millisecond + s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} + + mappingBefore := testutil.ToFloat64(metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportMappingRequired)) + timeoutBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout)) + + s.pollOnce(t.Context()) + + require.Len(t, client.syncCalls, 1) + assert.Equal(t, mappingBefore+1, testutil.ToFloat64(metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportMappingRequired))) + assert.Equal(t, timeoutBefore, testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout)), + "a slow mapping sync must not rewrite MAPPING_REQUIRED as a transport timeout") + }) +} + +// TestVMScraper_DialAndGetReport_ClosesConnectionBeforeSync guards against +// maybeSyncRepoCPEMapping's second dial racing the first GetReport connection: roxagent +// allows only one connection at a time, so dialAndGetReport must close its +// stream before calling maybeSyncRepoCPEMapping, not rely solely on its deferred close. +func TestVMScraper_DialAndGetReport_ClosesConnectionBeforeSync(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + dialer := &singleConnDialer{} + client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{{ + IndexReport: &v4.IndexReport{State: "IndexFinished"}, + Meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), + }}} + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, client) + s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} + + _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") + + require.Equal(t, scrapeOK, outcome) + require.Len(t, client.syncCalls, 1, "maybeSyncRepoCPEMapping's dial must succeed, not be rejected as busy by a still-open first connection") +} + +// TestVMScraper_DialAndGetReport_MappingRequired_ClosesConnectionBeforeSync +// is the MAPPING_REQUIRED-error counterpart: that path closes and syncs +// from a different branch than the success path above. +func TestVMScraper_DialAndGetReport_MappingRequired_ClosesConnectionBeforeSync(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + dialer := &singleConnDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{{Meta: metaWithMapping("", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)}}, + errQueue: []error{vsockclient.ErrMappingRequired}, + } + s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, client) + s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} + + _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") + + require.Equal(t, scrapeRetryable, outcome) + require.Len(t, client.syncCalls, 1, "maybeSyncRepoCPEMapping's dial must succeed, not be rejected as busy by a still-open first connection") +} + func TestClampPollInterval(t *testing.T) { cases := map[string]struct { in time.Duration diff --git a/sensor/common/virtualmachine/vsockclient/client.go b/sensor/common/virtualmachine/vsockclient/client.go index 5fe235c329aab..56246054a2cba 100644 --- a/sensor/common/virtualmachine/vsockclient/client.go +++ b/sensor/common/virtualmachine/vsockclient/client.go @@ -38,6 +38,9 @@ var ( ErrUnknownAgentError = errors.New("unrecognized agent error code") // ErrMappingRequired indicates the agent has no repository-to-CPE mapping yet. ErrMappingRequired = errors.New("agent has no repository-to-CPE mapping yet") + // ErrMappingNotSensorManaged indicates the agent rejected a + // SyncRepoCPEMapping because it is URL-managed, not Sensor-managed. + ErrMappingNotSensorManaged = errors.New("agent does not accept a sensor-pushed mapping") ) // GetReportResult holds the parsed response from a GetReport call. @@ -129,12 +132,62 @@ func (c *Client) GetReport(ctx context.Context, stream io.ReadWriteCloser, lastK Meta: resp.GetMeta(), }, nil case *pb.VMServiceResponse_Error: - return nil, errorFromResponse(r.Error) + // Meta is still returned alongside the error: a VM with no mapping + // at all has nothing to report except MAPPING_REQUIRED, and that + // error's Meta is the only way the caller learns it needs to push one. + return &GetReportResult{Meta: resp.GetMeta()}, errorFromResponse(r.Error) default: return nil, fmt.Errorf("unexpected response type: %T", resp.GetResult()) } } +// SyncRepoCPEMapping pushes mapping to the agent over stream and reports +// whether the agent applied it. Mirrors GetReport's request/response framing +// and error classification for the sync_repo_cpe_mapping method. +func (c *Client) SyncRepoCPEMapping(ctx context.Context, stream io.ReadWriteCloser, mapping []byte) (updated bool, meta *pb.ResponseMeta, err error) { + stop := context.AfterFunc(ctx, func() { + _ = stream.Close() + }) + defer stop() + + req := &pb.VMServiceRequest{ + Meta: &pb.RequestMeta{ + RequestId: uuid.NewV4().String(), + Capabilities: c.capabilities, + }, + Method: &pb.VMServiceRequest_SyncRepoCpeMapping{ + SyncRepoCpeMapping: &pb.SyncRepoCPEMappingRequest{Mapping: mapping}, + }, + } + + reqData, err := proto.Marshal(req) + if err != nil { + return false, nil, fmt.Errorf("marshaling request: %w", err) + } + if err := vsockframing.WriteFrame(stream, reqData); err != nil { + return false, nil, wrapStreamErr(ctx, "sending request", err) + } + + respData, err := vsockframing.ReadFrame(stream, uint32(c.maxResponseSize)) + if err != nil { + return false, nil, wrapStreamErr(ctx, "reading response", err) + } + + var resp pb.VMServiceResponse + if err := proto.Unmarshal(respData, &resp); err != nil { + return false, nil, fmt.Errorf("unmarshaling response: %w", err) + } + + switch r := resp.GetResult().(type) { + case *pb.VMServiceResponse_SyncRepoCpeMapping: + return r.SyncRepoCpeMapping.GetUpdated(), resp.GetMeta(), nil + case *pb.VMServiceResponse_Error: + return false, resp.GetMeta(), errorFromResponse(r.Error) + default: + return false, nil, fmt.Errorf("unexpected response type: %T", resp.GetResult()) + } +} + func wrapStreamErr(ctx context.Context, op string, err error) error { if ctxErr := ctx.Err(); ctxErr != nil { return fmt.Errorf("%s: %w", op, ctxErr) @@ -158,6 +211,8 @@ func errorFromResponse(e *pb.ErrorResponse) error { return fmt.Errorf("%w: %s", ErrBusy, e.GetMessage()) case pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED: return fmt.Errorf("%w: %s", ErrMappingRequired, e.GetMessage()) + case pb.ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED: + return fmt.Errorf("%w: %s", ErrMappingNotSensorManaged, e.GetMessage()) default: return fmt.Errorf("%w: agent error (%s): %s", ErrUnknownAgentError, e.GetCode(), e.GetMessage()) } diff --git a/sensor/common/virtualmachine/vsockclient/client_integration_test.go b/sensor/common/virtualmachine/vsockclient/client_integration_test.go index 2d8233d7fc889..0fd7183a52446 100644 --- a/sensor/common/virtualmachine/vsockclient/client_integration_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_integration_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "path/filepath" "testing" "testing/synctest" "time" @@ -12,25 +13,29 @@ import ( roxagentvsock "github.com/stackrox/rox/compliance/virtualmachines/roxagent/vsockserver" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" "github.com/stackrox/rox/pkg/vsockframing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) -// readyMappingProvider is Ready so GetReport reaches the cache path these -// tests cover (empty cache → NOT_READY, seeded cache → report). -type readyMappingProvider struct{} - -func (readyMappingProvider) Ready() bool { return true } -func (readyMappingProvider) Hash() string { return "" } -func (readyMappingProvider) UpdatePath() pb.RepoCPEMappingUpdatePath { - return pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR +// fakeMappingProvider is a MappingProvider test double whose Ready/Hash/ +// UpdatePath are directly settable; Bytes/Path are unused by the Handler +// and always error. +type fakeMappingProvider struct { + ready bool + hash string + updatePath pb.RepoCPEMappingUpdatePath } -func (readyMappingProvider) Bytes() ([]byte, error) { return nil, errors.New("not implemented") } -func (readyMappingProvider) Path() (string, error) { return "", errors.New("not implemented") } -var _ roxagentvsock.MappingProvider = readyMappingProvider{} +func (f *fakeMappingProvider) Ready() bool { return f.ready } +func (f *fakeMappingProvider) Hash() string { return f.hash } +func (f *fakeMappingProvider) UpdatePath() pb.RepoCPEMappingUpdatePath { return f.updatePath } +func (f *fakeMappingProvider) Bytes() ([]byte, error) { return nil, errors.New("not implemented") } +func (f *fakeMappingProvider) Path() (string, error) { return "", errors.New("not implemented") } + +var _ roxagentvsock.MappingProvider = (*fakeMappingProvider)(nil) // exchangeTimeout bounds a single client/handler exchange. Under synctest the // fake clock advances this duration instantly once every goroutine is durably @@ -76,7 +81,7 @@ func newProtocolHarness(t *testing.T, opts protocolHarnessOptions) *protocolHarn return &protocolHarness{ client: NewClient(opts.capabilities, opts.maxResponseSize), cache: cache, - handler: roxagentvsock.NewHandler(cache, opts.agentVersion, readyMappingProvider{}, nil), + handler: roxagentvsock.NewHandler(cache, opts.agentVersion, &fakeMappingProvider{ready: true}, nil), } } @@ -223,6 +228,69 @@ func TestGetReportIntegration_TokenRoundTrip(t *testing.T) { }) } +// syncOnce mirrors exchangeOnce for a single SyncRepoCPEMapping exchange. +func syncOnce(t *testing.T, client *Client, mapping []byte, responder func(net.Conn)) (bool, *pb.ResponseMeta, error) { + t.Helper() + + var updated bool + var meta *pb.ResponseMeta + var err error + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), exchangeTimeout) + defer cancel() + + clientConn, agentConn := net.Pipe() + + done := make(chan struct{}) + go func() { + defer close(done) + responder(agentConn) + }() + + updated, meta, err = client.SyncRepoCPEMapping(ctx, clientConn, mapping) + _ = clientConn.Close() + select { + case <-done: + case <-ctx.Done(): + if err == nil { + err = fmt.Errorf("waiting for responder: %w", ctx.Err()) + } + } + }) + return updated, meta, err +} + +// TestSyncRepoCPEMappingIntegration_FullRoundTrip drives a real Client and +// Handler through GetReport (MAPPING_REQUIRED) -> SyncRepoCPEMapping -> +// GetReport again; every seam here is otherwise only unit-tested against fakes. +func TestSyncRepoCPEMappingIntegration_FullRoundTrip(t *testing.T) { + mapping := []byte(`{"data":{"rhel-9-server-rpms":{"cpes":["cpe:/o:redhat:enterprise_linux:9"]}}}`) + updater := roxagentvsock.NewSensorUpdater(filepath.Join(t.TempDir(), "cache.json"), "", nil) + cache := &roxagentvsock.ReportCache{} + handler := roxagentvsock.NewHandler(cache, "test-agent", updater, updater) + client := NewClient([]string{CapabilityReportV1}, 10<<20) + + first, err := exchangeOnce(t, client, "", handler.HandleConn) + require.ErrorIs(t, err, ErrMappingRequired, "no bootstrapped mapping must surface as MAPPING_REQUIRED") + require.NotNil(t, first.Meta) + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, first.Meta.GetRepoCpeMappingUpdatePath()) + assert.Empty(t, first.Meta.GetRepoCpeMappingHash()) + + updated, syncMeta, err := syncOnce(t, client, mapping, handler.HandleConn) + require.NoError(t, err) + assert.True(t, updated) + wantHash := cpemapping.HashMapping(mapping) + assert.Equal(t, wantHash, syncMeta.GetRepoCpeMappingHash()) + + cache.SetReport(&v4.IndexReport{HashId: "sync-integration-hash"}, nil, wantHash) + + second, err := exchangeOnce(t, client, "", handler.HandleConn) + require.NoError(t, err) + require.NotNil(t, second.IndexReport) + assert.Equal(t, "sync-integration-hash", second.IndexReport.GetHashId()) + assert.Equal(t, wantHash, second.Meta.GetRepoCpeMappingHash(), "the follow-up GetReport must reflect the mapping pushed via Sync") +} + // --- Compatibility persona helpers --- // respondOnce reads one framed VMServiceRequest, builds a response, and writes diff --git a/sensor/common/virtualmachine/vsockclient/client_test.go b/sensor/common/virtualmachine/vsockclient/client_test.go index 82bc94ba3f7ed..ffd701dafa6b3 100644 --- a/sensor/common/virtualmachine/vsockclient/client_test.go +++ b/sensor/common/virtualmachine/vsockclient/client_test.go @@ -148,6 +148,11 @@ func TestSendGetReport_ErrorCodes(t *testing.T) { message: "repository-to-CPE mapping not yet available", wantErr: ErrMappingRequired, }, + "should wrap ErrMappingNotSensorManaged for MAPPING_NOT_SENSOR_MANAGED": { + code: pb.ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED, + message: "url-managed", + wantErr: ErrMappingNotSensorManaged, + }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { @@ -172,6 +177,87 @@ func TestSendGetReport_ErrorCodes(t *testing.T) { } } +// TestSendGetReport_ErrorCarriesMeta verifies that an error response's Meta +// is still returned alongside the error, not dropped: MAPPING_REQUIRED is +// the only way a VM with no mapping at all can tell Sensor it needs one. +func TestSendGetReport_ErrorCarriesMeta(t *testing.T) { + client := NewClient(nil, 10<<20) + clientConn, agentConn := net.Pipe() + defer utils.IgnoreError(clientConn.Close) + + go serveOnce(t, agentConn, &pb.VMServiceResponse{ + Meta: &pb.ResponseMeta{ + AgentVersion: "test-agent", + RepoCpeMappingHash: new(""), + RepoCpeMappingUpdatePath: pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR.Enum(), + }, + Result: &pb.VMServiceResponse_Error{ + Error: &pb.ErrorResponse{Code: pb.ErrorCode_ERROR_CODE_MAPPING_REQUIRED, Message: "no mapping yet"}, + }, + }, nil) + + result, err := client.GetReport(context.Background(), clientConn, "") + require.Error(t, err) + assert.ErrorIs(t, err, ErrMappingRequired) + require.NotNil(t, result, "error result must still carry Meta") + require.NotNil(t, result.Meta) + assert.Equal(t, pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR, result.Meta.GetRepoCpeMappingUpdatePath()) + assert.Empty(t, result.Meta.GetRepoCpeMappingHash()) +} + +func TestSendSyncRepoCPEMapping(t *testing.T) { + cases := map[string]struct { + resp *pb.VMServiceResponse + wantUpdated bool + wantErr error + }{ + "should report updated true on success": { + resp: &pb.VMServiceResponse{ + Meta: &pb.ResponseMeta{AgentVersion: "test-agent"}, + Result: &pb.VMServiceResponse_SyncRepoCpeMapping{SyncRepoCpeMapping: &pb.SyncRepoCPEMappingResponse{Updated: true}}, + }, + wantUpdated: true, + }, + "should report updated false when the mapping already matched": { + resp: &pb.VMServiceResponse{ + Meta: &pb.ResponseMeta{AgentVersion: "test-agent"}, + Result: &pb.VMServiceResponse_SyncRepoCpeMapping{SyncRepoCpeMapping: &pb.SyncRepoCPEMappingResponse{Updated: false}}, + }, + wantUpdated: false, + }, + "should wrap ErrMappingNotSensorManaged when the agent is URL-managed": { + resp: &pb.VMServiceResponse{ + Meta: &pb.ResponseMeta{AgentVersion: "test-agent"}, + Result: &pb.VMServiceResponse_Error{ + Error: &pb.ErrorResponse{Code: pb.ErrorCode_ERROR_CODE_MAPPING_NOT_SENSOR_MANAGED, Message: "url-managed"}, + }, + }, + wantErr: ErrMappingNotSensorManaged, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + client := NewClient(nil, 10<<20) + clientConn, agentConn := net.Pipe() + defer utils.IgnoreError(clientConn.Close) + + go serveOnce(t, agentConn, tc.resp, func(req *pb.VMServiceRequest) { + assert.Equal(t, []byte("mapping-bytes"), req.GetSyncRepoCpeMapping().GetMapping()) + }) + + updated, meta, err := client.SyncRepoCPEMapping(context.Background(), clientConn, []byte("mapping-bytes")) + if tc.wantErr != nil { + require.Error(t, err) + assert.ErrorIs(t, err, tc.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.wantUpdated, updated) + assert.Equal(t, "test-agent", meta.GetAgentVersion()) + }) + } +} + // Cancelling ctx must unblock a GetReport that is stuck waiting for a // response — shutdown cancel does not rewrite the dial-time deadline, so // GetReport closes the stream itself. diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index 443803a93df1c..e88ce9b2ca54c 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -44,6 +44,7 @@ import ( "github.com/stackrox/rox/sensor/common/processsignal" "github.com/stackrox/rox/sensor/common/reprocessor" "github.com/stackrox/rox/sensor/common/scan" + "github.com/stackrox/rox/sensor/common/scannerdefinitions" "github.com/stackrox/rox/sensor/common/sensor" signalService "github.com/stackrox/rox/sensor/common/signal" "github.com/stackrox/rox/sensor/common/store" @@ -182,6 +183,7 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { var virtualMachineHandler vmIndex.Handler var vmScraper *vmscraper.VMScraper var vmStats clustermetrics.VMStatsSource + var repo2CPE *scannerdefinitions.Repo2CPE if features.VirtualMachines.Enabled() { virtualMachineHandler = vmIndex.NewHandler(storeProvider.VirtualMachines()) @@ -191,7 +193,17 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) } else { vmProtoClient := vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) - vmScraper = vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient) + repo2CPE, err = scannerdefinitions.NewRepo2CPE(env.CentralEndpoint.Setting(), cfg.certLoader()) + if err != nil { + log.Errorf("Failed to create repo-to-CPE refresher: %v", err) + } + // A typed-nil *Repo2CPE is a non-nil Repo2CPEFetcher, so + // maybeSyncRepoCPEMapping would call FetchRepo2CPE on a nil receiver. + var repo2CPEFetcher vmscraper.Repo2CPEFetcher + if repo2CPE != nil { + repo2CPEFetcher = repo2CPE + } + vmScraper = vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient, repo2CPEFetcher) vmStats = vmScraper } } @@ -219,6 +231,9 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { if virtualMachineHandler != nil { components = append(components, virtualMachineHandler) } + if repo2CPE != nil { + components = append(components, repo2CPE) + } if vmScraper != nil { components = append(components, vmScraper) } diff --git a/tools/allowed-large-files b/tools/allowed-large-files index aaef7317597f7..ac11a963ed3f6 100644 --- a/tools/allowed-large-files +++ b/tools/allowed-large-files @@ -75,6 +75,7 @@ scanner/e2etests/testdata/image_tests.orig.json scanner/enricher/csaf/testdata/* scanner/enricher/nvd/testdata/feed.json scripts/ci/lib.sh +sensor/common/virtualmachine/vmscraper/scraper_test.go sensor/tests/data/policies.json sensor/upgrader/preflight/testdata/k8s-1.22.json.gz tests/common.go From b2dc938f9f57f158ad9cb6021ce2fe5adf35e436 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:31:43 +0000 Subject: [PATCH 200/638] Update Konflux references (#22640) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 18 +++++++++--------- .tekton/checks.yaml | 4 ++-- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/main-pipeline.yaml | 18 +++++++++--------- .tekton/operator-bundle-pipeline.yaml | 18 +++++++++--------- .tekton/operator-index-build.yaml | 6 +++--- .tekton/retag-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 18 +++++++++--------- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 17f5798fbf40e..958438687eaf3 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -251,7 +251,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da - name: kind value: task resolver: bundles @@ -310,7 +310,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 - name: kind value: task resolver: bundles @@ -350,7 +350,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 - name: kind value: task resolver: bundles @@ -410,7 +410,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.1@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 - name: kind value: task resolver: bundles @@ -582,7 +582,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 325d6f2d4453f..eaa07341e9f65 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -146,7 +146,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:7d15b5066a8bedc8926286078baa5fec5d42cc7c06054823581c62ccc7a93e86 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:d44fb2d0e1bb5eb916777bf129b9d6b5e8c08a14f6c4505dc67bce22660b3d9f - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index f8dd80df45c2f..ad0cd4dbd7f74 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -201,7 +201,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index b5200a3ec85a3..07740ca8fed8a 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -169,7 +169,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 - name: kind value: task resolver: bundles @@ -193,7 +193,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da - name: kind value: task resolver: bundles @@ -331,7 +331,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 - name: kind value: task resolver: bundles @@ -351,7 +351,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 - name: kind value: task resolver: bundles @@ -372,7 +372,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 - name: kind value: task resolver: bundles @@ -432,7 +432,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.1@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 - name: kind value: task resolver: bundles @@ -578,7 +578,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 - name: kind value: task resolver: bundles @@ -604,7 +604,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 77f181df728db..32a37b4605a59 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -265,7 +265,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 - name: kind value: task resolver: bundles @@ -289,7 +289,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -348,7 +348,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da - name: kind value: task resolver: bundles @@ -517,7 +517,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:7d15b5066a8bedc8926286078baa5fec5d42cc7c06054823581c62ccc7a93e86 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:d44fb2d0e1bb5eb916777bf129b9d6b5e8c08a14f6c4505dc67bce22660b3d9f - name: kind value: task resolver: bundles @@ -536,7 +536,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:721a5937da7958a568b6ed4c2b97c6c9047cd795de1a9c3dcfff4b5936be4bc3 + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:ccd3665345d86c6799bc7e2e6ad86b277d9f3a5c40b513e6f2a0af8ad92e7dba - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 - name: kind value: task resolver: bundles @@ -616,7 +616,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.1@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 - name: kind value: task resolver: bundles @@ -756,7 +756,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 - name: kind value: task resolver: bundles @@ -782,7 +782,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index b0dabcda71d7c..bb6756551c44d 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -195,7 +195,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -414,7 +414,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 - name: kind value: task resolver: bundles @@ -433,7 +433,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index ea8b5e386632d..71ee9192399f4 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index a81a4ddaa3c0a..3c424166d3b3a 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:f71e6710ff88f675ab27fcd1ba9ec4cad88d7169b5da018c51dde44f1ba7921c + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:fbd392bffc0bb04f639e0c0044f36ce7f9f4a283add5bea69a127b80608cad0c + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 - name: kind value: task resolver: bundles @@ -271,7 +271,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9214cb1d568a96bf5d605daac34f3b713325e1756724b64ac1ff2cb554e4d2f3 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da - name: kind value: task resolver: bundles @@ -330,7 +330,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:b1925fe47815ec57ff8fcaec93fe3653fbbd14ae31c1cab7c83a8fab7a7d4576 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 - name: kind value: task resolver: bundles @@ -349,7 +349,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:a9c9a480b8dfd8be66a3c451b0d368e65591c9961902e700bfc248a6fd09cb11 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 - name: kind value: task resolver: bundles @@ -370,7 +370,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:958e34bc09b185bb8ad0dc6275da77240f95e64583b40808dd8d7faf0e1e2cb2 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 - name: kind value: task resolver: bundles @@ -430,7 +430,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.4.1@sha256:219964897ac4d5a9473ec297a1b39ae078a34b76b45d04c4672bd569cc295c29 - name: kind value: task resolver: bundles @@ -576,7 +576,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:f110c53fd58cab7ac6e47d472bb801d49e2f584eb31837db9d611e11b3862a3d + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 - name: kind value: task resolver: bundles @@ -602,7 +602,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:a20180c6e4d88150f54ef441872dfaa244ddc64ac02fd8d8dd92714e5b155fdf + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 - name: kind value: task resolver: bundles From dcf6d34e4e1fea2b11df03499526306946edcdfd Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 4 Sep 2026 12:24:32 +0000 Subject: [PATCH 201/638] ROX-36250: add NODE_VULNERABILITY report type to schema (#22505) Co-authored-by: Claude Sonnet 4.5 Co-authored-by: Cursor --- .../datastore/store/postgres/store.go | 5 +- generated/storage/report_snapshot.pb.go | 2 +- .../migration.go | 24 +++ .../migration_impl.go | 29 ++++ .../migration_test.go | 147 ++++++++++++++++++ .../schema/report_snapshots.go | 40 +++++ .../test/schema/report_snapshots.go | 39 +++++ migrator/runner/all.go | 1 + pkg/migrations/internal/seq_num.go | 2 +- pkg/postgres/schema/report_snapshots.go | 1 + proto/storage/report_snapshot.proto | 2 +- 11 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 migrator/migrations/m_226_to_m_227_backfill_report_type/migration.go create mode 100644 migrator/migrations/m_226_to_m_227_backfill_report_type/migration_impl.go create mode 100644 migrator/migrations/m_226_to_m_227_backfill_report_type/migration_test.go create mode 100644 migrator/migrations/m_226_to_m_227_backfill_report_type/schema/report_snapshots.go create mode 100644 migrator/migrations/m_226_to_m_227_backfill_report_type/test/schema/report_snapshots.go diff --git a/central/reports/snapshot/datastore/store/postgres/store.go b/central/reports/snapshot/datastore/store/postgres/store.go index d019cc69b89cd..12fc029ac5500 100644 --- a/central/reports/snapshot/datastore/store/postgres/store.go +++ b/central/reports/snapshot/datastore/store/postgres/store.go @@ -104,6 +104,7 @@ func insertIntoReportSnapshots(batch *pgx.Batch, obj *storage.ReportSnapshot) er pgutils.NilOrUUID(obj.GetReportId()), pgutils.NilOrString(obj.GetReportConfigurationId()), obj.GetName(), + obj.GetType(), obj.GetReportStatus().GetRunState(), protocompat.NilOrTime(obj.GetReportStatus().GetQueuedAt()), protocompat.NilOrTime(obj.GetReportStatus().GetCompletedAt()), @@ -115,7 +116,7 @@ func insertIntoReportSnapshots(batch *pgx.Batch, obj *storage.ReportSnapshot) er serialized, } - finalStr := "INSERT INTO report_snapshots (ReportId, ReportConfigurationId, Name, ReportStatus_RunState, ReportStatus_QueuedAt, ReportStatus_CompletedAt, ReportStatus_ReportRequestType, ReportStatus_ReportNotificationMethod, Requester_Id, Requester_Name, AreaOfConcern, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT(ReportId) DO UPDATE SET ReportId = EXCLUDED.ReportId, ReportConfigurationId = EXCLUDED.ReportConfigurationId, Name = EXCLUDED.Name, ReportStatus_RunState = EXCLUDED.ReportStatus_RunState, ReportStatus_QueuedAt = EXCLUDED.ReportStatus_QueuedAt, ReportStatus_CompletedAt = EXCLUDED.ReportStatus_CompletedAt, ReportStatus_ReportRequestType = EXCLUDED.ReportStatus_ReportRequestType, ReportStatus_ReportNotificationMethod = EXCLUDED.ReportStatus_ReportNotificationMethod, Requester_Id = EXCLUDED.Requester_Id, Requester_Name = EXCLUDED.Requester_Name, AreaOfConcern = EXCLUDED.AreaOfConcern, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO report_snapshots (ReportId, ReportConfigurationId, Name, Type, ReportStatus_RunState, ReportStatus_QueuedAt, ReportStatus_CompletedAt, ReportStatus_ReportRequestType, ReportStatus_ReportNotificationMethod, Requester_Id, Requester_Name, AreaOfConcern, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT(ReportId) DO UPDATE SET ReportId = EXCLUDED.ReportId, ReportConfigurationId = EXCLUDED.ReportConfigurationId, Name = EXCLUDED.Name, Type = EXCLUDED.Type, ReportStatus_RunState = EXCLUDED.ReportStatus_RunState, ReportStatus_QueuedAt = EXCLUDED.ReportStatus_QueuedAt, ReportStatus_CompletedAt = EXCLUDED.ReportStatus_CompletedAt, ReportStatus_ReportRequestType = EXCLUDED.ReportStatus_ReportRequestType, ReportStatus_ReportNotificationMethod = EXCLUDED.ReportStatus_ReportNotificationMethod, Requester_Id = EXCLUDED.Requester_Id, Requester_Name = EXCLUDED.Requester_Name, AreaOfConcern = EXCLUDED.AreaOfConcern, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -125,6 +126,7 @@ var copyColsReportSnapshots = []string{ "reportid", "reportconfigurationid", "name", + "type", "reportstatus_runstate", "reportstatus_queuedat", "reportstatus_completedat", @@ -170,6 +172,7 @@ func copyFromReportSnapshots(ctx context.Context, s pgSearch.Deleter, tx *postgr pgutils.NilOrUUID(obj.GetReportId()), pgutils.NilOrString(obj.GetReportConfigurationId()), obj.GetName(), + obj.GetType(), obj.GetReportStatus().GetRunState(), protocompat.NilOrTime(obj.GetReportStatus().GetQueuedAt()), protocompat.NilOrTime(obj.GetReportStatus().GetCompletedAt()), diff --git a/generated/storage/report_snapshot.pb.go b/generated/storage/report_snapshot.pb.go index 1f4e4f2cedd82..54acf8958dd53 100644 --- a/generated/storage/report_snapshot.pb.go +++ b/generated/storage/report_snapshot.pb.go @@ -226,7 +226,7 @@ type ReportSnapshot struct { ReportConfigurationId string `protobuf:"bytes,2,opt,name=report_configuration_id,json=reportConfigurationId,proto3" json:"report_configuration_id,omitempty" search:"Report Configuration ID" sql:"fk(ReportConfiguration:id),allow-null"` // @gotags: search:"Report Configuration ID" sql:"fk(ReportConfiguration:id),allow-null" Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty" search:"Report Name"` // @gotags: search:"Report Name" Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` - Type ReportSnapshot_ReportType `protobuf:"varint,5,opt,name=type,proto3,enum=storage.ReportSnapshot_ReportType" json:"type,omitempty"` + Type ReportSnapshot_ReportType `protobuf:"varint,5,opt,name=type,proto3,enum=storage.ReportSnapshot_ReportType" json:"type,omitempty" search:"Report Type"` // @gotags: search:"Report Type" // Types that are valid to be assigned to Filter: // // *ReportSnapshot_VulnReportFilters diff --git a/migrator/migrations/m_226_to_m_227_backfill_report_type/migration.go b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration.go new file mode 100644 index 0000000000000..7ab145932298f --- /dev/null +++ b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration.go @@ -0,0 +1,24 @@ +// Code generated by make bootstrap_migration generator. DO NOT EDIT. +package m226tom227 + +import ( + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/migrator/migrations" + "github.com/stackrox/rox/migrator/types" +) + +const ( + startSeqNum = 226 +) + +var ( + migration = types.Migration{ + StartingSeqNum: startSeqNum, + VersionAfter: &storage.Version{SeqNum: int32(startSeqNum + 1)}, + Run: migrate, + } +) + +func init() { + migrations.MustRegisterMigration(migration) +} diff --git a/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_impl.go b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_impl.go new file mode 100644 index 0000000000000..328466c5b37be --- /dev/null +++ b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_impl.go @@ -0,0 +1,29 @@ +package m226tom227 + +import ( + "context" + + "github.com/pkg/errors" + "github.com/stackrox/rox/migrator/migrations/loghelper" + "github.com/stackrox/rox/migrator/migrations/m_226_to_m_227_backfill_report_type/schema" + "github.com/stackrox/rox/migrator/types" + "github.com/stackrox/rox/pkg/postgres/pgutils" + "github.com/stackrox/rox/pkg/sac" +) + +var ( + log = loghelper.LogWrapper{} +) + +func migrate(database *types.Databases) error { + ctx := sac.WithAllAccess(context.Background()) + + pgutils.CreateTableFromModel(ctx, database.GormDB, schema.CreateTableReportSnapshotsStmt) + + result, err := database.PostgresDB.Exec(ctx, `UPDATE report_snapshots SET type = 0 WHERE type IS NULL`) + if err != nil { + return errors.Wrap(err, "failed to backfill report_snapshots.type") + } + log.WriteToStderrf("Backfilled type=0 for %d report snapshots", result.RowsAffected()) + return nil +} diff --git a/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_test.go b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_test.go new file mode 100644 index 0000000000000..fe3936fa3a50a --- /dev/null +++ b/migrator/migrations/m_226_to_m_227_backfill_report_type/migration_test.go @@ -0,0 +1,147 @@ +//go:build sql_integration + +package m226tom227 + +import ( + "context" + "fmt" + "testing" + + "github.com/stackrox/rox/generated/storage" + updatedSchema "github.com/stackrox/rox/migrator/migrations/m_226_to_m_227_backfill_report_type/schema" + oldSchema "github.com/stackrox/rox/migrator/migrations/m_226_to_m_227_backfill_report_type/test/schema" + pghelper "github.com/stackrox/rox/migrator/migrations/postgreshelper" + "github.com/stackrox/rox/migrator/types" + "github.com/stackrox/rox/pkg/postgres/pgutils" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/suite" +) + +type migrationTestSuite struct { + suite.Suite + + db *pghelper.TestPostgres + ctx context.Context +} + +func TestMigration(t *testing.T) { + suite.Run(t, new(migrationTestSuite)) +} + +func (s *migrationTestSuite) SetupSuite() { + s.ctx = sac.WithAllAccess(context.Background()) + s.db = pghelper.ForT(s.T(), false) +} + +func (s *migrationTestSuite) TearDownSuite() { + s.db.Teardown(s.T()) +} + +func (s *migrationTestSuite) databases() *types.Databases { + return &types.Databases{ + GormDB: s.db.GetGormDB(), + PostgresDB: s.db.DB, + DBCtx: s.ctx, + } +} + +func (s *migrationTestSuite) setupPreMigrationTable() { + _, err := s.db.DB.Exec(s.ctx, "DROP TABLE IF EXISTS report_snapshots") + s.Require().NoError(err) + pgutils.CreateTableFromModel(s.ctx, s.db.GetGormDB(), oldSchema.CreateTableReportSnapshotsStmt) +} + +func (s *migrationTestSuite) insertOldSnapshot(name string) { + snapshot := oldSchema.ReportSnapshots{ + ReportID: uuid.NewV4().String(), + ReportConfigurationID: uuid.NewV4().String(), + Name: name, + Serialized: []byte("{}"), + } + s.Require().NoError(s.db.GetGormDB().Create(&snapshot).Error) +} + +func (s *migrationTestSuite) typeColumnExists() bool { + var exists bool + err := s.db.DB.QueryRow(s.ctx, ` + SELECT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'report_snapshots' AND column_name = 'type' + ) + `).Scan(&exists) + s.Require().NoError(err) + return exists +} + +func (s *migrationTestSuite) countWhere(where string) int { + var count int + s.Require().NoError(s.db.DB.QueryRow(s.ctx, "SELECT COUNT(*) FROM report_snapshots WHERE "+where).Scan(&count)) + return count +} + +func (s *migrationTestSuite) TestMigration() { + s.setupPreMigrationTable() + dbs := s.databases() + + const numReports = 3 + for i := range numReports { + s.insertOldSnapshot(fmt.Sprintf("Test Report %d", i+1)) + } + + s.Require().False(s.typeColumnExists(), "Type column should not exist before migration") + + s.Require().NoError(migration.Run(dbs)) + + s.Require().True(s.typeColumnExists(), "Type column should exist after migration") + s.Require().Equal(numReports, s.countWhere("type = 0"), "All reports should have type=0 after migration") + s.Require().Equal(0, s.countWhere("type IS NULL"), "No reports should have NULL type after migration") + + s.Require().NoError(migration.Run(dbs)) + s.Require().Equal(numReports, s.countWhere("type = 0"), "Count should remain the same after idempotent run") +} + +func (s *migrationTestSuite) TestMigrationWithMixedData() { + s.setupPreMigrationTable() + dbs := s.databases() + + s.insertOldSnapshot("Pre-column Report") + + pgutils.CreateTableFromModel(s.ctx, s.db.GetGormDB(), updatedSchema.CreateTableReportSnapshotsStmt) + + vulnerabilityType := int32(storage.ReportSnapshot_VULNERABILITY) + nodeVulnerabilityType := int32(storage.ReportSnapshot_NODE_VULNERABILITY) + testData := []struct { + name string + typ *int32 + }{ + {"Null Report 1", nil}, + {"Existing Report 1", &vulnerabilityType}, + {"Node Report 1", &nodeVulnerabilityType}, + } + for _, td := range testData { + if td.typ == nil { + _, err := s.db.DB.Exec(s.ctx, ` + INSERT INTO report_snapshots (reportid, reportconfigurationid, name, serialized, type) + VALUES ($1, $2, $3, $4, NULL) + `, uuid.NewV4().String(), uuid.NewV4().String(), td.name, []byte("{}")) + s.Require().NoError(err) + continue + } + _, err := s.db.DB.Exec(s.ctx, ` + INSERT INTO report_snapshots (reportid, reportconfigurationid, name, serialized, type) + VALUES ($1, $2, $3, $4, $5) + `, uuid.NewV4().String(), uuid.NewV4().String(), td.name, []byte("{}"), *td.typ) + s.Require().NoError(err) + } + + s.Require().Equal(2, s.countWhere("type IS NULL")) + s.Require().Equal(1, s.countWhere("type = 0")) + s.Require().Equal(1, s.countWhere("type = 1")) + + s.Require().NoError(migration.Run(dbs)) + + s.Require().Equal(0, s.countWhere("type IS NULL"), "No NULL values should remain") + s.Require().Equal(3, s.countWhere("type = 0"), "NULL values should be backfilled to 0, existing 0 unchanged") + s.Require().Equal(1, s.countWhere("type = 1"), "NODE_VULNERABILITY reports should remain unchanged") +} diff --git a/migrator/migrations/m_226_to_m_227_backfill_report_type/schema/report_snapshots.go b/migrator/migrations/m_226_to_m_227_backfill_report_type/schema/report_snapshots.go new file mode 100644 index 0000000000000..174f4446a4036 --- /dev/null +++ b/migrator/migrations/m_226_to_m_227_backfill_report_type/schema/report_snapshots.go @@ -0,0 +1,40 @@ +// Frozen schema for the report_snapshots table after adding the type column. + +package schema + +import ( + "time" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/postgres" +) + +var ( + // CreateTableReportSnapshotsStmt holds the create statement for table `report_snapshots`. + CreateTableReportSnapshotsStmt = &postgres.CreateStmts{ + GormModel: (*ReportSnapshots)(nil), + Children: []*postgres.CreateStmts{}, + } +) + +const ( + // ReportSnapshotsTableName specifies the name of the table in postgres. + ReportSnapshotsTableName = "report_snapshots" +) + +// ReportSnapshots holds the Gorm model for Postgres table `report_snapshots`. +type ReportSnapshots struct { + ReportID string `gorm:"column:reportid;type:uuid;primaryKey"` + ReportConfigurationID string `gorm:"column:reportconfigurationid;type:varchar"` + Name string `gorm:"column:name;type:varchar"` + Type storage.ReportSnapshot_ReportType `gorm:"column:type;type:integer"` + ReportStatusRunState storage.ReportStatus_RunState `gorm:"column:reportstatus_runstate;type:integer"` + ReportStatusQueuedAt *time.Time `gorm:"column:reportstatus_queuedat;type:timestamp"` + ReportStatusCompletedAt *time.Time `gorm:"column:reportstatus_completedat;type:timestamp"` + ReportStatusReportRequestType storage.ReportStatus_RunMethod `gorm:"column:reportstatus_reportrequesttype;type:integer"` + ReportStatusReportNotificationMethod storage.ReportStatus_NotificationMethod `gorm:"column:reportstatus_reportnotificationmethod;type:integer"` + RequesterID string `gorm:"column:requester_id;type:varchar"` + RequesterName string `gorm:"column:requester_name;type:varchar"` + AreaOfConcern string `gorm:"column:areaofconcern;type:varchar"` + Serialized []byte `gorm:"column:serialized;type:bytea"` +} diff --git a/migrator/migrations/m_226_to_m_227_backfill_report_type/test/schema/report_snapshots.go b/migrator/migrations/m_226_to_m_227_backfill_report_type/test/schema/report_snapshots.go new file mode 100644 index 0000000000000..5fb0e2d02ae81 --- /dev/null +++ b/migrator/migrations/m_226_to_m_227_backfill_report_type/test/schema/report_snapshots.go @@ -0,0 +1,39 @@ +// Frozen pre-migration schema for the report_snapshots table (without the type column). + +package schema + +import ( + "time" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/postgres" +) + +var ( + // CreateTableReportSnapshotsStmt holds the create statement for table `report_snapshots` (pre-migration). + CreateTableReportSnapshotsStmt = &postgres.CreateStmts{ + GormModel: (*ReportSnapshots)(nil), + Children: []*postgres.CreateStmts{}, + } +) + +const ( + // ReportSnapshotsTableName specifies the name of the table in postgres. + ReportSnapshotsTableName = "report_snapshots" +) + +// ReportSnapshots holds the pre-migration Gorm model (no type column). +type ReportSnapshots struct { + ReportID string `gorm:"column:reportid;type:uuid;primaryKey"` + ReportConfigurationID string `gorm:"column:reportconfigurationid;type:varchar"` + Name string `gorm:"column:name;type:varchar"` + ReportStatusRunState storage.ReportStatus_RunState `gorm:"column:reportstatus_runstate;type:integer"` + ReportStatusQueuedAt *time.Time `gorm:"column:reportstatus_queuedat;type:timestamp"` + ReportStatusCompletedAt *time.Time `gorm:"column:reportstatus_completedat;type:timestamp"` + ReportStatusReportRequestType storage.ReportStatus_RunMethod `gorm:"column:reportstatus_reportrequesttype;type:integer"` + ReportStatusReportNotificationMethod storage.ReportStatus_NotificationMethod `gorm:"column:reportstatus_reportnotificationmethod;type:integer"` + RequesterID string `gorm:"column:requester_id;type:varchar"` + RequesterName string `gorm:"column:requester_name;type:varchar"` + AreaOfConcern string `gorm:"column:areaofconcern;type:varchar"` + Serialized []byte `gorm:"column:serialized;type:bytea"` +} diff --git a/migrator/runner/all.go b/migrator/runner/all.go index 36d389254a23f..40cda51fdd633 100644 --- a/migrator/runner/all.go +++ b/migrator/runner/all.go @@ -19,4 +19,5 @@ import ( _ "github.com/stackrox/rox/migrator/migrations/m_223_to_m_224_add_deployment_type_and_enforcement_count_to_alerts" _ "github.com/stackrox/rox/migrator/migrations/m_224_to_m_225_populate_deployment_containers_imageidv2" _ "github.com/stackrox/rox/migrator/migrations/m_225_to_m_226_drop_unused_child_idx_indexes" + _ "github.com/stackrox/rox/migrator/migrations/m_226_to_m_227_backfill_report_type" ) diff --git a/pkg/migrations/internal/seq_num.go b/pkg/migrations/internal/seq_num.go index 9caca7a322b45..27c34574712f8 100644 --- a/pkg/migrations/internal/seq_num.go +++ b/pkg/migrations/internal/seq_num.go @@ -5,5 +5,5 @@ var ( // CurrentDBVersionSeqNum is the current DB version number. // This must be incremented every time we write a migration. // It is a shared constant between central and the migrator binary. - CurrentDBVersionSeqNum = 226 + CurrentDBVersionSeqNum = 227 ) diff --git a/pkg/postgres/schema/report_snapshots.go b/pkg/postgres/schema/report_snapshots.go index dd5f4894b17a8..b5e815fc90eb4 100644 --- a/pkg/postgres/schema/report_snapshots.go +++ b/pkg/postgres/schema/report_snapshots.go @@ -55,6 +55,7 @@ type ReportSnapshots struct { ReportID string `gorm:"column:reportid;type:uuid;primaryKey"` ReportConfigurationID string `gorm:"column:reportconfigurationid;type:varchar"` Name string `gorm:"column:name;type:varchar"` + Type storage.ReportSnapshot_ReportType `gorm:"column:type;type:integer"` ReportStatusRunState storage.ReportStatus_RunState `gorm:"column:reportstatus_runstate;type:integer"` ReportStatusQueuedAt *time.Time `gorm:"column:reportstatus_queuedat;type:timestamp"` ReportStatusCompletedAt *time.Time `gorm:"column:reportstatus_completedat;type:timestamp"` diff --git a/proto/storage/report_snapshot.proto b/proto/storage/report_snapshot.proto index 98707994acb74..260d5b35bdd6a 100644 --- a/proto/storage/report_snapshot.proto +++ b/proto/storage/report_snapshot.proto @@ -23,7 +23,7 @@ message ReportSnapshot { string report_configuration_id = 2; // @gotags: search:"Report Configuration ID" sql:"fk(ReportConfiguration:id),allow-null" string name = 3; // @gotags: search:"Report Name" string description = 4; - ReportType type = 5; + ReportType type = 5; // @gotags: search:"Report Type" oneof filter { VulnerabilityReportFilters vuln_report_filters = 6; ViewBasedVulnerabilityReportFilters view_based_vuln_report_filters = 12; From 1e2cdfb56509361e3057a6e5443c4709a5446582 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:00:02 +0200 Subject: [PATCH 202/638] ROX-35847: increase BuiltinPoliciesTest wait on OpenShift (#22613) --- .../src/test/groovy/BuiltinPoliciesTest.groovy | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qa-tests-backend/src/test/groovy/BuiltinPoliciesTest.groovy b/qa-tests-backend/src/test/groovy/BuiltinPoliciesTest.groovy index 0b9cc4098c5e6..5bfec018b8c02 100644 --- a/qa-tests-backend/src/test/groovy/BuiltinPoliciesTest.groovy +++ b/qa-tests-backend/src/test/groovy/BuiltinPoliciesTest.groovy @@ -1,6 +1,8 @@ import static Services.getPolicies import static Services.waitForViolation +import orchestratormanager.OrchestratorTypes + import io.stackrox.proto.api.v1.PolicyServiceOuterClass.PatchPolicyRequest import objects.Deployment @@ -72,6 +74,11 @@ class BuiltinPoliciesTest extends BaseSpecification { .setImage("non-existent:image"), ] + // Enabling disabled deploy-time policies re-evaluates every cluster workload. + // OpenShift platform namespaces make that queue several minutes long. + static final private Integer WAIT_FOR_VIOLATION_TIMEOUT = + (isRaceBuild() || Env.mustGetOrchestratorType() == OrchestratorTypes.OPENSHIFT) ? 450 : 120 + @Shared private List disabledPolicyIds @@ -127,7 +134,7 @@ class BuiltinPoliciesTest extends BaseSpecification { then: "Verify Violation for #policyName is triggered" - assert waitForViolation(deploymentName, policyName, isRaceBuild() ? 450 : 120) + assert waitForViolation(deploymentName, policyName, WAIT_FOR_VIOLATION_TIMEOUT) where: "Data inputs are:" From 8a0d701a130801f06517010c1d41c911b3150b82 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:18:55 +0200 Subject: [PATCH 203/638] fix(ci): Enable JUnit reporting for GHA-based upgrade tests (#22587) --- tests/e2e/lib.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 0f17518dafccd..89c37497d9e7d 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -1877,7 +1877,7 @@ handle_e2e_progress_failures() { fi case "$CI_JOB_NAME" in - *gke-upgrade-tests) + *gke-upgrade-tests*) record_upgrade_test_progess ;; *operator-e2e-tests|*-version-compatibility-tests|*-nongroovy-compatibility-tests) @@ -1910,13 +1910,16 @@ record_upgrade_test_progess() { # tracking files that the upgrade test leaves in its wake as it progresses. # tests/upgrade/postgres_sensor_run.sh - record_progress_step "${UPGRADE_PROGRESS_SENSOR_BUNDLE}" "${STATE_DEPLOYED}" \ - "postgres_sensor_run" "roxctl sensor bundle test" - record_progress_step "${UPGRADE_PROGRESS_UPGRADER}" "${UPGRADE_PROGRESS_SENSOR_BUNDLE}" \ - "postgres_sensor_run" "bin/upgrader tests" + if [[ "$CI_JOB_NAME" == "gke-upgrade-tests-sensor" ]]; then + record_progress_step "${UPGRADE_PROGRESS_SENSOR_BUNDLE}" "${STATE_DEPLOYED}" \ + "postgres_sensor_run" "roxctl sensor bundle test" + record_progress_step "${UPGRADE_PROGRESS_UPGRADER}" "${UPGRADE_PROGRESS_SENSOR_BUNDLE}" \ + "postgres_sensor_run" "bin/upgrader tests" + return + fi - # tests/upgrade/postgres_run.sh - record_progress_step "${UPGRADE_PROGRESS_POSTGRES_PREP}" "${UPGRADE_PROGRESS_UPGRADER}" \ + # tests/upgrade/postgres_run.sh and tests/upgrade/postgres_upgrade_run.sh + record_progress_step "${UPGRADE_PROGRESS_POSTGRES_PREP}" "${STATE_DEPLOYED}" \ "postgres_run" "Preparation for postgres testing" record_progress_step "${UPGRADE_PROGRESS_POSTGRES_EARLIER_CENTRAL}" "${UPGRADE_PROGRESS_POSTGRES_PREP}" \ "postgres_run" "Deployed earlier postgres central" @@ -1924,6 +1927,9 @@ record_upgrade_test_progess() { "postgres_run" "Bounced central" record_progress_step "${UPGRADE_PROGRESS_POSTGRES_CENTRAL_DB_BOUNCE}" "${UPGRADE_PROGRESS_POSTGRES_CENTRAL_BOUNCE}" \ "postgres_run" "Bounced central-db" + + # tests/upgrade/postgres_run.sh only + [[ "$CI_JOB_NAME" == "gke-upgrade-tests-central" ]] || return record_progress_step "${UPGRADE_PROGRESS_POSTGRES_MIGRATIONS}" "${UPGRADE_PROGRESS_POSTGRES_CENTRAL_DB_BOUNCE}" \ "postgres_run" "Test migrations with an upgrade to current" record_progress_step "${UPGRADE_PROGRESS_POSTGRES_ROLLBACK}" "${UPGRADE_PROGRESS_POSTGRES_MIGRATIONS}" \ From 82f86c14774a0b4406b140becf128d282b820fd4 Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:51:58 -0400 Subject: [PATCH 204/638] ROX-36264: Wire vulnerability reporting into worker (#21574) Co-authored-by: Claude Opus 4.6 (1M context) --- central/notifier/service/service.go | 5 +- central/notifier/service/service_impl.go | 15 ++ central/notifier/service/singleton.go | 2 + .../reports/scheduler/v2/mocks/scheduler.go | 14 + .../v2/reportgenerator/report_gen.go | 5 - .../report_gen_bench_flat_data_model_test.go | 15 +- .../v2/reportgenerator/report_gen_impl.go | 3 - .../report_gen_impl_flat_data_model_test.go | 8 +- .../report_gen_memory_bench_test.go | 8 +- .../report_gen_view_based_test.go | 2 +- .../scheduler/v2/reportgenerator/singleton.go | 6 - central/reports/scheduler/v2/scheduler.go | 2 + .../reports/scheduler/v2/scheduler_impl.go | 24 +- .../scheduler/v2/scheduler_impl_test.go | 10 +- central/worker/main.go | 28 +- central/worker/report_listener.go | 240 ++++++++++++++++++ pkg/env/central_worker.go | 3 +- pkg/postgres/notify/channels.go | 1 + 18 files changed, 338 insertions(+), 53 deletions(-) create mode 100644 central/worker/report_listener.go diff --git a/central/notifier/service/service.go b/central/notifier/service/service.go index 1b1d93c9c9fc0..da712971b88ec 100644 --- a/central/notifier/service/service.go +++ b/central/notifier/service/service.go @@ -10,6 +10,7 @@ import ( "github.com/stackrox/rox/pkg/integrationhealth" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/notifier" + "github.com/stackrox/rox/pkg/postgres" ) var ( @@ -30,12 +31,14 @@ func New(storage datastore.DataStore, processor notifier.Processor, policyCleaner policycleaner.PolicyCleaner, reporter integrationhealth.Reporter, - cryptoKey string) Service { + cryptoKey string, + db postgres.DB) Service { return &serviceImpl{ storage: storage, processor: processor, policyCleaner: policyCleaner, reporter: reporter, cryptoKey: cryptoKey, + db: db, } } diff --git a/central/notifier/service/service_impl.go b/central/notifier/service/service_impl.go index 186832a06d008..e720e0b3a2f97 100644 --- a/central/notifier/service/service_impl.go +++ b/central/notifier/service/service_impl.go @@ -24,6 +24,8 @@ import ( "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/notifier" pkgNotifiers "github.com/stackrox/rox/pkg/notifiers" + "github.com/stackrox/rox/pkg/postgres" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/secrets" "google.golang.org/grpc" @@ -58,6 +60,7 @@ type serviceImpl struct { processor notifier.Processor reporter integrationhealth.Reporter cryptoKey string + db postgres.DB policyCleaner policycleaner.PolicyCleaner } @@ -164,6 +167,7 @@ func (s *serviceImpl) UpdateNotifier(ctx context.Context, request *v1.UpdateNoti return nil, err } s.processor.UpdateNotifier(ctx, notifier) + s.notifyWorker(ctx, request.GetNotifier().GetId()) return &v1.Empty{}, nil } @@ -195,6 +199,7 @@ func (s *serviceImpl) PostNotifier(ctx context.Context, request *storage.Notifie } request.Id = id s.processor.UpdateNotifier(ctx, notifier) + s.notifyWorker(ctx, id) if err = s.reporter.Register(request.GetId(), request.GetName(), storage.IntegrationHealth_NOTIFIER); err != nil { return nil, err @@ -266,12 +271,22 @@ func (s *serviceImpl) DeleteNotifier(ctx context.Context, request *v1.DeleteNoti } s.processor.RemoveNotifier(ctx, request.GetId()) + s.notifyWorker(ctx, request.GetId()) if err := s.reporter.RemoveIntegrationHealth(request.GetId()); err != nil { return nil, err } return &v1.Empty{}, nil } +func (s *serviceImpl) notifyWorker(ctx context.Context, notifierID string) { + if s.db == nil { + return + } + if err := pgNotify.Notify(ctx, s.db, pgNotify.NotifierChanged, notifierID); err != nil { + log.Errorf("Failed to notify worker of notifier change %s: %v", notifierID, err) + } +} + func (s *serviceImpl) reconcileUpdateNotifierRequest(ctx context.Context, updateRequest *v1.UpdateNotifierRequest) error { if updateRequest.GetUpdatePassword() { return nil diff --git a/central/notifier/service/singleton.go b/central/notifier/service/singleton.go index 8e8e9ce009172..a95e071cff2a5 100644 --- a/central/notifier/service/singleton.go +++ b/central/notifier/service/singleton.go @@ -1,6 +1,7 @@ package service import ( + "github.com/stackrox/rox/central/globaldb" "github.com/stackrox/rox/central/integrationhealth/reporter" "github.com/stackrox/rox/central/notifier/datastore" "github.com/stackrox/rox/central/notifier/policycleaner" @@ -33,6 +34,7 @@ func initialize() { policycleaner.Singleton(), reporter.Singleton(), cryptoKey, + globaldb.GetPostgres(), ) } diff --git a/central/reports/scheduler/v2/mocks/scheduler.go b/central/reports/scheduler/v2/mocks/scheduler.go index 5aef7d3f80dd5..1b1dbb2e5e93a 100644 --- a/central/reports/scheduler/v2/mocks/scheduler.go +++ b/central/reports/scheduler/v2/mocks/scheduler.go @@ -73,6 +73,20 @@ func (mr *MockSchedulerMockRecorder) CancelReportRequest(ctx, reportID any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelReportRequest", reflect.TypeOf((*MockScheduler)(nil).CancelReportRequest), ctx, reportID) } +// GetScheduledConfigIDs mocks base method. +func (m *MockScheduler) GetScheduledConfigIDs() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetScheduledConfigIDs") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetScheduledConfigIDs indicates an expected call of GetScheduledConfigIDs. +func (mr *MockSchedulerMockRecorder) GetScheduledConfigIDs() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScheduledConfigIDs", reflect.TypeOf((*MockScheduler)(nil).GetScheduledConfigIDs)) +} + // RemoveReportSchedule mocks base method. func (m *MockScheduler) RemoveReportSchedule(reportConfigID string) { m.ctrl.T.Helper() diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen.go b/central/reports/scheduler/v2/reportgenerator/report_gen.go index b5916603941b9..92879bcc33aa3 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen.go @@ -3,7 +3,6 @@ package reportgenerator import ( "context" - "github.com/graph-gophers/graphql-go" blobDS "github.com/stackrox/rox/central/blob/datastore" clusterDS "github.com/stackrox/rox/central/cluster/datastore" imageCVE2DS "github.com/stackrox/rox/central/cve/image/v2/datastore" @@ -39,7 +38,6 @@ func New( clusterDatastore clusterDS.DataStore, namespaceDatastore namespaceDS.DataStore, imageCVE2DataStore imageCVE2DS.DataStore, - schema *graphql.Schema, ) ReportGenerator { return newReportGeneratorImpl( db, @@ -52,7 +50,6 @@ func New( clusterDatastore, namespaceDatastore, imageCVE2DataStore, - schema, ) } @@ -67,7 +64,6 @@ func newReportGeneratorImpl( clusterDatastore clusterDS.DataStore, namespaceDatastore namespaceDS.DataStore, imageCVE2Datastore imageCVE2DS.DataStore, - schema *graphql.Schema, ) *reportGeneratorImpl { return &reportGeneratorImpl{ reportSnapshotStore: reportSnapshotStore, @@ -80,6 +76,5 @@ func newReportGeneratorImpl( imageCVE2Datastore: imageCVE2Datastore, blobStore: blobStore, db: db, - Schema: schema, } } diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_bench_flat_data_model_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_bench_flat_data_model_test.go index 5bbef7cfb9604..f0a1f356215d0 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_bench_flat_data_model_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_bench_flat_data_model_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/graph-gophers/graphql-go" clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" "github.com/stackrox/rox/central/graphql/resolvers" "github.com/stackrox/rox/central/graphql/resolvers/loaders" @@ -96,11 +95,10 @@ func (bts *FlatDataModelReportGeneratorBenchmarkTestSuite) setupTestSuite() { // set up tables bts.watchedImageDatastore = watchedImageDS.GetTestPostgresDataStore(bts.b, bts.testDB.DB) - var schema *graphql.Schema // TODO(ROX-30117): Remove conditional when FlattenImageData feature flag is removed. if features.FlattenImageData.Enabled() { imgV2DataStore := resolvers.CreateTestImageV2Datastore(bts.b, bts.testDB, mockCtrl) - bts.resolver, schema = resolvers.SetupTestResolver(bts.b, + bts.resolver, _ = resolvers.SetupTestResolver(bts.b, imagesView.NewImageView(bts.testDB.DB), imgV2DataStore, resolvers.CreateTestImageComponentV2Datastore(bts.b, bts.testDB, mockCtrl), @@ -110,7 +108,7 @@ func (bts *FlatDataModelReportGeneratorBenchmarkTestSuite) setupTestSuite() { ) } else { imageDataStore := resolvers.CreateTestImageDatastore(bts.b, bts.testDB, mockCtrl) - bts.resolver, schema = resolvers.SetupTestResolver(bts.b, + bts.resolver, _ = resolvers.SetupTestResolver(bts.b, imagesView.NewImageView(bts.testDB.DB), imageDataStore, resolvers.CreateTestImageComponentV2Datastore(bts.b, bts.testDB, mockCtrl), @@ -127,7 +125,7 @@ func (bts *FlatDataModelReportGeneratorBenchmarkTestSuite) setupTestSuite() { bts.reportGenerator = newReportGeneratorImpl(bts.testDB, nil, bts.resolver.DeploymentDataStore, bts.watchedImageDatastore, collectionQueryResolver, nil, nil, bts.clusterDatastore, - bts.namespaceDatastore, bts.resolver.ImageCVEV2DataStore, schema) + bts.namespaceDatastore, bts.resolver.ImageCVEV2DataStore) } func BenchmarkEntityScopeReportGenerator(b *testing.B) { @@ -136,11 +134,10 @@ func BenchmarkEntityScopeReportGenerator(b *testing.B) { testDB := pgtest.ForT(b) watchedImageDatastore := watchedImageDS.GetTestPostgresDataStore(b, testDB.DB) - var schema *graphql.Schema var resolver *resolvers.Resolver if features.FlattenImageData.Enabled() { imgV2DataStore := resolvers.CreateTestImageV2Datastore(b, testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(b, + resolver, _ = resolvers.SetupTestResolver(b, imagesView.NewImageView(testDB.DB), imgV2DataStore, resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), @@ -150,7 +147,7 @@ func BenchmarkEntityScopeReportGenerator(b *testing.B) { ) } else { imageDataStore := resolvers.CreateTestImageDatastore(b, testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(b, + resolver, _ = resolvers.SetupTestResolver(b, imagesView.NewImageView(testDB.DB), imageDataStore, resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), @@ -166,7 +163,7 @@ func BenchmarkEntityScopeReportGenerator(b *testing.B) { reportGenerator := newReportGeneratorImpl(testDB, nil, resolver.DeploymentDataStore, watchedImageDatastore, nil, nil, nil, clusterDatastore, - nsDatastore, resolver.ImageCVEV2DataStore, schema) + nsDatastore, resolver.ImageCVEV2DataStore) clusters := []*storage.Cluster{ {Id: uuid.NewV4().String(), Name: "c1"}, diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index ce0a92bbd860c..47f6a4f54525b 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -9,7 +9,6 @@ import ( "slices" "time" - "github.com/graph-gophers/graphql-go" "github.com/pkg/errors" blobDS "github.com/stackrox/rox/central/blob/datastore" clusterDS "github.com/stackrox/rox/central/cluster/datastore" @@ -80,8 +79,6 @@ type reportGeneratorImpl struct { namespaceDatastore namespaceDS.DataStore imageCVE2Datastore imageCVE2DS.DataStore db postgres.DB - - Schema *graphql.Schema } type ImageCVEInterface interface { diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl_flat_data_model_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl_flat_data_model_test.go index 2ddad1551afb2..b45f5ff05d5d3 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl_flat_data_model_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl_flat_data_model_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/graph-gophers/graphql-go" blobDS "github.com/stackrox/rox/central/blob/datastore" clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" "github.com/stackrox/rox/central/graphql/resolvers" @@ -73,10 +72,9 @@ func (s *NewDataModelEnhancedReportingTestSuite) SetupSuite() { // TODO(ROX-30117): Remove conditional when FlattenImageData feature flag is removed. var resolver *resolvers.Resolver - var schema *graphql.Schema if features.FlattenImageData.Enabled() { imgV2DataStore := resolvers.CreateTestImageV2Datastore(s.T(), s.testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(s.T(), + resolver, _ = resolvers.SetupTestResolver(s.T(), imagesView.NewImageView(s.testDB.DB), imgV2DataStore, resolvers.CreateTestImageComponentV2Datastore(s.T(), s.testDB, mockCtrl), @@ -86,7 +84,7 @@ func (s *NewDataModelEnhancedReportingTestSuite) SetupSuite() { ) } else { imageDataStore := resolvers.CreateTestImageDatastore(s.T(), s.testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(s.T(), + resolver, _ = resolvers.SetupTestResolver(s.T(), imagesView.NewImageView(s.testDB.DB), imageDataStore, resolvers.CreateTestImageComponentV2Datastore(s.T(), s.testDB, mockCtrl), @@ -165,7 +163,7 @@ func (s *NewDataModelEnhancedReportingTestSuite) SetupSuite() { s.reportGenerator = newReportGeneratorImpl(s.testDB, nil, resolver.DeploymentDataStore, s.watchedImageDatastore, collectionQueryResolver, nil, blobStore, s.clusterDatastore, - s.namespaceDatastore, resolver.ImageCVEV2DataStore, schema) + s.namespaceDatastore, resolver.ImageCVEV2DataStore) } func (s *NewDataModelEnhancedReportingTestSuite) upsertManyWatchedImages(images []*storage.Image) { for _, img := range images { diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go index 8f6bba1c35605..3ddb8a21990c6 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_memory_bench_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/graph-gophers/graphql-go" blobDS "github.com/stackrox/rox/central/blob/datastore" clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" "github.com/stackrox/rox/central/graphql/resolvers" @@ -53,10 +52,9 @@ func setupMemoryBench(b *testing.B, numNamespacesPerCluster, numDeploymentsPerNa watchedImageDatastore := watchedImageDS.GetTestPostgresDataStore(b, testDB.DB) var resolver *resolvers.Resolver - var schema *graphql.Schema if features.FlattenImageData.Enabled() { imgV2DataStore := resolvers.CreateTestImageV2Datastore(b, testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(b, + resolver, _ = resolvers.SetupTestResolver(b, imagesView.NewImageView(testDB.DB), imgV2DataStore, resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), @@ -66,7 +64,7 @@ func setupMemoryBench(b *testing.B, numNamespacesPerCluster, numDeploymentsPerNa ) } else { imageDataStore := resolvers.CreateTestImageDatastore(b, testDB, mockCtrl) - resolver, schema = resolvers.SetupTestResolver(b, + resolver, _ = resolvers.SetupTestResolver(b, imagesView.NewImageView(testDB.DB), imageDataStore, resolvers.CreateTestImageComponentV2Datastore(b, testDB, mockCtrl), @@ -89,7 +87,7 @@ func setupMemoryBench(b *testing.B, numNamespacesPerCluster, numDeploymentsPerNa rg := newReportGeneratorImpl(testDB, reportSnapshotStore, resolver.DeploymentDataStore, watchedImageDatastore, collectionQueryResolver, nil, blobStore, clusterDatastore, - nsDatastore, resolver.ImageCVEV2DataStore, schema) + nsDatastore, resolver.ImageCVEV2DataStore) // pgtest limits all pools to pool_max_conns=1 which is too restrictive for // report generation. Use a shared pool with enough connections. diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_view_based_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_view_based_test.go index da2701f4d16e3..ffee70a669748 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_view_based_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_view_based_test.go @@ -91,7 +91,7 @@ func (s *ViewBasedReportingTestSuite) SetupTest() { s.reportGenerator = newReportGeneratorImpl(s.testDB, nil, s.resolver.DeploymentDataStore, s.watchedImageDatastore, nil, nil, s.blobStore, s.clusterDatastore, - s.namespaceDatastore, s.resolver.ImageCVEV2DataStore, nil) + s.namespaceDatastore, s.resolver.ImageCVEV2DataStore) } func (s *ViewBasedReportingTestSuite) TearDownTest() { diff --git a/central/reports/scheduler/v2/reportgenerator/singleton.go b/central/reports/scheduler/v2/reportgenerator/singleton.go index 8584149ead221..3bb242c54d343 100644 --- a/central/reports/scheduler/v2/reportgenerator/singleton.go +++ b/central/reports/scheduler/v2/reportgenerator/singleton.go @@ -1,20 +1,17 @@ package reportgenerator import ( - "github.com/graph-gophers/graphql-go" blobDS "github.com/stackrox/rox/central/blob/datastore" clusterDS "github.com/stackrox/rox/central/cluster/datastore" imageCVE2DS "github.com/stackrox/rox/central/cve/image/v2/datastore" deploymentDS "github.com/stackrox/rox/central/deployment/datastore" "github.com/stackrox/rox/central/globaldb" - "github.com/stackrox/rox/central/graphql/resolvers" namespaceDS "github.com/stackrox/rox/central/namespace/datastore" notifierProcessor "github.com/stackrox/rox/central/notifier/processor" reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" watchedImageDS "github.com/stackrox/rox/central/watchedimage/datastore" "github.com/stackrox/rox/pkg/sync" - "github.com/stackrox/rox/pkg/utils" ) var ( @@ -24,8 +21,6 @@ var ( func initialize() { _, collectionQueryRes := collectionDS.Singleton() - schema, err := graphql.ParseSchema(resolvers.Schema(), resolvers.New()) - utils.CrashOnError(err) rg = New(globaldb.GetPostgres(), reportSnapshotDS.Singleton(), deploymentDS.Singleton(), @@ -36,7 +31,6 @@ func initialize() { clusterDS.Singleton(), namespaceDS.Singleton(), imageCVE2DS.Singleton(), - schema, ) } diff --git a/central/reports/scheduler/v2/scheduler.go b/central/reports/scheduler/v2/scheduler.go index 266cb90cba578..682a264747d6a 100644 --- a/central/reports/scheduler/v2/scheduler.go +++ b/central/reports/scheduler/v2/scheduler.go @@ -16,6 +16,8 @@ type Scheduler interface { UpsertReportSchedule(reportConfig *storage.ReportConfiguration) error // RemoveReportSchedule removes the given report configuration from scheduled execution. RemoveReportSchedule(reportConfigID string) + // GetScheduledConfigIDs returns the IDs of all report configurations that have active schedules. + GetScheduledConfigIDs() []string // CanSubmitReportRequest returns true if the given user can submit an on-demand report request for the given report configuration. CanSubmitReportRequest(user *storage.SlimUser, reportConfig *storage.ReportConfiguration) (bool, error) diff --git a/central/reports/scheduler/v2/scheduler_impl.go b/central/reports/scheduler/v2/scheduler_impl.go index d6166f13a9097..b418608d39d40 100644 --- a/central/reports/scheduler/v2/scheduler_impl.go +++ b/central/reports/scheduler/v2/scheduler_impl.go @@ -5,9 +5,7 @@ import ( "sync/atomic" "time" - "github.com/graph-gophers/graphql-go" "github.com/pkg/errors" - "github.com/stackrox/rox/central/graphql/resolvers" notifierDS "github.com/stackrox/rox/central/notifier/datastore" "github.com/stackrox/rox/central/reports/common" reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" @@ -68,7 +66,6 @@ type scheduler struct { // request is enqueued or when a report completes. Reset when no runnable report // is found across any queue. readyForReports concurrency.Signal - Schema *graphql.Schema /* Concurrency and synchronization related fields */ // isStarted will make sure only one scheduling routine runs for an instance of scheduler @@ -98,19 +95,14 @@ func New(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotStore rep cronScheduler := cron.New() cronScheduler.Start() - ourSchema, err := graphql.ParseSchema(resolvers.Schema(), resolvers.New()) - if err != nil { - panic(err) - } return newSchedulerImpl(reportConfigDatastore, reportSnapshotStore, collectionDatastore, notifierDatastore, - imageReportGenerator, nodeReportGenerator, validator, cronScheduler, ourSchema) + imageReportGenerator, nodeReportGenerator, validator, cronScheduler) } func newSchedulerImpl(reportConfigDatastore reportConfigDS.DataStore, reportSnapshotStore reportSnapshotDS.DataStore, collectionDatastore collectionDS.DataStore, notifierDatastore notifierDS.DataStore, imageReportGenerator reportGen.ReportGenerator, nodeReportGenerator reportGen.ReportGenerator, - validator *validation.Validator, cronScheduler *cron.Cron, - schema *graphql.Schema) *scheduler { + validator *validation.Validator, cronScheduler *cron.Cron) *scheduler { imageQueue := reportqueue.New() queues := []queueGeneratorBinding{ @@ -137,7 +129,6 @@ func newSchedulerImpl(reportConfigDatastore reportConfigDS.DataStore, reportSnap nextQueueIdx: 0, queueByType: queueByType, readyForReports: concurrency.NewSignal(), - Schema: schema, stopper: concurrency.NewStopper(), cron: cronScheduler, concurrencySema: semaphore.NewWeighted(int64(env.ReportExecutionMaxConcurrency.IntegerSetting())), @@ -283,6 +274,17 @@ func (s *scheduler) RemoveReportSchedule(reportConfigID string) { } } +func (s *scheduler) GetScheduledConfigIDs() []string { + s.cronJobsLock.Lock() + defer s.cronJobsLock.Unlock() + + ids := make([]string, 0, len(s.reportConfigToEntryIDs)) + for id := range s.reportConfigToEntryIDs { + ids = append(ids, id) + } + return ids +} + /* Functions to add/remove report jobs from queue */ // CancelReportRequest cancels a report request. If the report is waiting in queue, it is removed diff --git a/central/reports/scheduler/v2/scheduler_impl_test.go b/central/reports/scheduler/v2/scheduler_impl_test.go index ea1df02cc3646..5a61a6ec102ee 100644 --- a/central/reports/scheduler/v2/scheduler_impl_test.go +++ b/central/reports/scheduler/v2/scheduler_impl_test.go @@ -154,7 +154,7 @@ func TestQueueScheduledReportsSkipsEmptyResourceScope(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(mockReportConfigDS, nil, nil, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(mockReportConfigDS, nil, nil, nil, nil, nil, nil, cronScheduler) s.queueScheduledReports() // Only the two valid configs should have been scheduled @@ -173,7 +173,7 @@ func TestCancelReportRequestCancelsRunningReport(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, nil, cronScheduler) imageQueue := s.queues[0].queue started := make(chan struct{}) @@ -221,7 +221,7 @@ func TestCancelReportRequestReturnsFalseForUnknownReport(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, nil, nil, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, nil, nil, nil, nil, nil, nil, cronScheduler) cancelled, err := s.CancelReportRequest(context.Background(), "nonexistent-id") assert.NoError(t, err) @@ -290,7 +290,7 @@ func TestQueuePendingReports(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(mockReportConfigDS, mockSnapshotStore, mockCollectionDS, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(mockReportConfigDS, mockSnapshotStore, mockCollectionDS, nil, nil, nil, nil, cronScheduler) s.isStarted.Store(true) s.queuePendingReports() @@ -305,7 +305,7 @@ func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { cronScheduler.Start() defer cronScheduler.Stop() - s := newSchedulerImpl(nil, mockSnapshotStore, nil, nil, nil, nil, nil, cronScheduler, nil) + s := newSchedulerImpl(nil, mockSnapshotStore, nil, nil, nil, nil, nil, cronScheduler) imageQueue := s.queues[0].queue req := &reportGen.ReportRequest{ diff --git a/central/worker/main.go b/central/worker/main.go index ab769c1af45f8..0493c0d6355ad 100644 --- a/central/worker/main.go +++ b/central/worker/main.go @@ -11,7 +11,14 @@ import ( "time" "github.com/stackrox/rox/central/globaldb" + notifierDS "github.com/stackrox/rox/central/notifier/datastore" + notifierProcessor "github.com/stackrox/rox/central/notifier/processor" + _ "github.com/stackrox/rox/central/notifiers/all" "github.com/stackrox/rox/central/pruning" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + vulnReportV2Scheduler "github.com/stackrox/rox/central/reports/scheduler/v2" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" "github.com/stackrox/rox/central/version" vStore "github.com/stackrox/rox/central/version/store" "github.com/stackrox/rox/pkg/dblock" @@ -37,7 +44,8 @@ func main() { log.Infof("Starting central-worker") - ctx := context.Background() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() poolVal := workerPoolSize.IntegerSetting() if poolVal < 1 || poolVal > math.MaxInt32 { @@ -56,6 +64,23 @@ func main() { pruning.Singleton().Start() log.Infof("Pruning GC started") + scheduler := vulnReportV2Scheduler.Singleton() + scheduler.Start(globaldb.GetPostgres()) + log.Infof("Vulnerability report scheduler started") + + collectionDatastore, _ := collectionDS.Singleton() + rl := newReportListener( + globaldb.GetPostgres(), + scheduler, + reportConfigDS.Singleton(), + reportSnapshotDS.Singleton(), + collectionDatastore, + notifierDS.Singleton(), + notifierProcessor.Singleton(), + ) + rl.start(ctx) + log.Infof("Report LISTEN/NOTIFY listener started") + log.Infof("central-worker is ready") waitForTerminationSignal() @@ -63,6 +88,7 @@ func main() { log.Infof("central-worker shutting down") pruning.Singleton().Stop() + scheduler.Stop() globaldb.Close() } diff --git a/central/worker/report_listener.go b/central/worker/report_listener.go new file mode 100644 index 0000000000000..2b2310a7a6359 --- /dev/null +++ b/central/worker/report_listener.go @@ -0,0 +1,240 @@ +package main + +import ( + "context" + "time" + + notifierDS "github.com/stackrox/rox/central/notifier/datastore" + "github.com/stackrox/rox/central/reports/common" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + reportGen "github.com/stackrox/rox/central/reports/scheduler/v2/reportgenerator" + reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/notifier" + pkgNotifiers "github.com/stackrox/rox/pkg/notifiers" + "github.com/stackrox/rox/pkg/postgres" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/set" + "github.com/stackrox/rox/pkg/sync" +) + +var listenerCtx = sac.WithAllAccess(context.Background()) + +type reportListener struct { + db postgres.DB + scheduler schedulerV2.Scheduler + reportConfigStore reportConfigDS.DataStore + snapshotStore reportSnapshotDS.DataStore + collectionDatastore collectionDS.DataStore + notifierStore notifierDS.DataStore + notifierProcessor notifier.Processor + knownMu sync.Mutex + knownReportIDs set.StringSet +} + +func newReportListener( + db postgres.DB, + scheduler schedulerV2.Scheduler, + reportConfigStore reportConfigDS.DataStore, + snapshotStore reportSnapshotDS.DataStore, + collectionDatastore collectionDS.DataStore, + notifierStore notifierDS.DataStore, + notifierProcessor notifier.Processor, +) *reportListener { + return &reportListener{ + db: db, + scheduler: scheduler, + reportConfigStore: reportConfigStore, + snapshotStore: snapshotStore, + collectionDatastore: collectionDatastore, + notifierStore: notifierStore, + notifierProcessor: notifierProcessor, + knownReportIDs: set.NewStringSet(), + } +} + +func (r *reportListener) start(ctx context.Context) { + listener := pgNotify.NewListener(r.db, r.handleNotification, + pgNotify.NotifierChanged, + pgNotify.ReportConfigChanged, + pgNotify.ReportRequestSubmitted, + pgNotify.ReportRequestCancelled, + ) + go listener.Listen(ctx) + go r.periodicResync(ctx) +} + +func (r *reportListener) handleNotification(channel, payload string) { + switch channel { + case pgNotify.NotifierChanged: + r.handleNotifierChanged(payload) + case pgNotify.ReportConfigChanged: + r.handleConfigChanged(payload) + case pgNotify.ReportRequestSubmitted: + r.handleRequestSubmitted(payload) + case pgNotify.ReportRequestCancelled: + r.handleRequestCancelled(payload) + } +} + +func (r *reportListener) handleNotifierChanged(notifierID string) { + protoNotifier, exists, err := r.notifierStore.GetNotifier(listenerCtx, notifierID) + if err != nil { + log.Errorf("Failed to load notifier %s: %v", notifierID, err) + return + } + if !exists { + r.notifierProcessor.RemoveNotifier(listenerCtx, notifierID) + return + } + n, err := pkgNotifiers.CreateNotifier(protoNotifier) + if err != nil { + log.Errorf("Failed to create notifier %s: %v", notifierID, err) + return + } + r.notifierProcessor.UpdateNotifier(listenerCtx, n) +} + +func (r *reportListener) handleConfigChanged(configID string) { + config, exists, err := r.reportConfigStore.GetReportConfiguration(listenerCtx, configID) + if err != nil { + log.Errorf("Failed to load report config %s: %v", configID, err) + return + } + if !exists { + r.scheduler.RemoveReportSchedule(configID) + return + } + if config.GetSchedule() != nil { + if err := r.scheduler.UpsertReportSchedule(config); err != nil { + log.Errorf("Failed to upsert schedule for config %s: %v", configID, err) + } + } else { + r.scheduler.RemoveReportSchedule(configID) + } +} + +func (r *reportListener) handleRequestSubmitted(snapshotID string) { + snap, exists, err := r.snapshotStore.Get(listenerCtx, snapshotID) + if err != nil || !exists { + log.Errorf("Failed to load report snapshot %s: exists=%v err=%v", snapshotID, exists, err) + return + } + if snap.GetReportStatus().GetRunState() != storage.ReportStatus_WAITING { + return + } + + var collection *storage.ResourceCollection + if snap.GetCollection().GetId() != "" { + collection, exists, err = r.collectionDatastore.Get(listenerCtx, snap.GetCollection().GetId()) + if err != nil || !exists { + log.Errorf("Failed to load collection for snapshot %s: %v", snapshotID, err) + return + } + } + + req := &reportGen.ReportRequest{ + ReportSnapshot: snap, + Collection: collection, + } + r.submitIfNew(snapshotID, req) +} + +func (r *reportListener) handleRequestCancelled(snapshotID string) { + if _, err := r.scheduler.CancelReportRequest(listenerCtx, snapshotID); err != nil { + log.Errorf("Failed to cancel report request %s: %v", snapshotID, err) + } +} + +func (r *reportListener) periodicResync(ctx context.Context) { + ticker := time.NewTicker(time.Duration(env.CentralWorkerResyncIntervalMins.IntegerSetting()) * time.Minute) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + r.resyncConfigs() + r.resyncPendingRequests() + } + } +} + +func (r *reportListener) resyncConfigs() { + query := search.NewQueryBuilder(). + AddExactMatches(search.ReportType, + storage.ReportConfiguration_VULNERABILITY.String(), + storage.ReportConfiguration_NODE_VULNERABILITY.String(), + ). + ProtoQuery() + configs, err := r.reportConfigStore.GetReportConfigurations(listenerCtx, query) + if err != nil { + log.Errorf("Error resyncing report configs: %v", err) + return + } + + activeConfigIDs := set.NewStringSet() + for _, rc := range configs { + if rc.GetSchedule() != nil && common.HasValidResourceScope(rc.GetResourceScope()) { + activeConfigIDs.Add(rc.GetId()) + if err := r.scheduler.UpsertReportSchedule(rc); err != nil { + log.Errorf("Error resyncing schedule for config %s: %v", rc.GetId(), err) + } + } + } + + for _, id := range r.scheduler.GetScheduledConfigIDs() { + if !activeConfigIDs.Contains(id) { + r.scheduler.RemoveReportSchedule(id) + } + } +} + +func (r *reportListener) resyncPendingRequests() { + query := search.NewQueryBuilder(). + AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String()). + WithPagination(search.NewPagination().AddSortOption(search.NewSortOption(search.ReportQueuedTime))). + ProtoQuery() + snapshots, err := r.snapshotStore.SearchReportSnapshots(listenerCtx, query) + if err != nil { + log.Errorf("Error resyncing pending report requests: %v", err) + return + } + + for _, snap := range snapshots { + var collection *storage.ResourceCollection + if collID := snap.GetCollection().GetId(); collID != "" { + var exists bool + collection, exists, err = r.collectionDatastore.Get(listenerCtx, collID) + if err != nil || !exists { + log.Errorf("Error loading collection for pending snapshot %s: %v", snap.GetReportId(), err) + continue + } + } + + req := &reportGen.ReportRequest{ + ReportSnapshot: snap, + Collection: collection, + } + r.submitIfNew(snap.GetReportId(), req) + } +} + +func (r *reportListener) submitIfNew(reportID string, req *reportGen.ReportRequest) { + r.knownMu.Lock() + defer r.knownMu.Unlock() + + if r.knownReportIDs.Contains(reportID) { + return + } + if _, err := r.scheduler.SubmitReportRequest(listenerCtx, req, true); err != nil { + log.Errorf("Failed to submit report request %s: %v", reportID, err) + return + } + r.knownReportIDs.Add(reportID) +} diff --git a/pkg/env/central_worker.go b/pkg/env/central_worker.go index 0ae359772f14e..a46f152ec891b 100644 --- a/pkg/env/central_worker.go +++ b/pkg/env/central_worker.go @@ -1,5 +1,6 @@ package env var ( - CentralWorkerEnabled = RegisterBooleanSetting("ROX_CENTRAL_WORKER_ENABLED", false) + CentralWorkerEnabled = RegisterBooleanSetting("ROX_CENTRAL_WORKER_ENABLED", false) + CentralWorkerResyncIntervalMins = RegisterIntegerSetting("ROX_WORKER_RESYNC_INTERVAL_MINUTES", 5).WithMinimum(1) ) diff --git a/pkg/postgres/notify/channels.go b/pkg/postgres/notify/channels.go index 7401e1e3a0797..bd3b19acae705 100644 --- a/pkg/postgres/notify/channels.go +++ b/pkg/postgres/notify/channels.go @@ -1,6 +1,7 @@ package notify const ( + NotifierChanged = "notifier_changed" ReportConfigChanged = "report_config_changed" ReportRequestSubmitted = "report_request_submitted" ReportRequestCancelled = "report_request_cancelled" From 56c29f1af06543656a22782e4cd184dc9cbfe02a Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 4 Sep 2026 19:46:23 +0200 Subject: [PATCH 205/638] ROX-36271: Port fake VM workloads to VSOCK pull (using scraper poll) (#22503) --- pkg/fixtures/vmindexreport/generator.go | 9 - scale/workloads/sample.yaml | 2 - scale/workloads/virtual-machines-small.yaml | 9 +- scale/workloads/virtual-machines.yaml | 9 +- sensor/common/virtualmachine/ACK_MODEL.md | 10 +- sensor/common/virtualmachine/index/handler.go | 36 -- .../virtualmachine/index/handler_impl.go | 295 ---------------- .../virtualmachine/index/handler_impl_test.go | 317 ------------------ .../virtualmachine/index/mocks/handler.go | 221 ------------ .../common/virtualmachine/metrics/metrics.go | 54 +-- .../virtualmachine/vmscraper/scraper.go | 105 +++++- .../virtualmachine/vmscraper/scraper_test.go | 260 ++++++-------- .../vmscraper/spreading_test.go | 28 +- .../virtualmachine/vmscraper/stats_test.go | 2 +- sensor/kubernetes/fake/common.go | 9 - sensor/kubernetes/fake/fake.go | 126 +------ sensor/kubernetes/fake/virtualmachines.go | 156 ++------- .../kubernetes/fake/virtualmachines_test.go | 145 ++++---- sensor/kubernetes/fake/vmagent/client.go | 68 ++++ sensor/kubernetes/fake/vmagent/client_test.go | 128 +++++++ sensor/kubernetes/fake/vmagent/dialer.go | 35 ++ sensor/kubernetes/fake/vmagent/dialer_test.go | 39 +++ sensor/kubernetes/fake/vmagent/doc.go | 4 + sensor/kubernetes/fake/workload.go | 12 +- .../store/virtual_machine_store.go | 65 +--- .../store/virtual_machine_store_test.go | 81 ----- sensor/kubernetes/sensor/sensor.go | 53 ++- tools/local-sensor/main.go | 4 +- 28 files changed, 671 insertions(+), 1611 deletions(-) delete mode 100644 sensor/common/virtualmachine/index/handler.go delete mode 100644 sensor/common/virtualmachine/index/handler_impl.go delete mode 100644 sensor/common/virtualmachine/index/handler_impl_test.go delete mode 100644 sensor/common/virtualmachine/index/mocks/handler.go create mode 100644 sensor/kubernetes/fake/vmagent/client.go create mode 100644 sensor/kubernetes/fake/vmagent/client_test.go create mode 100644 sensor/kubernetes/fake/vmagent/dialer.go create mode 100644 sensor/kubernetes/fake/vmagent/dialer_test.go create mode 100644 sensor/kubernetes/fake/vmagent/doc.go diff --git a/pkg/fixtures/vmindexreport/generator.go b/pkg/fixtures/vmindexreport/generator.go index 0be8e171d8fa9..ca2fb65136cbe 100644 --- a/pkg/fixtures/vmindexreport/generator.go +++ b/pkg/fixtures/vmindexreport/generator.go @@ -10,7 +10,6 @@ import ( "strings" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" - v1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" ) const ( @@ -165,14 +164,6 @@ func NewGeneratorWithSeed(numPackages int, seed int64) *Generator { } } -// GenerateV1IndexReport creates a fake v1.IndexReport (used by Sensor for sending to Central). -func (g *Generator) GenerateV1IndexReport(vsockCID uint32) *v1.IndexReport { - return &v1.IndexReport{ - VsockCid: fmt.Sprintf("%d", vsockCID), - IndexV4: g.GenerateV4IndexReport(), - } -} - // GenerateV4IndexReport creates a fake v4.IndexReport (used by Scanner V4). func (g *Generator) GenerateV4IndexReport() *v4.IndexReport { return &v4.IndexReport{ diff --git a/scale/workloads/sample.yaml b/scale/workloads/sample.yaml index 05c0acecd67ce..a63cfc52c23d6 100755 --- a/scale/workloads/sample.yaml +++ b/scale/workloads/sample.yaml @@ -78,8 +78,6 @@ virtualMachineWorkload: reportInterval: 60s # Number of fake packages to include in each index report numPackages: 100 - # Delay before the first index report for each VM (±20% max deviation applied) - initialReportDelay: 10s # File activity workload generates synthetic file access events (requires ROX_SENSITIVE_FILE_ACTIVITY=true) fileActivityWorkload: diff --git a/scale/workloads/virtual-machines-small.yaml b/scale/workloads/virtual-machines-small.yaml index 4fe7e14c99f25..f2b6af1912b93 100644 --- a/scale/workloads/virtual-machines-small.yaml +++ b/scale/workloads/virtual-machines-small.yaml @@ -4,10 +4,10 @@ # # The unified VirtualMachineWorkload config handles both: # 1. VM/VMI informer events (always generated when poolSize > 0) -# 2. VM index reports (only generated when reportInterval > 0) +# 2. VM index reports (when reportInterval > 0): VMScraper polls an in-process +# fake agent. Cadence is the scraper poll interval, not reportInterval. # -# Index reports are tied to VM lifecycle: reports are only sent while the VM is "alive". -# You can run just the informers (for testing k8s events) or informers + reports (full flow). +# You can run just the informers (reportInterval: 0) or informers + reports. # # Usage: # ROX_VIRTUAL_MACHINES=true go run tools/local-sensor/main.go -no-mem-prof -no-cpu-prof -with-metrics -with-fakeworkload scale/workloads/virtual-machines.yaml @@ -29,9 +29,8 @@ virtualMachineWorkload: updateInterval: 5m lifecycleDuration: 2h numLifecycles: 0 - reportInterval: 5m # ±5% max deviation applied + reportInterval: 5m # > 0 enables fake agent reports; scrape cadence is independent numPackages: 700 - initialReportDelay: 30s # ±20% max deviation applied deploymentWorkload: - deploymentType: Deployment diff --git a/scale/workloads/virtual-machines.yaml b/scale/workloads/virtual-machines.yaml index cff61df29bac2..8ad4b3e717b7f 100644 --- a/scale/workloads/virtual-machines.yaml +++ b/scale/workloads/virtual-machines.yaml @@ -4,10 +4,10 @@ # # The unified VirtualMachineWorkload config handles both: # 1. VM/VMI informer events (always generated when poolSize > 0) -# 2. VM index reports (only generated when reportInterval > 0) +# 2. VM index reports (when reportInterval > 0): VMScraper polls an in-process +# fake agent. Cadence is the scraper poll interval, not reportInterval. # -# Index reports are tied to VM lifecycle: reports are only sent while the VM is "alive". -# You can run just the informers (for testing k8s events) or informers + reports (full flow). +# You can run just the informers (reportInterval: 0) or informers + reports. # # Usage: # ROX_VIRTUAL_MACHINES=true go run tools/local-sensor/main.go -no-mem-prof -no-cpu-prof -with-metrics -with-fakeworkload scale/workloads/virtual-machines.yaml @@ -29,9 +29,8 @@ virtualMachineWorkload: updateInterval: 5m lifecycleDuration: 2h numLifecycles: 0 - reportInterval: 5m # ±5% max deviation applied + reportInterval: 5m # > 0 enables fake agent reports; scrape cadence is independent numPackages: 700 - initialReportDelay: 30s # ±20% max deviation applied deploymentWorkload: - deploymentType: Deployment diff --git a/sensor/common/virtualmachine/ACK_MODEL.md b/sensor/common/virtualmachine/ACK_MODEL.md index bdb638f84fce6..11b71adc01bed 100644 --- a/sensor/common/virtualmachine/ACK_MODEL.md +++ b/sensor/common/virtualmachine/ACK_MODEL.md @@ -2,7 +2,7 @@ VM index reports flow directly from Sensor to Central; Compliance is not involved (see `compliance/ACK_MODEL.md` for the separate node-scanning ACK model, which does go through Compliance). -`VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central through `vmIndex.Handler`, which wraps the Scanner V4 payload as a `v1.IndexReport`. +`VMScraper` pulls reports from each running VM's roxagent over VSOCK and forwards successful reports to Central on `ResponsesC`, wrapping the Scanner V4 payload as a `v1.IndexReport`. A successful forward already returns the VM to poll cadence. Central's ACK is counted (`IndexReportAcksReceived`) and otherwise ignored. A NACK clears cached `report_token` and applies the shared retry backoff so the next attempt pulls a full report. There is no payload cache and no ACK timeout. @@ -12,19 +12,17 @@ A short tick (`ROX_VIRTUAL_MACHINES_SCRAPER_TICK_INTERVAL`, default 10s) walks t Retryable pull failures and NACKs share one exponential backoff (`ROX_VIRTUAL_MACHINES_SCRAPER_INITIAL_BACKOFF`, default 10s, doubled each time, capped at `min(pollInterval, 30m)`). -Central's `SensorACK` is delivered to Sensor components whose `Accepts()` matches. `Handler` advertises `SensorACKSupport` so Central sends these ACKs; only `VMScraper` accepts `SensorACK_VM_INDEX_REPORT`. +Central's `SensorACK` is delivered to Sensor components whose `Accepts()` matches. `VMScraper` advertises `SensorACKSupport` so Central sends these ACKs, and it is the only component that accepts `SensorACK_VM_INDEX_REPORT`. ```mermaid sequenceDiagram participant A as roxagent inside VM participant Sc as Sensor VMScraper - participant H as Sensor Handler participant X as Central VM pipeline Sc->>A: GetReport (VSOCK, when the VM is due) A-->>Sc: index report - Sc->>H: Send(report) - H->>X: SensorEvent(vmID, index report) + Sc->>X: SensorEvent(vmID, index report) X-->>Sc: SensorACK(vmID:vsockCID, ACK|NACK, reason) alt ACK @@ -43,4 +41,4 @@ Using only the vsock CID is unsafe because a CID can be reused by a different VM The pair is not a per-report unique ID: multiple in-flight reports from the same VM with the same CID are still not fully distinguishable, so a stale ACK can match the latest entry instead of the one it was actually for. -A NACK can also lose a race with the post-`Send` commit of `lastToken`, in which case the next poll may still see an "unchanged" delta. +A NACK can also lose a race with the post-forward commit of `lastToken`, in which case the next poll may still see an "unchanged" delta. diff --git a/sensor/common/virtualmachine/index/handler.go b/sensor/common/virtualmachine/index/handler.go deleted file mode 100644 index 1a7126273e119..0000000000000 --- a/sensor/common/virtualmachine/index/handler.go +++ /dev/null @@ -1,36 +0,0 @@ -package index - -import ( - "context" - - v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" - "github.com/stackrox/rox/pkg/concurrency" - "github.com/stackrox/rox/pkg/sync" - "github.com/stackrox/rox/sensor/common" - "github.com/stackrox/rox/sensor/common/virtualmachine" -) - -// Handler provides functionality to send virtual machine index reports to Central. -type Handler interface { - common.SensorComponent - - Send(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error -} - -// VirtualMachineStore interface to the VirtualMachine store -// -//go:generate mockgen-wrapper -type VirtualMachineStore interface { - Get(id virtualmachine.VMID) *virtualmachine.Info - GetFromCID(cid uint32) *virtualmachine.Info -} - -// NewHandler returns the virtual machine component for Sensor to use. -func NewHandler(store VirtualMachineStore) Handler { - return &handlerImpl{ - centralReady: concurrency.NewSignal(), - lock: &sync.RWMutex{}, - stopper: concurrency.NewStopper(), - store: store, - } -} diff --git a/sensor/common/virtualmachine/index/handler_impl.go b/sensor/common/virtualmachine/index/handler_impl.go deleted file mode 100644 index 73fd536ea0b14..0000000000000 --- a/sensor/common/virtualmachine/index/handler_impl.go +++ /dev/null @@ -1,295 +0,0 @@ -package index - -import ( - "context" - "strconv" - "time" - - "github.com/pkg/errors" - "github.com/stackrox/rox/generated/internalapi/central" - v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" - v1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" - "github.com/stackrox/rox/pkg/centralsensor" - "github.com/stackrox/rox/pkg/concurrency" - "github.com/stackrox/rox/pkg/env" - "github.com/stackrox/rox/pkg/errox" - "github.com/stackrox/rox/pkg/logging" - "github.com/stackrox/rox/pkg/sync" - "github.com/stackrox/rox/pkg/utils" - "github.com/stackrox/rox/sensor/common" - "github.com/stackrox/rox/sensor/common/centralcaps" - "github.com/stackrox/rox/sensor/common/message" - "github.com/stackrox/rox/sensor/common/virtualmachine" - "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" -) - -var ( - log = logging.LoggerForModule() - - errCapabilityNotSupported = errors.New("Central does not have virtual machine capability") - errCentralNotReachable = errors.New("Central is not reachable") - errInputChanClosed = errors.New("channel receiving virtual machines is closed") - errStartMoreThanOnce = errors.New("unable to start the handler more than once") - errVirtualMachineNotFound = errors.New("virtual machine not found") -) - -type handlerImpl struct { - centralReady concurrency.Signal - // lock prevents the race condition between Start() [writer] and ResponsesC(), Send() [reader]. - lock *sync.RWMutex - stopper concurrency.Stopper - toCentral <-chan *message.ExpiringMessage - indexReports chan *v1.IndexReport - store VirtualMachineStore -} - -func (h *handlerImpl) Capabilities() []centralsensor.SensorCapability { - return []centralsensor.SensorCapability{centralsensor.SensorACKSupport} -} - -func (h *handlerImpl) Send(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error { - if vm == nil { - return errors.New("virtual machine info is nil") - } - var cidStr string - if vm.VSOCKCID != nil { - cidStr = strconv.FormatUint(uint64(*vm.VSOCKCID), 10) - } - indexReport := &v1.IndexReport{ - VsockCid: cidStr, - VmId: string(vm.ID), - IndexV4: report, - } - - if h.stopper.Client().Stopped().IsDone() { - return errox.InvariantViolation.CausedBy(errInputChanClosed) - } - if !centralcaps.Has(centralsensor.VirtualMachinesSupported) { - return errox.NotImplemented.CausedBy(errCapabilityNotSupported) - } - if !h.centralReady.IsDone() { - log.Warnf("Cannot send index report for virtual machine with vsock_cid=%q to Central because Central is not reachable", indexReport.GetVsockCid()) - metrics.IndexReportsSent.With(metrics.StatusCentralNotReadyLabels).Inc() - return errox.ResourceExhausted.CausedBy(errCentralNotReachable) - } - - h.lock.RLock() - defer h.lock.RUnlock() - - blockingStart := time.Now() - blocked := false - outcome := metrics.IndexReportEnqueueOutcomeSuccess - defer func() { - if blocked { - metrics.IndexReportBlockingEnqueueDurationMilliseconds. - WithLabelValues(outcome). - Observe(metrics.StartTimeToMS(blockingStart)) - } - }() - - // Fast-path select to detect blocking on the channel for metrics - select { - case <-ctx.Done(): - // Handled in the next select statement - case h.indexReports <- indexReport: - return nil - default: - blocked = true - blockingStart = time.Now() - metrics.IndexReportEnqueueBlockedTotal.Inc() - } - - select { - case <-ctx.Done(): - if err := ctx.Err(); errors.Is(err, context.DeadlineExceeded) { - outcome = metrics.IndexReportEnqueueOutcomeTimeout - return err //nolint:wrapcheck - } - outcome = metrics.IndexReportEnqueueOutcomeCanceled - return ctx.Err() //nolint:wrapcheck - case h.indexReports <- indexReport: - return nil - } -} - -func (h *handlerImpl) Name() string { - return "virtualmachine.index.handlerImpl" -} - -func (h *handlerImpl) Notify(e common.SensorComponentEvent) { - log.Info(common.LogSensorComponentEvent(e)) - switch e { - case common.SensorComponentEventCentralReachable: - h.centralReady.Signal() - case common.SensorComponentEventOfflineMode: - // As clients are expected to retry virtual machine upserts when Sensor is in - // offline mode, there is no need to do anything here other than reset the signal. - h.centralReady.Reset() - } -} - -func (h *handlerImpl) Accepts(_ *central.MsgToSensor) bool { - // VM index SensorACKs are consumed only by VMScraper. - return false -} - -func (h *handlerImpl) ProcessMessage(_ context.Context, _ *central.MsgToSensor) error { - return nil -} - -// ResponsesC returns a channel with messages to Central. It must be called -// after Start() for the channel to be not nil. -func (h *handlerImpl) ResponsesC() <-chan *message.ExpiringMessage { - h.lock.RLock() - defer h.lock.RUnlock() - if h.toCentral == nil { - log.Panic("Start must be called before ResponsesC") - } - return h.toCentral -} - -func (h *handlerImpl) Start() error { - log.Debug("Starting virtual machine handler") - h.lock.Lock() - defer h.lock.Unlock() - if h.toCentral != nil || h.indexReports != nil { - return errStartMoreThanOnce - } - h.indexReports = make(chan *v1.IndexReport, env.VirtualMachinesIndexReportsBufferSize.IntegerSetting()) - h.toCentral = h.run(h.indexReports) - return nil -} - -func (h *handlerImpl) Stop() { - // Stop the stopper FIRST so Send() will see it as stopped and return early - // before we close the channel. This prevents panics from sending on closed channel. - // Matters mainly for local-sensor, as we care that local-sensor stops cleanly before saving the data to a file. - client := h.stopper.Client() - if !client.Stopped().IsDone() { - defer utils.IgnoreError(client.Stopped().Wait) - client.Stop() - } - // Acquire write lock to prevent concurrent Send() calls from racing with channel close - h.lock.Lock() - defer h.lock.Unlock() - // Now close the channel - this will cause the run() goroutine to exit. - // Guard against closing an already-closed channel to make Stop() idempotent - if h.indexReports != nil { - close(h.indexReports) - h.indexReports = nil - } -} - -// run handles the virtual machine data and forwards it to Central. -// This is the only goroutine that writes into the toCentral channel, thus it is -// responsible for creating and closing that chan. -func (h *handlerImpl) run(indexReports <-chan *v1.IndexReport) (toCentral <-chan *message.ExpiringMessage) { - ch2Central := make(chan *message.ExpiringMessage) - go func() { - defer func() { - h.stopper.Flow().ReportStopped() - close(ch2Central) - }() - log.Debugf("virtual machine index report handler is running") - for { - select { - case <-h.stopper.Flow().StopRequested(): - return - case indexReport, ok := <-indexReports: - if !ok { - h.stopper.Flow().StopWithError(errInputChanClosed) - return - } - h.handleIndexReport(ch2Central, indexReport) - } - } - }() - return ch2Central -} - -func (h *handlerImpl) handleIndexReport( - toCentral chan *message.ExpiringMessage, - indexReport *v1.IndexReport, -) { - startTime := time.Now() - outcome := metrics.IndexReportHandlingMessageToCentralSuccess - defer func() { - metrics.IndexReportProcessingDurationMilliseconds. - WithLabelValues(outcome). - Observe(metrics.StartTimeToMS(startTime)) - }() - - if indexReport == nil { - outcome = metrics.IndexReportHandlingMessageToCentralNilReport - log.Warn("Received nil virtual machine index report: not sending to Central") - return - } - log.Debugf("Handling virtual machine index report (vm_id=%q vsock_cid=%q)...", - indexReport.GetVmId(), indexReport.GetVsockCid()) - - msg, outcome, err := h.newMessageToCentral(indexReport) - if err != nil { - log.Warnf("unable to send index report message for virtual machine (vm_id=%q vsock_cid=%q) to central: %v", - indexReport.GetVmId(), indexReport.GetVsockCid(), err) - return - } - h.sendIndexReportEvent(toCentral, msg) - metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() -} - -func (h *handlerImpl) newMessageToCentral(indexReport *v1.IndexReport) (*message.ExpiringMessage, string, error) { - vmInfo, outcome, err := h.resolveVM(indexReport) - if err != nil { - return nil, outcome, err - } - - return message.New(¢ral.MsgFromSensor{ - Msg: ¢ral.MsgFromSensor_Event{ - Event: ¢ral.SensorEvent{ - Id: string(vmInfo.ID), - Action: central.ResourceAction_SYNC_RESOURCE, - Resource: ¢ral.SensorEvent_VirtualMachineIndexReport{ - VirtualMachineIndexReport: &v1.IndexReportEvent{ - Id: string(vmInfo.ID), - Index: indexReport, - }, - }, - }, - }, - }), metrics.IndexReportHandlingMessageToCentralSuccess, nil -} - -// resolveVM prefers vm_id; vsock_cid is only used when the report has no UID. -func (h *handlerImpl) resolveVM(indexReport *v1.IndexReport) (*virtualmachine.Info, string, error) { - if vmID := indexReport.GetVmId(); vmID != "" { - vmInfo := h.store.Get(virtualmachine.VMID(vmID)) - if vmInfo == nil { - return nil, metrics.IndexReportHandlingMessageToCentralVMUnknown, - errors.Wrapf(errVirtualMachineNotFound, "VirtualMachine %q not found", vmID) - } - return vmInfo, "", nil - } - - cid, err := strconv.ParseUint(indexReport.GetVsockCid(), 10, 32) - if err != nil { - return nil, metrics.IndexReportHandlingMessageToCentralInvalidCID, - errors.Wrapf(err, "Received an invalid Vsock CID: %q", indexReport.GetVsockCid()) - } - - vmInfo := h.store.GetFromCID(uint32(cid)) - if vmInfo == nil { - return nil, metrics.IndexReportHandlingMessageToCentralVMUnknown, - errors.Wrapf(errVirtualMachineNotFound, "VirtualMachine with Vsock CID %q not found", indexReport.GetVsockCid()) - } - return vmInfo, "", nil -} - -func (h *handlerImpl) sendIndexReportEvent( - toCentral chan<- *message.ExpiringMessage, - msg *message.ExpiringMessage, -) { - select { - case <-h.stopper.Flow().StopRequested(): - case toCentral <- msg: - } -} diff --git a/sensor/common/virtualmachine/index/handler_impl_test.go b/sensor/common/virtualmachine/index/handler_impl_test.go deleted file mode 100644 index c27786f09cc6b..0000000000000 --- a/sensor/common/virtualmachine/index/handler_impl_test.go +++ /dev/null @@ -1,317 +0,0 @@ -package index - -import ( - "context" - "testing" - "testing/synctest" - "time" - - "github.com/stackrox/rox/generated/internalapi/central" - v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" - v1 "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" - "github.com/stackrox/rox/pkg/centralsensor" - "github.com/stackrox/rox/pkg/concurrency" - "github.com/stackrox/rox/pkg/errox" - "github.com/stackrox/rox/pkg/sync" - "github.com/stackrox/rox/pkg/testutils/goleak" - "github.com/stackrox/rox/sensor/common" - "github.com/stackrox/rox/sensor/common/centralcaps" - "github.com/stackrox/rox/sensor/common/virtualmachine" - "github.com/stackrox/rox/sensor/common/virtualmachine/index/mocks" - "github.com/stretchr/testify/suite" - "go.uber.org/mock/gomock" -) - -func TestVirtualMachineHandler(t *testing.T) { - suite.Run(t, new(virtualMachineHandlerSuite)) -} - -type virtualMachineHandlerSuite struct { - suite.Suite - ctrl *gomock.Controller - store *mocks.MockVirtualMachineStore - handler *handlerImpl -} - -func (s *virtualMachineHandlerSuite) SetupTest() { - s.ctrl = gomock.NewController(s.T()) - s.store = mocks.NewMockVirtualMachineStore(s.ctrl) - s.handler = &handlerImpl{ - centralReady: concurrency.NewSignal(), - lock: &sync.RWMutex{}, - stopper: concurrency.NewStopper(), - store: s.store, - } - centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) -} - -func (s *virtualMachineHandlerSuite) TearDownTest() { - // Reset global capability state to prevent test pollution. - // If not reset, subsequent tests may incorrectly assume capabilities are present/absent, - // leading to false negatives that could hide regressions. - centralcaps.Set(nil) - goleak.AssertNoGoroutineLeaks(s.T()) -} - -func (s *virtualMachineHandlerSuite) TestSend() { - err := s.handler.Start() - s.Require().NoError(err) - s.handler.Notify(common.SensorComponentEventCentralReachable) - defer s.handler.Stop() - s.Require().NotNil(s.handler.toCentral) - - cid := uint32(1) - s.store.EXPECT().Get(gomock.Eq(virtualmachine.VMID("test-vm"))).Times(1).Return( - &virtualmachine.Info{ - ID: "test-vm", - }) - - go func() { - err := s.handler.Send(context.Background(), &virtualmachine.Info{ - ID: "test-vm", - VSOCKCID: &cid, - }, &v4.IndexReport{State: "IndexFinished"}) - s.Require().NoError(err) - }() - - select { - case msg := <-s.handler.ResponsesC(): - s.Require().NotNil(msg) - s.Require().NotNil(msg.MsgFromSensor) - - sensorEvent := msg.GetEvent() - s.Require().NotNil(sensorEvent) - s.Assert().Equal("test-vm", sensorEvent.GetId()) - s.Assert().Equal(central.ResourceAction_SYNC_RESOURCE, sensorEvent.GetAction()) - s.Assert().NotNil(sensorEvent.GetVirtualMachineIndexReport()) - s.Assert().Equal("test-vm", sensorEvent.GetVirtualMachineIndexReport().GetId()) - index := sensorEvent.GetVirtualMachineIndexReport().GetIndex() - s.Require().NotNil(index) - s.Assert().Equal("test-vm", index.GetVmId()) - s.Assert().Equal("1", index.GetVsockCid()) - s.Assert().Equal("IndexFinished", index.GetIndexV4().GetState()) - case <-time.After(time.Second): - s.Fail("Expected message to be sent to central") - } -} - -func (s *virtualMachineHandlerSuite) TestConcurrentSends() { - err := s.handler.Start() - s.Require().NoError(err) - s.handler.Notify(common.SensorComponentEventCentralReachable) - defer s.handler.Stop() - - ctx := context.Background() - numGoroutines := 3 - numVMsPerGoroutine := 2 - s.store.EXPECT().Get(gomock.Any()).Times(numGoroutines * numVMsPerGoroutine). - DoAndReturn(func(id virtualmachine.VMID) *virtualmachine.Info { - return &virtualmachine.Info{ID: id} - }) - - cont := 0 - mu := sync.Mutex{} - for range numGoroutines { - go func() { - for range numVMsPerGoroutine { - var cid uint32 - concurrency.WithLock(&mu, func() { - cid = uint32(cont) - cont++ - }) - err := s.handler.Send(ctx, &virtualmachine.Info{ - ID: virtualmachine.VMID("test-vm"), - VSOCKCID: &cid, - }, &v4.IndexReport{}) - s.Require().NoError(err) - } - }() - } - - // Collect all responses. The timeout breaks the loop so the Assert - // below reports the actual vs expected count — do not fail inside - // the select; that would abort before the diagnostic assertion. - totalResponses := 0 - for range numGoroutines * numVMsPerGoroutine { - select { - case <-s.handler.toCentral: - totalResponses++ - case <-time.After(500 * time.Millisecond): - s.T().Logf("Timeout waiting for response, got %d responses", totalResponses) - s.Assert().Equal(numGoroutines*numVMsPerGoroutine, totalResponses) - return - } - } - s.Assert().Equal(numGoroutines*numVMsPerGoroutine, totalResponses) -} - -func (s *virtualMachineHandlerSuite) TestVirtualMachineNotFound() { - err := s.handler.Start() - s.Require().NoError(err) - s.handler.Notify(common.SensorComponentEventCentralReachable) - defer s.handler.Stop() - s.Require().NotNil(s.handler.toCentral) - - cid := uint32(1) - s.store.EXPECT().GetFromCID(gomock.Eq(uint32(1))).Times(1).Return(nil) - - wg := sync.WaitGroup{} - wg.Go(func() { - // Empty ID forces CID-based resolution in the processing path. - err := s.handler.Send(context.Background(), &virtualmachine.Info{ - VSOCKCID: &cid, - }, &v4.IndexReport{}) - s.Require().NoError(err) - }) - - wg.Wait() - - // No message should be forwarded when the VM cannot be resolved. - select { - case <-s.handler.ResponsesC(): - s.Fail("Unexpected message to be sent to central") - case <-time.After(500 * time.Millisecond): - } -} - -func (s *virtualMachineHandlerSuite) TestResolveVM_InvalidCID() { - _, _, err := s.handler.resolveVM(&v1.IndexReport{VsockCid: "invalid-cid"}) - s.Require().Error(err) - s.Assert().ErrorContains(err, "invalid Vsock CID") -} - -func (s *virtualMachineHandlerSuite) TestSend_ResolveByVmID() { - vmID := virtualmachine.VMID("test-vm") - cases := map[string]struct { - vsockCID *uint32 - wantCID string - }{ - "should forward when only vm_id is set": { - vsockCID: nil, - wantCID: "", - }, - "should include vsock_cid when known": { - vsockCID: new(uint32(42)), - wantCID: "42", - }, - } - s.store.EXPECT().Get(gomock.Eq(vmID)).Times(len(cases)).Return(&virtualmachine.Info{ID: vmID}) - - for name, tc := range cases { - s.Run(name, func() { - synctest.Test(s.T(), func(t *testing.T) { - handler := &handlerImpl{ - centralReady: concurrency.NewSignal(), - lock: &sync.RWMutex{}, - stopper: concurrency.NewStopper(), - store: s.store, - } - err := handler.Start() - s.Require().NoError(err) - handler.Notify(common.SensorComponentEventCentralReachable) - defer handler.Stop() - - go func() { - err := handler.Send(context.Background(), &virtualmachine.Info{ - ID: vmID, - VSOCKCID: tc.vsockCID, - }, &v4.IndexReport{}) - s.Require().NoError(err) - }() - - synctest.Wait() - - msg := <-handler.ResponsesC() - s.Require().NotNil(msg) - sensorEvent := msg.GetEvent() - s.Require().NotNil(sensorEvent) - s.Assert().Equal(string(vmID), sensorEvent.GetId()) - indexEvent := sensorEvent.GetVirtualMachineIndexReport() - s.Require().NotNil(indexEvent) - s.Assert().Equal(string(vmID), indexEvent.GetId()) - s.Assert().Equal(string(vmID), indexEvent.GetIndex().GetVmId()) - s.Assert().Equal(tc.wantCID, indexEvent.GetIndex().GetVsockCid()) - }) - }) - } -} - -func (s *virtualMachineHandlerSuite) TestSend_NilVM() { - err := s.handler.Send(context.Background(), nil, &v4.IndexReport{}) - s.Require().Error(err) - s.Assert().ErrorContains(err, "virtual machine info is nil") -} - -func (s *virtualMachineHandlerSuite) TestStop() { - err := s.handler.Start() - s.Require().NoError(err) - - s.handler.Stop() - - select { - case <-s.handler.stopper.Client().Stopped().Done(): - case <-time.After(time.Second): - s.Fail("handler should have stopped") - } -} - -func (s *virtualMachineHandlerSuite) TestCapabilities() { - caps := s.handler.Capabilities() - s.Require().Len(caps, 1) - s.Contains(caps, centralsensor.SensorACKSupport) -} - -func (s *virtualMachineHandlerSuite) TestResponsesC_BeforeStart() { - s.Assert().Panics(func() { _ = s.handler.ResponsesC() }) -} - -func (s *virtualMachineHandlerSuite) TestResponsesC_AfterStart() { - err := s.handler.Start() - s.Require().NoError(err) - defer s.handler.Stop() - - ch := s.handler.ResponsesC() - s.Require().NotNil(ch) -} - -func (s *virtualMachineHandlerSuite) TestSend_CapabilityNotSupported() { - // Simulate an older Central that lacks VirtualMachinesSupported. - centralcaps.Set(nil) - - err := s.handler.Send(context.Background(), &virtualmachine.Info{ID: "vm-1"}, &v4.IndexReport{}) - s.Require().Error(err) - s.Require().ErrorIs(err, errCapabilityNotSupported) - s.Require().ErrorIs(err, errox.NotImplemented) - s.Assert().ErrorContains(err, "Central does not have virtual machine capability") -} - -func (s *virtualMachineHandlerSuite) TestSend_AfterStop() { - err := s.handler.Start() - s.Require().NoError(err) - s.handler.Stop() - - err = s.handler.Send(context.Background(), &virtualmachine.Info{ID: "vm-1"}, &v4.IndexReport{}) - s.Require().Error(err) - s.Require().ErrorIs(err, errInputChanClosed) - s.Require().ErrorIs(err, errox.InvariantViolation) -} - -func (s *virtualMachineHandlerSuite) TestSend_CentralNotReachable() { - err := s.handler.Start() - s.Require().NoError(err) - defer s.handler.Stop() - - err = s.handler.Send(context.Background(), &virtualmachine.Info{ID: "vm-1"}, &v4.IndexReport{}) - s.Require().Error(err) - s.Require().ErrorIs(err, errCentralNotReachable) - s.Require().ErrorIs(err, errox.ResourceExhausted) -} - -func (s *virtualMachineHandlerSuite) TestStart_CalledTwice() { - err := s.handler.Start() - s.Require().NoError(err) - defer s.handler.Stop() - - err = s.handler.Start() - s.Require().ErrorIs(err, errStartMoreThanOnce) -} diff --git a/sensor/common/virtualmachine/index/mocks/handler.go b/sensor/common/virtualmachine/index/mocks/handler.go deleted file mode 100644 index fb928f1e990af..0000000000000 --- a/sensor/common/virtualmachine/index/mocks/handler.go +++ /dev/null @@ -1,221 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: handler.go -// -// Generated by this command: -// -// mockgen -package mocks -destination mocks/handler.go -source handler.go -// - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - - central "github.com/stackrox/rox/generated/internalapi/central" - v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" - centralsensor "github.com/stackrox/rox/pkg/centralsensor" - common "github.com/stackrox/rox/sensor/common" - message "github.com/stackrox/rox/sensor/common/message" - virtualmachine "github.com/stackrox/rox/sensor/common/virtualmachine" - gomock "go.uber.org/mock/gomock" -) - -// MockHandler is a mock of Handler interface. -type MockHandler struct { - ctrl *gomock.Controller - recorder *MockHandlerMockRecorder - isgomock struct{} -} - -// MockHandlerMockRecorder is the mock recorder for MockHandler. -type MockHandlerMockRecorder struct { - mock *MockHandler -} - -// NewMockHandler creates a new mock instance. -func NewMockHandler(ctrl *gomock.Controller) *MockHandler { - mock := &MockHandler{ctrl: ctrl} - mock.recorder = &MockHandlerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHandler) EXPECT() *MockHandlerMockRecorder { - return m.recorder -} - -// Accepts mocks base method. -func (m *MockHandler) Accepts(msg *central.MsgToSensor) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Accepts", msg) - ret0, _ := ret[0].(bool) - return ret0 -} - -// Accepts indicates an expected call of Accepts. -func (mr *MockHandlerMockRecorder) Accepts(msg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accepts", reflect.TypeOf((*MockHandler)(nil).Accepts), msg) -} - -// Capabilities mocks base method. -func (m *MockHandler) Capabilities() []centralsensor.SensorCapability { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Capabilities") - ret0, _ := ret[0].([]centralsensor.SensorCapability) - return ret0 -} - -// Capabilities indicates an expected call of Capabilities. -func (mr *MockHandlerMockRecorder) Capabilities() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capabilities", reflect.TypeOf((*MockHandler)(nil).Capabilities)) -} - -// Name mocks base method. -func (m *MockHandler) Name() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Name") - ret0, _ := ret[0].(string) - return ret0 -} - -// Name indicates an expected call of Name. -func (mr *MockHandlerMockRecorder) Name() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockHandler)(nil).Name)) -} - -// Notify mocks base method. -func (m *MockHandler) Notify(e common.SensorComponentEvent) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Notify", e) -} - -// Notify indicates an expected call of Notify. -func (mr *MockHandlerMockRecorder) Notify(e any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notify", reflect.TypeOf((*MockHandler)(nil).Notify), e) -} - -// ProcessMessage mocks base method. -func (m *MockHandler) ProcessMessage(ctx context.Context, msg *central.MsgToSensor) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProcessMessage", ctx, msg) - ret0, _ := ret[0].(error) - return ret0 -} - -// ProcessMessage indicates an expected call of ProcessMessage. -func (mr *MockHandlerMockRecorder) ProcessMessage(ctx, msg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessMessage", reflect.TypeOf((*MockHandler)(nil).ProcessMessage), ctx, msg) -} - -// ResponsesC mocks base method. -func (m *MockHandler) ResponsesC() <-chan *message.ExpiringMessage { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResponsesC") - ret0, _ := ret[0].(<-chan *message.ExpiringMessage) - return ret0 -} - -// ResponsesC indicates an expected call of ResponsesC. -func (mr *MockHandlerMockRecorder) ResponsesC() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResponsesC", reflect.TypeOf((*MockHandler)(nil).ResponsesC)) -} - -// Send mocks base method. -func (m *MockHandler) Send(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Send", ctx, vm, report) - ret0, _ := ret[0].(error) - return ret0 -} - -// Send indicates an expected call of Send. -func (mr *MockHandlerMockRecorder) Send(ctx, vm, report any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockHandler)(nil).Send), ctx, vm, report) -} - -// Start mocks base method. -func (m *MockHandler) Start() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Start") - ret0, _ := ret[0].(error) - return ret0 -} - -// Start indicates an expected call of Start. -func (mr *MockHandlerMockRecorder) Start() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockHandler)(nil).Start)) -} - -// Stop mocks base method. -func (m *MockHandler) Stop() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Stop") -} - -// Stop indicates an expected call of Stop. -func (mr *MockHandlerMockRecorder) Stop() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockHandler)(nil).Stop)) -} - -// MockVirtualMachineStore is a mock of VirtualMachineStore interface. -type MockVirtualMachineStore struct { - ctrl *gomock.Controller - recorder *MockVirtualMachineStoreMockRecorder - isgomock struct{} -} - -// MockVirtualMachineStoreMockRecorder is the mock recorder for MockVirtualMachineStore. -type MockVirtualMachineStoreMockRecorder struct { - mock *MockVirtualMachineStore -} - -// NewMockVirtualMachineStore creates a new mock instance. -func NewMockVirtualMachineStore(ctrl *gomock.Controller) *MockVirtualMachineStore { - mock := &MockVirtualMachineStore{ctrl: ctrl} - mock.recorder = &MockVirtualMachineStoreMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockVirtualMachineStore) EXPECT() *MockVirtualMachineStoreMockRecorder { - return m.recorder -} - -// Get mocks base method. -func (m *MockVirtualMachineStore) Get(id virtualmachine.VMID) *virtualmachine.Info { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", id) - ret0, _ := ret[0].(*virtualmachine.Info) - return ret0 -} - -// Get indicates an expected call of Get. -func (mr *MockVirtualMachineStoreMockRecorder) Get(id any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockVirtualMachineStore)(nil).Get), id) -} - -// GetFromCID mocks base method. -func (m *MockVirtualMachineStore) GetFromCID(cid uint32) *virtualmachine.Info { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFromCID", cid) - ret0, _ := ret[0].(*virtualmachine.Info) - return ret0 -} - -// GetFromCID indicates an expected call of GetFromCID. -func (mr *MockVirtualMachineStoreMockRecorder) GetFromCID(cid any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFromCID", reflect.TypeOf((*MockVirtualMachineStore)(nil).GetFromCID), cid) -} diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index 9adaf0f8d974e..baed6a5eda51c 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -1,17 +1,10 @@ package metrics import ( - "time" - "github.com/prometheus/client_golang/prometheus" "github.com/stackrox/rox/pkg/metrics" ) -// StartTimeToMS allows to record sub-millisecond durations. Without this, things faster than 1ms are rounded to 0. -func StartTimeToMS(t time.Time) float64 { - return float64(time.Since(t).Nanoseconds()) / float64(time.Millisecond) -} - var ( StatusCentralNotReadyLabels = prometheus.Labels{"status": "central not ready"} StatusErrorLabels = prometheus.Labels{"status": "error"} @@ -30,56 +23,13 @@ var IndexReportsSent = prometheus.NewCounterVec( []string{"status"}, ) -// IndexReportProcessingDuration label values. -const ( - // IndexReportHandlingMessageToCentralSuccess marks processing flows that successfully send to Central. - IndexReportHandlingMessageToCentralSuccess = "success" - // IndexReportHandlingMessageToCentralNilReport marks flows that exit because the report was nil. - IndexReportHandlingMessageToCentralNilReport = "nil_report" - // IndexReportHandlingMessageToCentralInvalidCID marks flows that exit because the message could not be constructed due to an invalid vsock CID. - IndexReportHandlingMessageToCentralInvalidCID = "invalid_vsock_cid" - // IndexReportHandlingMessageToCentralVMUnknown marks flows that exit because the virtual machine is not known to Sensor. - IndexReportHandlingMessageToCentralVMUnknown = "vm_unknown_to_sensor" -) - -// IndexReportProcessingDurationMilliseconds tracks how long Sensor spends processing index reports after dequeuing them. -var IndexReportProcessingDurationMilliseconds = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: metrics.PrometheusNamespace, - Subsystem: metrics.SensorSubsystem.String(), - Name: "virtual_machine_index_report_processing_duration_milliseconds", - Help: "Distribution of time spent (in ms) processing virtual machine index reports after reading from indexReports and before sending to Central", - Buckets: prometheus.ExponentialBuckets(10, 2, 12), - }, - []string{"outcome"}, -) - -// IndexReportEnqueueOutcome label values for enqueue latency observations. -const ( - IndexReportEnqueueOutcomeSuccess = "success" - IndexReportEnqueueOutcomeTimeout = "context_timeout" - IndexReportEnqueueOutcomeCanceled = "context_canceled" -) - -// IndexReportBlockingEnqueueDurationMilliseconds measures how long Sensor waits after detecting backpressure. -var IndexReportBlockingEnqueueDurationMilliseconds = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: metrics.PrometheusNamespace, - Subsystem: metrics.SensorSubsystem.String(), - Name: "virtual_machine_index_report_blocking_enqueue_duration_milliseconds", - Help: "Time spent (in ms) waiting for indexReports capacity after encountering a full channel", - Buckets: append([]float64{1, 5, 10, 50, 100, 250, 500}, prometheus.ExponentialBuckets(1000, 2, 8)...), // 1ms to 128s - }, - []string{"outcome"}, -) - // IndexReportEnqueueBlockedTotal counts how often the enqueue channel was full. var IndexReportEnqueueBlockedTotal = prometheus.NewCounter( prometheus.CounterOpts{ Namespace: metrics.PrometheusNamespace, Subsystem: metrics.SensorSubsystem.String(), Name: "virtual_machine_index_report_enqueue_blocked_total", - Help: "Number of times virtual machine index report enqueue attempts found the indexReports channel full", + Help: "Number of times virtual machine index report enqueue attempts found the channel full", }, ) @@ -354,8 +304,6 @@ var PullScheduleOffsetSeconds = prometheus.NewHistogram( func init() { prometheus.MustRegister( IndexReportsSent, - IndexReportProcessingDurationMilliseconds, - IndexReportBlockingEnqueueDurationMilliseconds, IndexReportEnqueueBlockedTotal, VMDiscoveredData, IndexReportAcksReceived, diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 6af54dfb81f59..d514911f0ae8b 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -3,8 +3,10 @@ package vmscraper import ( "context" "errors" + "fmt" "io" "math/rand" + "strconv" "strings" "sync/atomic" "time" @@ -31,8 +33,10 @@ import ( ) var ( - log = logging.LoggerForModule() - errStartMoreThanOnce = errors.New("unable to start the VM scraper more than once") + log = logging.LoggerForModule() + errStartMoreThanOnce = errors.New("unable to start the VM scraper more than once") + errCapabilityNotSupported = errors.New("Central does not have virtual machine capability") + errCentralNotReachable = errors.New("Central is not reachable") ) const ( @@ -67,11 +71,6 @@ type VMDialer interface { Dial(ctx context.Context, namespace, name string, port uint32, useTLS bool) (io.ReadWriteCloser, error) } -// IndexReportSender sends index reports toward Central. -type IndexReportSender interface { - Send(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error -} - // ProtocolClient performs the request/response protocol over a stream. type ProtocolClient interface { GetReport(ctx context.Context, stream io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) @@ -95,10 +94,11 @@ type closeCoder interface { // VMScraper polls running VMs and pulls their scan reports via VSOCK. type VMScraper struct { store RunningVMStore - sender IndexReportSender dialer VMDialer client ProtocolClient repo2CPEFetcher Repo2CPEFetcher + toCentral chan *message.ExpiringMessage + centralReady concurrency.Signal interval time.Duration tickInterval time.Duration initialBackoff time.Duration @@ -138,14 +138,15 @@ var _ common.SensorComponent = (*VMScraper)(nil) // New creates a VMScraper with production defaults. A nil repo2CPEFetcher leaves // maybeSyncRepoCPEMapping a no-op, so pull still works if the repo-to-CPE cache was not built. -func New(store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient, repo2CPEFetcher Repo2CPEFetcher) *VMScraper { +func New(store RunningVMStore, dialer VMDialer, client ProtocolClient, repo2CPEFetcher Repo2CPEFetcher) *VMScraper { interval := clampPollInterval(env.VirtualMachinesScraperPollInterval.DurationSetting()) return &VMScraper{ store: store, - sender: sender, dialer: dialer, client: client, repo2CPEFetcher: repo2CPEFetcher, + toCentral: make(chan *message.ExpiringMessage, env.VirtualMachinesIndexReportsBufferSize.IntegerSetting()), + centralReady: concurrency.NewSignal(), interval: interval, tickInterval: env.VirtualMachinesScraperTickInterval.DurationSetting(), initialBackoff: env.VirtualMachinesScraperInitialBackoff.DurationSetting(), @@ -180,8 +181,18 @@ func (s *VMScraper) Stop() { s.stopper.Client().Stop() } -func (s *VMScraper) Capabilities() []centralsensor.SensorCapability { return nil } -func (s *VMScraper) Notify(_ common.SensorComponentEvent) {} +func (s *VMScraper) Capabilities() []centralsensor.SensorCapability { + return []centralsensor.SensorCapability{centralsensor.SensorACKSupport} +} + +func (s *VMScraper) Notify(e common.SensorComponentEvent) { + switch e { + case common.SensorComponentEventCentralReachable: + s.centralReady.Signal() + case common.SensorComponentEventOfflineMode: + s.centralReady.Reset() + } +} // Accepts reports whether this component wants to see SensorACK messages // for VM index reports. @@ -214,7 +225,7 @@ func (s *VMScraper) ProcessMessage(_ context.Context, msg *central.MsgToSensor) // handleNACK clears the cached token and applies the shared backoff so // the next tick resends a full report without tight-looping on persistent NACKs. // -// Race with commitVMState after Send is accepted: a fast NACK may be +// Race with commitVMState after forward is accepted: a fast NACK may be // overwritten by a late success commit. func (s *VMScraper) handleNACK(resourceID string) { key := s.findKeyByVMID(vmIDFromResourceID(resourceID)) @@ -260,12 +271,18 @@ func vmIDFromResourceID(resourceID string) string { return vmID } -func (s *VMScraper) ResponsesC() <-chan *message.ExpiringMessage { return nil } +func (s *VMScraper) ResponsesC() <-chan *message.ExpiringMessage { return s.toCentral } func (s *VMScraper) run() { defer s.stopper.Flow().ReportStopped() ctx := concurrency.AsContext(s.stopper.LowLevel().GetStopRequestSignal()) + // Block until stop: there is nothing to poll, but the component stays registered. + if s.dialer == nil || s.client == nil { + <-ctx.Done() + return + } + // Reconcile + scrape immediately so VMs don't wait a full tick on start. s.tick(ctx, true) @@ -499,8 +516,8 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool metrics.PullReportPackages.Observe(float64(len(result.IndexReport.GetContents().GetPackages()))) logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) - // Mapping sync has its own deadline, so Send uses the scrape parent. - if err := s.sender.Send(ctx, vm, result.IndexReport); err != nil { + // Mapping sync has its own deadline, so forward uses the scrape parent. + if err := s.forwardReport(ctx, vm, result.IndexReport); err != nil { metrics.PullScrapeTotal.WithLabelValues(metrics.PullScrapeSendError).Inc() outcome := scrapeRetryable if errors.Is(err, errox.NotImplemented) { @@ -828,7 +845,7 @@ func (s *VMScraper) snapshotVMState(key string) vmStateSnapshot { // commitVMState records the token from a just-sent report as key's cached scrape state. // // Race: If a NACK arrives from Central faster than this function completes, for example, due to: -// - a scheduling delay on the caller's goroutine between Send returning and this function +// - a scheduling delay on the caller's goroutine between forward returning and this function // acquiring `s.mu` (a GC pause, CPU throttling, or GOMAXPROCS contention), // - contention on `s.mu` itself, from other concurrent scrapes or NACKs delaying this // function's lock acquisition, @@ -847,3 +864,57 @@ func (s *VMScraper) commitVMState(key string, vmID virtualmachine.VMID, newToken state.lastAgentVersion = agentVersion }) } + +func (s *VMScraper) forwardReport(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error { + if !centralcaps.Has(centralsensor.VirtualMachinesSupported) { + return errox.NotImplemented.CausedBy(errCapabilityNotSupported) + } + if !s.centralReady.IsDone() { + metrics.IndexReportsSent.With(metrics.StatusCentralNotReadyLabels).Inc() + return errox.ResourceExhausted.CausedBy(errCentralNotReachable) + } + + msg := newIndexReportMessage(vm, report) + select { + case <-ctx.Done(): + case s.toCentral <- msg: + metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() + return nil + default: + metrics.IndexReportEnqueueBlockedTotal.Inc() + } + + select { + case <-ctx.Done(): + return fmt.Errorf("waiting to forward index report: %w", ctx.Err()) + case s.toCentral <- msg: + metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() + return nil + } +} + +func newIndexReportMessage(vm *virtualmachine.Info, report *v4.IndexReport) *message.ExpiringMessage { + var cidStr string + if vm.VSOCKCID != nil { + cidStr = strconv.FormatUint(uint64(*vm.VSOCKCID), 10) + } + indexReport := &pb.IndexReport{ + VsockCid: cidStr, + VmId: string(vm.ID), + IndexV4: report, + } + return message.New(¢ral.MsgFromSensor{ + Msg: ¢ral.MsgFromSensor_Event{ + Event: ¢ral.SensorEvent{ + Id: string(vm.ID), + Action: central.ResourceAction_SYNC_RESOURCE, + Resource: ¢ral.SensorEvent_VirtualMachineIndexReport{ + VirtualMachineIndexReport: &pb.IndexReportEvent{ + Id: string(vm.ID), + Index: indexReport, + }, + }, + }, + }, + }) +} diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 05f34d222d72b..4945fa868c13e 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -17,11 +17,11 @@ import ( pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" "github.com/stackrox/rox/pkg/centralsensor" "github.com/stackrox/rox/pkg/concurrency" - "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/sensor/common/centralcaps" + "github.com/stackrox/rox/sensor/common/message" "github.com/stackrox/rox/sensor/common/virtualmachine" "github.com/stackrox/rox/sensor/common/virtualmachine/metrics" "github.com/stackrox/rox/sensor/common/virtualmachine/vsockclient" @@ -210,25 +210,6 @@ func (f *fakeFetcher) FetchRepo2CPE(_ context.Context) ([]byte, string, bool) { return f.mapping, f.hash, f.ok } -type mockSender struct { - mu sync.Mutex - sent []*v4.IndexReport - err error -} - -func (m *mockSender) Send(ctx context.Context, _ *virtualmachine.Info, report *v4.IndexReport) error { - m.mu.Lock() - defer m.mu.Unlock() - if err := ctx.Err(); err != nil { - return err - } - if m.err != nil { - return m.err - } - m.sent = append(m.sent, report) - return nil -} - // --- Helpers --- func makeVM(ns, name string, cid uint32) *virtualmachine.Info { @@ -300,18 +281,17 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { makeVM("ns1", "vm-a", 100), makeVM("ns2", "vm-b", 200), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, errQueue: []error{nil, nil}, } - s, _ := newTestScraper(t, store, sender, dialer, client) + s, _ := newTestScraper(t, store, dialer, client) discoveredBefore := testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE")) s.pollOnce(context.Background()) - assert.Len(t, sender.sent, 2) + assert.Equal(t, 2, forwardedCount(s)) assert.Len(t, client.calls, 2) assert.Equal(t, discoveredBefore+2, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) } @@ -323,20 +303,19 @@ func TestVMScraper_SkipsWhenCentralLacksCapability(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, _ := newTestScraper(t, store, sender, dialer, client) + s, _ := newTestScraper(t, store, dialer, client) centralcaps.Set(nil) s.pollOnce(context.Background()) assert.Equal(t, 0, store.listRunningCalls, "should not reconcile when Central cannot consume reports") assert.Zero(t, dialer.callIdx.Load()) - assert.Empty(t, sender.sent) + assert.Zero(t, forwardedCount(s)) assert.Empty(t, client.calls) } @@ -351,7 +330,7 @@ func TestVMScraper_LogsSkipOnceWhileCapabilityMissing(t *testing.T) { s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), - }}, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + }}, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) centralcaps.Set(nil) @@ -372,46 +351,45 @@ func TestVMScraper_SkipsUnchangedToken(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) - require.Len(t, sender.sent, 1) + require.Equal(t, 1, forwardedCount(s)) // Second poll returns unchanged client.reset() client.resultQueue = []*vsockclient.GetReportResult{unchangedResult()} clock.Advance(s.interval) s.pollOnce(context.Background()) - assert.Len(t, sender.sent, 1, "should not forward unchanged report") + assert.Zero(t, forwardedCount(s), "should not forward unchanged report") } func TestVMScraper_RemainsScheduledAcrossUnchangedPolls(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) require.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) + require.Equal(t, 1, forwardedCount(s)) client.reset() client.resultQueue = []*vsockclient.GetReportResult{unchangedResult()} clock.Advance(s.interval) s.pollOnce(context.Background()) - assert.Len(t, sender.sent, 1, "should not forward unchanged report") + assert.Zero(t, forwardedCount(s), "should not forward unchanged report") assert.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) } @@ -419,16 +397,15 @@ func TestVMScraper_ForwardsAfter4Hours(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) - require.Len(t, sender.sent, 1) + require.Equal(t, 1, forwardedCount(s)) // Past both the success schedule and the mandatory refresh window. clock.Advance(s.mandatoryRefreshAfter + time.Second) @@ -441,7 +418,7 @@ func TestVMScraper_ForwardsAfter4Hours(t *testing.T) { require.Len(t, client.calls, 1, "mandatory refresh should resolve in a single round trip") assert.Empty(t, client.calls[0].lastKnownToken, "mandatory refresh forces the full report on the only call") - assert.Len(t, sender.sent, 2, "should forward after 4h even if unchanged") + assert.Equal(t, 1, forwardedCount(s), "should forward after 4h even if unchanged") } // TestVMScraper_SendsLastKnownTokenOnRequest verifies Sensor sends its @@ -450,13 +427,12 @@ func TestVMScraper_SendsLastKnownTokenOnRequest(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("tok-100")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) require.Len(t, client.calls, 1) @@ -475,22 +451,21 @@ func TestVMScraper_ForwardsOnTokenChange(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) - require.Len(t, sender.sent, 1) + require.Equal(t, 1, forwardedCount(s)) // New token client.reset() client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} clock.Advance(s.interval) s.pollOnce(context.Background()) - assert.Len(t, sender.sent, 2, "should forward on token change") + assert.Equal(t, 1, forwardedCount(s), "should forward on token change") } // TestVMScraper_NACK reproduces a scenario where Central NACKs a report @@ -568,15 +543,14 @@ func TestVMScraper_NACK(t *testing.T) { vmA := makeVM("ns1", "vm-a", 100) vmA.ID = "vm-a-id" store := &mockStore{vms: []*virtualmachine.Info{vmA}} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) - require.Len(t, sender.sent, 1) + require.Equal(t, 1, forwardedCount(s)) // Flip Running only after the first poll, so the VM is scraped normally // once before the ACK/NACK under test is delivered. @@ -605,32 +579,11 @@ func TestVMScraper_NACK(t *testing.T) { assert.Equal(t, tc.wantLastKnownTokenOnRetryPoll, client.calls[0].lastKnownToken, "token the scraper requests on the poll following the NACK") } - assert.Len(t, sender.sent, tc.wantTotalSent, "total reports handed to the sender across both polls") + assert.Equal(t, tc.wantTotalSent-1, forwardedCount(s), "additional reports forwarded after the ACK/NACK poll") }) } } -// gateSender blocks the Nth Send (1-based) until release is closed. -type gateSender struct { - blockAt int - release chan struct{} - mu sync.Mutex - n int - sent []*v4.IndexReport -} - -func (g *gateSender) Send(_ context.Context, _ *virtualmachine.Info, report *v4.IndexReport) error { - g.mu.Lock() - g.n++ - n := g.n - g.sent = append(g.sent, report) - g.mu.Unlock() - if n == g.blockAt { - <-g.release - } - return nil -} - // TestVMScraper_InFlightSendCanOverwriteNACKReset exercises a known race involving // an unusually slow execution of `commitVMState` and a quick NACK from Central. func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { @@ -639,16 +592,13 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { vmA.ID = "vm-a-id" store := &mockStore{vms: []*virtualmachine.Info{vmA}} client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockDialer{}, client) s.pollOnce(t.Context()) require.Equal(t, "1", cachedToken(t, s, "ns1/vm-a")) + require.Equal(t, 1, forwardedCount(s)) - gate := &gateSender{ - blockAt: 1, - release: make(chan struct{}), - } - s.sender = gate + s.toCentral = make(chan *message.ExpiringMessage) client.reset() client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} clock.Advance(s.interval) @@ -659,8 +609,8 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { s.pollOnce(t.Context()) }() - // Block until the scrape goroutine is durably blocked inside Send, - // i.e. the report has been handed off but not yet committed. + // Block until the scrape goroutine is durably blocked in forwardReport, + // i.e. the report is on toCentral but not yet committed. synctest.Wait() require.NoError(t, s.ProcessMessage(t.Context(), ¢ral.MsgToSensor{ @@ -675,7 +625,7 @@ func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { assert.Empty(t, cachedToken(t, s, "ns1/vm-a"), "NACK applies immediately while the send it targets is still in flight") - close(gate.release) + <-s.toCentral <-done assert.Equal(t, "2", cachedToken(t, s, "ns1/vm-a"), "the in-flight send's commit runs after the NACK reset and overwrites it unconditionally") @@ -721,25 +671,24 @@ func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - sender := &mockSender{} client := &mockProtocolClient{ resultQueue: tc.resultQueue, errQueue: tc.errQueue, } - s, _ := newTestScraper(t, &mockStore{vms: vms}, sender, tc.dialer, client) + s, _ := newTestScraper(t, &mockStore{vms: vms}, tc.dialer, client) if tc.perVMTimeout > 0 { s.perVMTimeout = tc.perVMTimeout } s.pollOnce(context.Background()) assert.Len(t, client.calls, tc.wantCalls) - assert.Len(t, sender.sent, tc.wantSent) + assert.Equal(t, tc.wantSent, forwardedCount(s)) }) } } func TestVMScraper_StartRejectsSecondCall(t *testing.T) { - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) require.NoError(t, s.Start()) t.Cleanup(s.Stop) @@ -754,7 +703,7 @@ func TestVMScraper_GetReportTimeoutClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{errors.New("i/o timeout")}, } - s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, client) timeoutBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportTimeout)) readErrBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)) @@ -778,7 +727,7 @@ func TestVMScraper_GetReportDeadlineExceededClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{errors.New("i/o timeout")}, } - s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, client) ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) defer cancel() @@ -795,7 +744,7 @@ func TestVMScraper_GetReportBusyClassified(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{vsockclient.ErrBusy}, } - s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, client) busyBefore := testutil.ToFloat64(metrics.PullGetReportTotal.WithLabelValues(metrics.PullGetReportBusy)) readErrBefore := testutil.ToFloat64(metrics.PullTransportTotal.WithLabelValues(metrics.PullTransportReadError)) @@ -814,13 +763,12 @@ func TestVMScraper_ReconcileVMState(t *testing.T) { makeVM("ns1", "vm-a", 100), makeVM("ns2", "vm-b", 200), }} - sender := &mockSender{} dialer := &mockDialer{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } - s, clock := newTestScraper(t, store, sender, dialer, client) + s, clock := newTestScraper(t, store, dialer, client) s.pollOnce(context.Background()) assert.Len(t, s.vmState, 2) assert.True(t, hasScheduleSlot(t, s, "ns1/vm-a")) @@ -846,7 +794,7 @@ func TestVMScraper_ReconcileVMState(t *testing.T) { Running: true, } store := &mockStore{vms: []*virtualmachine.Info{oldVM}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) @@ -878,7 +826,7 @@ func TestVMScraper_ReconcileVMState(t *testing.T) { } // TestVMScraper_IgnoresLateScrapeAfterVMReplacement covers a KubeVirt recreate -// while a scrape of the predecessor is still in Send: the late commit must not +// while a scrape of the predecessor is still forwarding: the late commit must not // mark the replacement as already scanned or push its next poll out by interval. func TestVMScraper_IgnoresLateScrapeAfterVMReplacement(t *testing.T) { synctest.Test(t, func(t *testing.T) { @@ -891,13 +839,10 @@ func TestVMScraper_IgnoresLateScrapeAfterVMReplacement(t *testing.T) { Running: true, } store := &mockStore{vms: []*virtualmachine.Info{oldVM}} - gate := &gateSender{ - blockAt: 1, - release: make(chan struct{}), - } - s, clock := newTestScraper(t, store, gate, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("99")}, }) + s.toCentral = make(chan *message.ExpiringMessage) done := make(chan struct{}) go func() { @@ -921,7 +866,7 @@ func TestVMScraper_IgnoresLateScrapeAfterVMReplacement(t *testing.T) { require.Zero(t, s.Stats().VMsScanned) require.Equal(t, clock.Now(), cachedNextAttemptAt(t, s, key)) - close(gate.release) + <-s.toCentral <-done assert.Equal(t, virtualmachine.VMID("uid-new"), concurrency.WithLock1(&s.mu, func() virtualmachine.VMID { @@ -1140,7 +1085,19 @@ func TestIsAbnormalClose(t *testing.T) { } } -func newTestScraper(t *testing.T, store RunningVMStore, sender IndexReportSender, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { +func forwardedCount(s *VMScraper) int { + n := 0 + for { + select { + case <-s.toCentral: + n++ + default: + return n + } + } +} + +func newTestScraper(t *testing.T, store RunningVMStore, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { t.Helper() centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) t.Cleanup(func() { centralcaps.Set(nil) }) @@ -1149,9 +1106,10 @@ func newTestScraper(t *testing.T, store RunningVMStore, sender IndexReportSender interval := 5 * time.Minute s := &VMScraper{ store: store, - sender: sender, dialer: dialer, client: client, + toCentral: make(chan *message.ExpiringMessage, 256), + centralReady: concurrency.NewSignal(), interval: interval, tickInterval: defaultTickInterval, initialBackoff: initialBackoff, @@ -1168,6 +1126,7 @@ func newTestScraper(t *testing.T, store RunningVMStore, sender IndexReportSender now: clock.Now, randFloat64: func() float64 { return 0 }, } + s.centralReady.Signal() setTickToDrain(s) return s, clock } @@ -1225,18 +1184,6 @@ func (c *safeProtocolClient) SyncRepoCPEMapping(_ context.Context, _ io.ReadWrit return false, nil, nil } -type safeSender struct { - mu sync.Mutex - sent int -} - -func (s *safeSender) Send(_ context.Context, _ *virtualmachine.Info, _ *v4.IndexReport) error { - s.mu.Lock() - s.sent++ - s.mu.Unlock() - return nil -} - func TestVMScraper_ConcurrentFasterThanSequential(t *testing.T) { const ( numVMs = 10 @@ -1250,11 +1197,10 @@ func TestVMScraper_ConcurrentFasterThanSequential(t *testing.T) { } store := &mockStore{vms: vms} - sender := &safeSender{} dialer := &delayDialer{delay: dialDelay} client := &safeProtocolClient{token: "1"} - s, _ := newTestScraper(t, store, sender, dialer, client) + s, _ := newTestScraper(t, store, dialer, client) s.concurrency = concurrency // This test measures real dial overlap via delayDialer's timers, so it // needs the wall clock rather than the fake test clock. @@ -1262,7 +1208,7 @@ func TestVMScraper_ConcurrentFasterThanSequential(t *testing.T) { s.pollOnce(context.Background()) - assert.Equal(t, numVMs, sender.sent) + assert.Equal(t, numVMs, forwardedCount(s)) assert.Equal(t, numVMs, client.calls) // Direct concurrency signal instead of a wall-clock inference: with // concurrency == numVMs, all dials should be in flight together at some @@ -1279,7 +1225,7 @@ func TestVMScraper_RetryableFailureSchedulesBackoff(t *testing.T) { client := &mockProtocolClient{ errQueue: []error{vsockclient.ErrNotReady}, } - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockDialer{}, client) s.pollOnce(context.Background()) require.Len(t, client.calls, 1) @@ -1316,68 +1262,54 @@ func TestVMScraper_RetryableFailureSchedulesBackoff(t *testing.T) { func TestVMScraper_SchedulesByOutcome(t *testing.T) { const key = "ns1/vm-a" cases := map[string]struct { - client *mockProtocolClient - sender *mockSender - wantBackoff time.Duration - wantGap time.Duration + client *mockProtocolClient + centralNotReady bool + wantBackoff time.Duration + wantGap time.Duration }{ "send failure should retry using backoff": { - client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}}, - sender: &mockSender{err: errors.New("central unavailable")}, - wantBackoff: initialBackoff, - wantGap: initialBackoff, - }, - "not-implemented send should not retry using backoff": { - client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}}, - sender: &mockSender{err: errox.NotImplemented}, - wantBackoff: 0, - wantGap: 5 * time.Minute, + client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}}, + centralNotReady: true, + wantBackoff: initialBackoff, + wantGap: initialBackoff, }, "ErrInternal should retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrInternal}}, - sender: &mockSender{}, wantBackoff: initialBackoff, wantGap: initialBackoff, }, "ErrMappingRequired should retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrMappingRequired}}, - sender: &mockSender{}, wantBackoff: initialBackoff, wantGap: initialBackoff, }, "io.EOF should retry using backoff": { client: &mockProtocolClient{errQueue: []error{io.EOF}}, - sender: &mockSender{}, wantBackoff: initialBackoff, wantGap: initialBackoff, }, "io.ErrUnexpectedEOF should retry using backoff": { client: &mockProtocolClient{errQueue: []error{io.ErrUnexpectedEOF}}, - sender: &mockSender{}, wantBackoff: initialBackoff, wantGap: initialBackoff, }, "unhandled protocol error should retry using backoff": { client: &mockProtocolClient{errQueue: []error{errors.New("bogus frame")}}, - sender: &mockSender{}, wantBackoff: initialBackoff, wantGap: initialBackoff, }, "ErrUnknownMethod should not retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrUnknownMethod}}, - sender: &mockSender{}, wantBackoff: 0, wantGap: 5 * time.Minute, // matches newTestScraper interval }, "ErrMalformedRequest should not retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrMalformedRequest}}, - sender: &mockSender{}, wantBackoff: 0, wantGap: 5 * time.Minute, }, "ErrRequestTooLarge should not retry using backoff": { client: &mockProtocolClient{errQueue: []error{vsockclient.ErrRequestTooLarge}}, - sender: &mockSender{}, wantBackoff: 0, wantGap: 5 * time.Minute, }, @@ -1385,7 +1317,6 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { client: &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{{ Meta: &pb.ResponseMeta{ReportToken: "1"}, }}}, - sender: &mockSender{}, wantBackoff: 0, wantGap: 5 * time.Minute, }, @@ -1394,8 +1325,11 @@ func TestVMScraper_SchedulesByOutcome(t *testing.T) { t.Run(name, func(t *testing.T) { s, clock := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}}, - tc.sender, &mockDialer{}, tc.client, + &mockDialer{}, tc.client, ) + if tc.centralNotReady { + s.centralReady.Reset() + } start := clock.Now() s.pollOnce(context.Background()) @@ -1466,7 +1400,7 @@ func TestVMScraper_MaybeSyncRepoCPEMapping(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { client := &mockProtocolClient{syncErr: tc.syncErr} - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, client) if !tc.noFetcher { s.repo2CPEFetcher = tc.fetcher } @@ -1495,7 +1429,7 @@ func TestVMScraper_NonForcedTickSkipsReconcileWhenNotDue(t *testing.T) { client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1"), makeReport("1")}, } - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockDialer{}, client) s.pollOnce(context.Background()) require.Equal(t, 1, store.listRunningCalls, "pollOnce forces exactly one reconcile") @@ -1577,7 +1511,7 @@ func TestVMScraper_DialAndGetReport_SyncTriggering(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { client := &mockProtocolClient{resultQueue: tc.resultQueue, errQueue: tc.errQueue} - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, client) s.repo2CPEFetcher = staleFetcher() _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") @@ -1593,7 +1527,7 @@ func TestVMScraper_DialAndGetReport_SyncTriggering(t *testing.T) { func TestVMScraper_SyncRepoCPEMapping_CanceledParent_ClassifiedAsTimeout(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) dialer := &mockDialer{err: errors.New("dial must not be attempted")} - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, dialer, &mockProtocolClient{}) timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) syncErrBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncError)) @@ -1613,7 +1547,7 @@ func TestVMScraper_SyncRepoCPEMapping_CanceledParent_ClassifiedAsTimeout(t *test // PullSyncError (same rule as dialAndGetReport). func TestVMScraper_SyncRepoCPEMapping_DialTimeout_ClassifiedAsTimeout(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, blockingDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, blockingDialer{}, &mockProtocolClient{}) s.perVMTimeout = 20 * time.Millisecond timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) @@ -1632,7 +1566,7 @@ func TestVMScraper_SyncRepoCPEMapping_DialTimeout_ClassifiedAsTimeout(t *testing func TestVMScraper_SyncRepoCPEMapping_SyncTimeout_ClassifiedAsTimeout(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) client := &mockProtocolClient{syncBlocksOnCtx: true} - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, client) s.perVMTimeout = 20 * time.Millisecond timeoutBefore := testutil.ToFloat64(metrics.PullSyncTotal.WithLabelValues(metrics.PullSyncTimeout)) @@ -1654,19 +1588,18 @@ func TestVMScraper_SendSucceedsAfterSyncOverrunsGetReportDeadline(t *testing.T) rep := makeReport("1") rep.Meta.RepoCpeMappingHash = new("old-hash") rep.Meta.RepoCpeMappingUpdatePath = pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR.Enum() - sender := &mockSender{} client := &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{rep}, getReportDelay: 15 * time.Millisecond, syncDelay: 15 * time.Millisecond, } - s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, sender, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, client) s.perVMTimeout = 20 * time.Millisecond s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} s.pollOnce(t.Context()) - require.Len(t, sender.sent, 1, "GetReport's deadline must not fail Send after a mapping sync") + require.Equal(t, 1, forwardedCount(s), "GetReport's deadline must not fail forward after a mapping sync") require.Len(t, client.syncCalls, 1) }) } @@ -1684,7 +1617,7 @@ func TestVMScraper_MappingRequiredClassifiedDespiteSlowSync(t *testing.T) { getReportDelay: 15 * time.Millisecond, syncDelay: 15 * time.Millisecond, } - s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockSender{}, &mockDialer{}, client) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, client) s.perVMTimeout = 20 * time.Millisecond s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} @@ -1711,7 +1644,7 @@ func TestVMScraper_DialAndGetReport_ClosesConnectionBeforeSync(t *testing.T) { IndexReport: &v4.IndexReport{State: "IndexFinished"}, Meta: metaWithMapping("old-hash", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR), }}} - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, client) + s, _ := newTestScraper(t, &mockStore{}, dialer, client) s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") @@ -1730,7 +1663,7 @@ func TestVMScraper_DialAndGetReport_MappingRequired_ClosesConnectionBeforeSync(t resultQueue: []*vsockclient.GetReportResult{{Meta: metaWithMapping("", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)}}, errQueue: []error{vsockclient.ErrMappingRequired}, } - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, dialer, client) + s, _ := newTestScraper(t, &mockStore{}, dialer, client) s.repo2CPEFetcher = &fakeFetcher{ok: true, hash: "new-hash", mapping: []byte("payload")} _, outcome := s.dialAndGetReport(context.Background(), vm, "ns1/vm-a", 1, "") @@ -1763,3 +1696,32 @@ func TestClampPollInterval(t *testing.T) { }) } } + +func TestVMScraper_Capabilities(t *testing.T) { + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) + assert.Equal(t, []centralsensor.SensorCapability{centralsensor.SensorACKSupport}, s.Capabilities()) +} + +func TestVMScraper_ForwardReport(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + require.NoError(t, s.forwardReport(t.Context(), vm, makeReport("tok-1").IndexReport)) + assert.Equal(t, 1, forwardedCount(s)) +} + +func TestVMScraper_ForwardReport_EnqueueBlocked(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.toCentral = make(chan *message.ExpiringMessage) + blockedBefore := testutil.ToFloat64(metrics.IndexReportEnqueueBlockedTotal) + done := make(chan error, 1) + go func() { + done <- s.forwardReport(t.Context(), vm, makeReport("tok-1").IndexReport) + }() + synctest.Wait() + assert.Equal(t, blockedBefore+1, testutil.ToFloat64(metrics.IndexReportEnqueueBlockedTotal)) + <-s.toCentral + require.NoError(t, <-done) + }) +} diff --git a/sensor/common/virtualmachine/vmscraper/spreading_test.go b/sensor/common/virtualmachine/vmscraper/spreading_test.go index ab2229ec6ef46..ee0870931bb79 100644 --- a/sensor/common/virtualmachine/vmscraper/spreading_test.go +++ b/sensor/common/virtualmachine/vmscraper/spreading_test.go @@ -46,7 +46,7 @@ func sequencedRand(seq []float64) func() float64 { func TestVMScraper_CadenceRescheduleUsesSteadyBand(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "vm-a", 1)}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.spreadFraction = 2.0 / 3 @@ -69,7 +69,7 @@ func TestVMScraper_ManyCadencedSuccessesSpanSteadyBand(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(t, store, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.spreadFraction = 2.0 / 3 @@ -116,7 +116,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } store := &mockStore{vms: vms} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour units := make([]float64, numVMs) @@ -150,7 +150,7 @@ func TestVMScraper_MassFirstInsertSpansNewVMIndexReportWindow(t *testing.T) { func TestVMScraper_SingleInsertUsesNewVMIndexReportSpread(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns", "only", 1)}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = func() float64 { return 0.5 } @@ -166,7 +166,7 @@ func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { makeVM("ns", "a", 1), makeVM("ns", "b", 2), }} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, store, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = 20 s.randFloat64 = sequencedRand([]float64{0.25, 0.75}) @@ -180,7 +180,7 @@ func TestVMScraper_MultiInsertUsesNewVMIndexReportSpread(t *testing.T) { func TestVMScraper_RetryPathDoesNotUseSteadyBand(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{makeVM("ns1", "vm-a", 100)}} client := &mockProtocolClient{errQueue: []error{vsockclient.ErrNotReady}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, client) + s, clock := newTestScraper(t, store, &mockDialer{}, client) // Insert due now; a later unit of 1 would be full steadyWidth if cadence used RNG. s.randFloat64 = sequencedRand([]float64{0, 1}) @@ -194,7 +194,7 @@ func TestVMScraper_RetryPathDoesNotUseSteadyBand(t *testing.T) { func TestVMScraper_NACKPathDoesNotUseSteadyBand(t *testing.T) { vm := makeVM("ns1", "vm-a", 100) store := &mockStore{vms: []*virtualmachine.Info{vm}} - s, clock := newTestScraper(t, store, &mockSender{}, &mockDialer{}, &mockProtocolClient{ + s, clock := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{ resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, }) s.randFloat64 = sequencedRand([]float64{0, 1}) @@ -213,7 +213,7 @@ func TestVMScraper_DuePileIsPacedByStartBudget(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.interval = time.Hour s.tickInterval = defaultTickInterval @@ -254,7 +254,7 @@ func TestVMScraper_AllDueClumpDrainsWithinIndexWindow(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -298,7 +298,7 @@ func TestVMScraper_SpreadDuePileStartsAtFleetRate(t *testing.T) { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(100+i))) } dialer := &recordingDialer{} - s, _ := newTestScraper(t, &mockStore{vms: vms}, &mockSender{}, dialer, &safeProtocolClient{token: "1"}) + s, _ := newTestScraper(t, &mockStore{vms: vms}, dialer, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.tickInterval = defaultTickInterval s.reconcileEvery = reconcilePeriod(s.interval) @@ -336,7 +336,7 @@ func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { for i := range numVMs { vms = append(vms, makeVM("ns", fmt.Sprintf("vm-%d", i), uint32(10+i))) } - s, clock := newTestScraper(t, &mockStore{vms: vms}, &safeSender{}, &mockDialer{}, &safeProtocolClient{token: "1"}) + s, clock := newTestScraper(t, &mockStore{vms: vms}, &mockDialer{}, &safeProtocolClient{token: "1"}) s.concurrency = numVMs s.reconcileEvery = reconcilePeriod(s.interval) @@ -359,7 +359,7 @@ func TestVMScraper_StartsPerTickObservesLaunchCount(t *testing.T) { } func TestVMScraper_StartsPerTickSkipsIdleTicks(t *testing.T) { - s, clock := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, clock := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) now := clock.Now() concurrency.WithLock(&s.mu, func() { s.lastReconcile = now @@ -409,7 +409,7 @@ func TestVMScraper_WarnIfSpreadSaturated(t *testing.T) { log = &logging.LoggerImpl{InnerLogger: zap.New(core).Sugar()} t.Cleanup(func() { log = orig }) - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) s.tickInterval = tc.tickInterval s.spreadFraction = tc.spreadFraction s.warnIfSpreadSaturated(tc.numVMs) @@ -420,7 +420,7 @@ func TestVMScraper_WarnIfSpreadSaturated(t *testing.T) { } func TestVMScraper_ForwardInterarrivalObservesAfterFirst(t *testing.T) { - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) before := histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds) s.observeForwardInterarrival() assert.Equal(t, before, histogramSampleCount(t, metrics.PullForwardInterarrivalSeconds), diff --git a/sensor/common/virtualmachine/vmscraper/stats_test.go b/sensor/common/virtualmachine/vmscraper/stats_test.go index b89818699eabe..91664565cf591 100644 --- a/sensor/common/virtualmachine/vmscraper/stats_test.go +++ b/sensor/common/virtualmachine/vmscraper/stats_test.go @@ -95,7 +95,7 @@ func TestVMScraper_Stats(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - s, _ := newTestScraper(t, &mockStore{}, &mockSender{}, &mockDialer{}, &mockProtocolClient{}) + s, _ := newTestScraper(t, &mockStore{}, &mockDialer{}, &mockProtocolClient{}) tc.setup(s) stats := s.Stats() diff --git a/sensor/kubernetes/fake/common.go b/sensor/kubernetes/fake/common.go index 92e0e453550d5..d91426c4182da 100644 --- a/sensor/kubernetes/fake/common.go +++ b/sensor/kubernetes/fake/common.go @@ -1,7 +1,6 @@ package fake import ( - "fmt" "math/rand" "time" @@ -13,14 +12,6 @@ func newUUID() types.UID { return types.UID(uuid.NewV4().String()) } -// fakeVMUUID generates a deterministic UUID-like string from an index. -// This ensures the same index always produces the same ID, and the ID -// is a valid UUID format that Central will accept. -// Format: 00000000-0000-4000-8000-{12-digit-index} -func fakeVMUUID(index int) string { - return fmt.Sprintf("00000000-0000-4000-8000-%012d", index) -} - const charset = "abcdef0123456789" func randStringWithLength(n int) string { diff --git a/sensor/kubernetes/fake/fake.go b/sensor/kubernetes/fake/fake.go index 000e4e63a69bc..efa58b0412389 100644 --- a/sensor/kubernetes/fake/fake.go +++ b/sensor/kubernetes/fake/fake.go @@ -15,16 +15,13 @@ import ( routeVersioned "github.com/openshift/client-go/route/clientset/versioned" "github.com/stackrox/rox/pkg/concurrency" "github.com/stackrox/rox/pkg/env" - "github.com/stackrox/rox/pkg/fixtures/vmindexreport" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/sync" vmPkg "github.com/stackrox/rox/pkg/virtualmachine" "github.com/stackrox/rox/sensor/common" "github.com/stackrox/rox/sensor/common/networkflow/manager" "github.com/stackrox/rox/sensor/common/signal" - "github.com/stackrox/rox/sensor/common/virtualmachine/index" "github.com/stackrox/rox/sensor/kubernetes/client" - vmStore "github.com/stackrox/rox/sensor/kubernetes/listener/resources/virtualmachine/store" "go.yaml.in/yaml/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -42,49 +39,8 @@ const ( workloadPath = "/var/scale/stackrox/workload.yaml" defaultNamespaceNum = 30 - - // Starting CID for VM population. This is used as a part of the name and its value does not matter - // as long as it is unique and different than 0, 1, and 2 (reserved values). - vmBaseVSOCKCID = uint32(1000) - - // reportGeneratorSeed is the seed for deterministic package selection in VM index reports. - reportGeneratorSeed = int64(42) ) -// vmReadiness encapsulates the three readiness signals needed before VM workload can start -type vmReadiness struct { - handlerReady concurrency.Signal - storeReady concurrency.Signal - centralReady concurrency.Signal -} - -func newVMReadiness() *vmReadiness { - return &vmReadiness{ - handlerReady: concurrency.NewSignal(), - storeReady: concurrency.NewSignal(), - centralReady: concurrency.NewSignal(), - } -} - -func (r *vmReadiness) signalHandlerReady() { r.handlerReady.Signal() } -func (r *vmReadiness) signalStoreReady() { r.storeReady.Signal() } -func (r *vmReadiness) signalCentralReady() { r.centralReady.Signal() } -func (r *vmReadiness) resetCentralReady() { r.centralReady.Reset() } - -// Wait blocks until all three signals are ready. Returns true if all ready, false if context cancelled. -func (r *vmReadiness) Wait(ctx context.Context) bool { - if !concurrency.WaitInContext(&r.handlerReady, ctx) { - return false - } - if !concurrency.WaitInContext(&r.storeReady, ctx) { - return false - } - if !concurrency.WaitInContext(&r.centralReady, ctx) { - return false - } - return true -} - var ( log = logging.LoggerForModule() ) @@ -157,15 +113,10 @@ type WorkloadManager struct { dockerSecretNamespaces []string // signals services - servicesInitialized concurrency.Signal - processes signal.Pipeline - networkManager manager.Manager - vmIndexReportHandler index.Handler - vmStore *vmStore.VirtualMachineStore - pubSubDispatcher common.PubSubDispatcher - - // VM readiness coordinator - vmPrerequisitesReady *vmReadiness + servicesInitialized concurrency.Signal + processes signal.Pipeline + networkManager manager.Manager + pubSubDispatcher common.PubSubDispatcher // shutdown coordination shutdownCtx context.Context @@ -281,19 +232,18 @@ func NewWorkloadManager(config *WorkloadManagerConfig) *WorkloadManager { } shutdownCtx, shutdownCancel := context.WithCancel(context.Background()) mgr := &WorkloadManager{ - db: db, - workload: &workload, - originatorCache: NewOriginatorCache(), - labelsPool: config.labelsPool, - endpointPool: config.endpointPool, - ipPool: config.ipPool, - externalIpPool: config.externalIpPool, - containerPool: config.containerPool, - processPool: config.processPool, - servicesInitialized: concurrency.NewSignal(), - vmPrerequisitesReady: newVMReadiness(), - shutdownCtx: shutdownCtx, - shutdownCancel: shutdownCancel, + db: db, + workload: &workload, + originatorCache: NewOriginatorCache(), + labelsPool: config.labelsPool, + endpointPool: config.endpointPool, + ipPool: config.ipPool, + externalIpPool: config.externalIpPool, + containerPool: config.containerPool, + processPool: config.processPool, + servicesInitialized: concurrency.NewSignal(), + shutdownCtx: shutdownCtx, + shutdownCancel: shutdownCancel, } mgr.initializePreexistingResources() @@ -332,37 +282,11 @@ func (w *WorkloadManager) SetSignalHandlers(processPipeline signal.Pipeline, net w.servicesInitialized.Signal() } -// SetVMIndexReportHandler sets the handler that will accept VM index reports -func (w *WorkloadManager) SetVMIndexReportHandler(handler index.Handler) { - w.vmIndexReportHandler = handler - w.vmPrerequisitesReady.signalHandlerReady() -} - -// SetVMStore sets the VirtualMachineStore -func (w *WorkloadManager) SetVMStore(store *vmStore.VirtualMachineStore) { - log.Debugf("SetVMStore called: store=%p, poolSize=%d", store, w.workload.VirtualMachineWorkload.PoolSize) - w.vmStore = store - w.vmPrerequisitesReady.signalStoreReady() - log.Debugf("SetVMStore completed (VMs will be populated by informer events)") -} - // SetPubSubDispatcher sets the pub/sub dispatcher for publishing synthetic file activities func (w *WorkloadManager) SetPubSubDispatcher(dispatcher common.PubSubDispatcher) { w.pubSubDispatcher = dispatcher } -// Notify implements common.Notifiable to receive Sensor component event notifications -func (w *WorkloadManager) Notify(e common.SensorComponentEvent) { - switch e { - case common.SensorComponentEventCentralReachable: - log.Debugf("WorkloadManager: Central is reachable, signaling VM workload can start") - w.vmPrerequisitesReady.signalCentralReady() - case common.SensorComponentEventOfflineMode: - log.Debugf("WorkloadManager: Central went offline, resetting reachability signal") - w.vmPrerequisitesReady.resetCentralReady() - } -} - // Stop gracefully stops all background goroutines managed by WorkloadManager. // This should be called before shutting down the process pipeline to prevent // sending signals on closed channels. @@ -547,27 +471,11 @@ func (w *WorkloadManager) initializePreexistingResources() { } // Start VirtualMachine/VirtualMachineInstance workload if configured. - // This unified workload handles both informer events AND index reports. - // Index reports are only sent while VMs are "alive" in the lifecycle. if w.workload.VirtualMachineWorkload.PoolSize > 0 { - // Initialize report generator if index reports are enabled - var reportGen *vmindexreport.Generator - if w.workload.VirtualMachineWorkload.ReportInterval > 0 { - reportGen = vmindexreport.NewGeneratorWithSeed( - w.workload.VirtualMachineWorkload.NumPackages, - reportGeneratorSeed, - ) - log.Infof("VM index reports enabled: interval=%s, packages=%d", - w.workload.VirtualMachineWorkload.ReportInterval, - w.workload.VirtualMachineWorkload.NumPackages) - } - - // Fork management of VM/VMI resources (including index reports if enabled) workload := w.workload.VirtualMachineWorkload for i := range workload.PoolSize { w.wg.Add(1) - cid := vmBaseVSOCKCID + uint32(i) - go w.manageVirtualMachine(w.shutdownCtx, workload, cid, reportGen) + go w.manageVirtualMachine(w.shutdownCtx, workload, i) } } } diff --git a/sensor/kubernetes/fake/virtualmachines.go b/sensor/kubernetes/fake/virtualmachines.go index 85aa743087d8c..d5ceb5cd40f5d 100644 --- a/sensor/kubernetes/fake/virtualmachines.go +++ b/sensor/kubernetes/fake/virtualmachines.go @@ -7,12 +7,10 @@ import ( "strings" "time" - "github.com/stackrox/rox/pkg/fixtures/vmindexreport" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" kubeVirtV1 "kubevirt.io/api/core/v1" ) @@ -25,12 +23,6 @@ func setNestedField(obj *unstructured.Unstructured, value interface{}, fields .. const ( defaultVMLifecycleDuration = 30 * time.Minute defaultVMUpdateInterval = 3 * time.Minute - // initialReportMaxDeviation is the maximum relative deviation applied to the initial report delay. - // With 0.2 (20%), a 30s delay will vary uniformly between 24s-36s. - initialReportMaxDeviation = 0.2 - // reportIntervalMaxDeviation is the maximum relative deviation applied to report intervals. - // With 0.05 (5%), a 60s interval will vary uniformly between 57s-63s. - reportIntervalMaxDeviation = 0.05 // lifecycleMaxDeviation is the maximum relative deviation applied to VM lifecycle duration. // With 0.5 (50%), a 60s lifecycle will vary uniformly between 30s-90s. lifecycleMaxDeviation = 0.5 @@ -91,33 +83,15 @@ func validateVMWorkload(workload VirtualMachineWorkload) (VirtualMachineWorkload return workload, fmt.Errorf("%s. %s may cause some VMs to never receive an update. %s", lifecycleText, causeText("updateInterval", workload.UpdateInterval), actionText("updateInterval")) } - if workload.ReportInterval > 0 { - if workload.ReportInterval > upperBoundVMLifetime { - return workload, fmt.Errorf("%s. %s causes the workload to never send any index reports. %s", - lifecycleText, causeText("reportInterval", workload.ReportInterval), actionText("reportInterval")) - } - if workload.ReportInterval > lowerBoundVMLifetime { - return workload, fmt.Errorf("%s. %s may cause some VMs to never send any index reports. %s", - lifecycleText, causeText("reportInterval", workload.ReportInterval), actionText("reportInterval")) - } - if workload.InitialReportDelay > upperBoundVMLifetime { - return workload, fmt.Errorf("%s. %s causes the workload to never send any index reports. %s", - lifecycleText, causeText("initialReportDelay", workload.InitialReportDelay), actionText("initialReportDelay")) - } - if workload.InitialReportDelay > lowerBoundVMLifetime { - return workload, fmt.Errorf("%s. %s may cause some VMs to never send any index reports. %s", - lifecycleText, causeText("initialReportDelay", workload.InitialReportDelay), actionText("initialReportDelay")) - } - } return workload, nil } -func getRandomVMPair(vsockCID uint32, guestOSes []string) (*unstructured.Unstructured, *unstructured.Unstructured) { - // Use deterministic UUID based on template index to match index report generation. - // This ensures the VM UID in informer events matches the VM ID used in index reports. - vmUID := types.UID(fakeVMUUID(int(vsockCID))) - vmName := fmt.Sprintf("%s-%d", "vm", vsockCID) - os := guestOSes[int(vsockCID)%len(guestOSes)] +func getRandomVMPair(slot int, guestOSes []string) (*unstructured.Unstructured, *unstructured.Unstructured) { + // A new UID per lifecycle: Central REMOVE/CREATE and the scraper's + // UID-change path both treat a recycled slot as a new VM. + vmUID := newUUID() + vmName := fmt.Sprintf("vm-%d", slot) + os := guestOSes[slot%len(guestOSes)] vm := &kubeVirtV1.VirtualMachine{ TypeMeta: metav1.TypeMeta{ @@ -139,10 +113,8 @@ func getRandomVMPair(vsockCID uint32, guestOSes []string) (*unstructured.Unstruc }, } - // VMI gets a unique UUID based on template index and iteration - // Format: 00000000-0000-4000-9000-{12-digit-vsockCID} - vmiUID := types.UID(fmt.Sprintf("00000000-0000-4000-9000-%012d", vsockCID)) - vmiName := fmt.Sprintf("vm-%d-vmi", vsockCID) + vmiUID := newUUID() + vmiName := fmt.Sprintf("vm-%d-vmi", slot) vmi := &kubeVirtV1.VirtualMachineInstance{ TypeMeta: metav1.TypeMeta{ Kind: "VirtualMachineInstance", @@ -167,8 +139,7 @@ func getRandomVMPair(vsockCID uint32, guestOSes []string) (*unstructured.Unstruc }, }, Status: kubeVirtV1.VirtualMachineInstanceStatus{ - Phase: kubeVirtV1.Running, - VSOCKCID: &vsockCID, + Phase: kubeVirtV1.Running, GuestOSInfo: kubeVirtV1.VirtualMachineInstanceGuestOSInfo{ Name: os, }, @@ -227,8 +198,7 @@ func jsonSafeUint64(value uint64) int64 { func (w *WorkloadManager) manageVirtualMachine( ctx context.Context, workload VirtualMachineWorkload, - vsockCID uint32, - reportGen *vmindexreport.Generator, + slot int, ) { defer w.wg.Done() @@ -237,20 +207,17 @@ func (w *WorkloadManager) manageVirtualMachine( if ctx.Err() != nil { return } - vm, vmi := getRandomVMPair(vsockCID, defaultGuestOSPool) - w.runVMLifecycle(ctx, workload, vsockCID, vm, vmi, reportGen) + vm, vmi := getRandomVMPair(slot, defaultGuestOSPool) + w.runVMLifecycle(ctx, workload, vm, vmi) } } // runVMLifecycle runs a single VM/VMI lifecycle. // It blocks until the lifecycle ends (timer fires) or context is cancelled. -// If index reports are enabled (reportGen != nil), it sends index reports while the VM is alive. func (w *WorkloadManager) runVMLifecycle( ctx context.Context, workload VirtualMachineWorkload, - vsockCID uint32, vm, vmi *unstructured.Unstructured, - reportGen *vmindexreport.Generator, ) { lifecycleTimer := newTimerWithJitter(randomizedInterval(workload.LifecycleDuration, lifecycleMaxDeviation)) defer lifecycleTimer.Stop() @@ -273,27 +240,6 @@ func (w *WorkloadManager) runVMLifecycle( // Continue even if VMI creation fails } - // Start index report generation if enabled (runs while VM is alive) - var reportCancel context.CancelFunc - if reportGen != nil && workload.ReportInterval > 0 { - var reportCtx context.Context - reportCtx, reportCancel = context.WithCancel(ctx) - go w.sendIndexReportsWhileAlive( - reportCtx, - reportGen, - vsockCID, - workload.ReportInterval, - workload.InitialReportDelay, - ) - } - - // Ensure report generation stops when this function exits - defer func() { - if reportCancel != nil { - reportCancel() - } - }() - for { select { case <-ctx.Done(): @@ -329,74 +275,20 @@ func (w *WorkloadManager) runVMLifecycle( } } -// sendIndexReportsWhileAlive sends index reports for a VM at the configured interval. -// It waits for prerequisites (handler, store, central) before starting, then runs -// until the context is cancelled (when the VM lifecycle ends). -func (w *WorkloadManager) sendIndexReportsWhileAlive( - ctx context.Context, - reportGen *vmindexreport.Generator, - vsockCID uint32, - interval time.Duration, - initialDelay time.Duration, -) { - // Wait for all prerequisites before sending reports - if !w.vmPrerequisitesReady.Wait(ctx) { - log.Debugf("Prerequisites not ready to start sending fake index reports") - return - } - - log.Debugf("Starting index report generation for a VM (vsockCID=%d, interval=%s, initialDelay=%s)", vsockCID, interval, initialDelay) - - reportTicker := time.NewTicker(randomizedInterval(interval, reportIntervalMaxDeviation)) - if initialDelay > 0 { - firstPeriod := randomizedInterval(initialDelay, initialReportMaxDeviation) - reportTicker.Reset(firstPeriod) - } else { - w.sendOneIndexReport(ctx, reportGen, vsockCID) - } - defer reportTicker.Stop() - - for { - select { - case <-ctx.Done(): - log.Debugf("Stopping index report generation for VM with vsockCID %d (lifecycle ended)", vsockCID) - return - case <-reportTicker.C: - reportTicker.Reset(randomizedInterval(interval, reportIntervalMaxDeviation)) - w.sendOneIndexReport(ctx, reportGen, vsockCID) - } - } +// HasFakeVMWorkload reports whether this manager is simulating KubeVirt VMs. +func (w *WorkloadManager) HasFakeVMWorkload() bool { + return w != nil && w.workload != nil && w.workload.VirtualMachineWorkload.PoolSize > 0 } -// sendOneIndexReport generates and sends a single index report for a VM. -func (w *WorkloadManager) sendOneIndexReport( - ctx context.Context, - reportGen *vmindexreport.Generator, - vsockCID uint32, -) { - if ctx.Err() != nil { - return - } - - if w.vmIndexReportHandler == nil { - log.Debugf("VM index report handler not set, skipping report for VM %d", vsockCID) - return - } - if w.vmStore == nil { - log.Debugf("VM store not set, skipping report for VM %d", vsockCID) - return - } - - vm := w.vmStore.GetFromCID(vsockCID) - if vm == nil { - log.Debugf("VM with vsockCID %d not found in store, skipping index report", vsockCID) - return - } +// HasFakeVMIndexReports reports whether the fake agent should serve index reports. +func (w *WorkloadManager) HasFakeVMIndexReports() bool { + return w.HasFakeVMWorkload() && w.workload.VirtualMachineWorkload.ReportInterval > 0 +} - if err := w.vmIndexReportHandler.Send(ctx, vm, reportGen.GenerateV4IndexReport()); err != nil { - // Don't log errors during shutdown - if ctx.Err() == nil { - log.Debugf("Failed to send index report for VM %d: %v", vsockCID, err) - } +// FakeVMNumPackages is the package count used in generated fake index reports. +func (w *WorkloadManager) FakeVMNumPackages() int { + if w == nil || w.workload == nil { + return 0 } + return w.workload.VirtualMachineWorkload.NumPackages } diff --git a/sensor/kubernetes/fake/virtualmachines_test.go b/sensor/kubernetes/fake/virtualmachines_test.go index 74485e3f5e14b..4e43e91a2ed03 100644 --- a/sensor/kubernetes/fake/virtualmachines_test.go +++ b/sensor/kubernetes/fake/virtualmachines_test.go @@ -1,12 +1,10 @@ package fake import ( - "fmt" "math" "testing" "time" - "github.com/stackrox/rox/pkg/fixtures/vmindexreport" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -105,32 +103,16 @@ func TestValidateVMWorkload(t *testing.T) { `Setting "updateInterval"=1m40s causes none of the VMs to ever receive an update. ` + `Lower the value of "updateInterval" or increase the 'lifecycleDuration'.`, }, - "reportInterval in jitter range warns": { + "reportInterval does not affect lifecycle validation": { input: VirtualMachineWorkload{ PoolSize: 5, LifecycleDuration: 60 * time.Second, // bounds: 30s-90s UpdateInterval: 20 * time.Second, // < lower bound, OK - ReportInterval: 45 * time.Second, // in range + ReportInterval: 45 * time.Second, }, wantLifecycleDuration: 60 * time.Second, wantUpdateInterval: 20 * time.Second, - wantErr: `The VM will live for a random duration between 30s and 1m30s. ` + - `Setting "reportInterval"=45s may cause some VMs to never send any index reports. ` + - `Lower the value of "reportInterval" or increase the 'lifecycleDuration'.`, - }, - "initialReportDelay in jitter range warns": { - input: VirtualMachineWorkload{ - PoolSize: 5, - LifecycleDuration: 60 * time.Second, // bounds: 30s-90s - UpdateInterval: 20 * time.Second, // < lower bound, OK - ReportInterval: 20 * time.Second, // < lower bound, OK - InitialReportDelay: 45 * time.Second, // in range - }, - wantLifecycleDuration: 60 * time.Second, - wantUpdateInterval: 20 * time.Second, - wantErr: `The VM will live for a random duration between 30s and 1m30s. ` + - `Setting "initialReportDelay"=45s may cause some VMs to never send any index reports. ` + - `Lower the value of "initialReportDelay" or increase the 'lifecycleDuration'.`, + wantErr: "", }, } @@ -151,41 +133,6 @@ func TestValidateVMWorkload(t *testing.T) { } } -func TestGenerateFakeIndexReport(t *testing.T) { - gen := vmindexreport.NewGeneratorWithSeed(10, 42) // 10 packages, seed=42 for reproducibility - tests := map[string]uint32{ - "basic report": 1234, - "different VM": 9999, - } - for name, vsockCID := range tests { - t.Run(name, func(t *testing.T) { - report := gen.GenerateV1IndexReport(vsockCID) - - // Verify vsockCID is set as string - assert.Equal(t, fmt.Sprintf("%d", vsockCID), report.GetVsockCid(), "vsockCID mismatch") - - // Verify index report structure - require.NotNil(t, report.GetIndexV4(), "IndexV4 should not be nil") - assert.Equal(t, "IndexFinished", report.GetIndexV4().GetState(), "State mismatch") - assert.True(t, report.GetIndexV4().GetSuccess(), "Success should be true") - - // Verify contents - require.NotNil(t, report.GetIndexV4().GetContents(), "Contents should not be nil") - assert.Len(t, report.GetIndexV4().GetContents().GetPackages(), 10, "expected 10 packages") - assert.Len(t, report.GetIndexV4().GetContents().GetRepositories(), 3, "expected 3 real repositories") - - // Verify packages have valid CPEs (regression test for WFN error) - for _, pkg := range report.GetIndexV4().GetContents().GetPackages() { - assert.NotEmpty(t, pkg.GetCpe(), "package CPE should not be empty") - assert.Contains(t, pkg.GetCpe(), "cpe:2.3:", "package CPE should be valid format") - if pkg.GetSource() != nil { - assert.NotEmpty(t, pkg.GetSource().GetCpe(), "source package CPE should not be empty") - } - } - }) - } -} - func TestToUnstructuredVMI(t *testing.T) { tests := map[string]struct { vsockCID *uint32 @@ -253,3 +200,89 @@ func TestToUnstructuredVMI(t *testing.T) { }) } } + +func TestGetRandomVMPair_OmitsVSOCKCID(t *testing.T) { + _, vmi := getRandomVMPair(3, defaultGuestOSPool) + _, found, err := unstructured.NestedInt64(vmi.Object, "status", "vsockCID") + require.NoError(t, err) + assert.False(t, found) +} + +func TestGetRandomVMPair_NewUIDEachLifecycle(t *testing.T) { + vm1, vmi1 := getRandomVMPair(3, defaultGuestOSPool) + vm2, vmi2 := getRandomVMPair(3, defaultGuestOSPool) + + require.NotEmpty(t, vm1.GetUID()) + require.Len(t, vmi1.GetOwnerReferences(), 1) + assert.Equal(t, vm1.GetUID(), vmi1.GetOwnerReferences()[0].UID) + assert.NotEqual(t, vm1.GetUID(), vmi1.GetUID()) + + assert.NotEqual(t, vm1.GetUID(), vm2.GetUID(), "same slot must not reuse a VM UID across lifecycles") + assert.NotEqual(t, vmi1.GetUID(), vmi2.GetUID(), "same slot must not reuse a VMI UID across lifecycles") +} + +func TestWorkloadManager_HasFakeVMWorkload(t *testing.T) { + tests := map[string]struct { + mgr *WorkloadManager + want bool + }{ + "nil manager": {}, + "nil workload": { + mgr: &WorkloadManager{}, + }, + "zero pool": { + mgr: &WorkloadManager{workload: &Workload{}}, + }, + "pool set": { + mgr: &WorkloadManager{workload: &Workload{VirtualMachineWorkload: VirtualMachineWorkload{PoolSize: 3}}}, + want: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.mgr.HasFakeVMWorkload()) + }) + } +} + +func TestWorkloadManager_HasFakeVMIndexReports(t *testing.T) { + tests := map[string]struct { + mgr *WorkloadManager + want bool + }{ + "nil manager": {}, + "pool without reports": { + mgr: &WorkloadManager{workload: &Workload{VirtualMachineWorkload: VirtualMachineWorkload{PoolSize: 3}}}, + }, + "pool with reports": { + mgr: &WorkloadManager{workload: &Workload{VirtualMachineWorkload: VirtualMachineWorkload{ + PoolSize: 3, + ReportInterval: time.Minute, + }}}, + want: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.mgr.HasFakeVMIndexReports()) + }) + } +} + +func TestWorkloadManager_FakeVMNumPackages(t *testing.T) { + tests := map[string]struct { + mgr *WorkloadManager + want int + }{ + "nil manager": {}, + "packages set": { + mgr: &WorkloadManager{workload: &Workload{VirtualMachineWorkload: VirtualMachineWorkload{NumPackages: 700}}}, + want: 700, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.mgr.FakeVMNumPackages()) + }) + } +} diff --git a/sensor/kubernetes/fake/vmagent/client.go b/sensor/kubernetes/fake/vmagent/client.go new file mode 100644 index 0000000000000..6532fecb7f1f2 --- /dev/null +++ b/sensor/kubernetes/fake/vmagent/client.go @@ -0,0 +1,68 @@ +package vmagent + +import ( + "context" + "fmt" + "io" + + pb "github.com/stackrox/rox/generated/internalapi/virtualmachine/v1" + "github.com/stackrox/rox/pkg/fixtures/vmindexreport" + "github.com/stackrox/rox/sensor/common/virtualmachine/vmscraper" + "github.com/stackrox/rox/sensor/common/virtualmachine/vsockclient" +) + +var _ vmscraper.ProtocolClient = (*Client)(nil) + +const ( + reportGeneratorSeed = int64(42) + agentVersion = "fake" +) + +// Client is a ProtocolClient that returns generated v4 reports without speaking vsock framing. +type Client struct { + gen *vmindexreport.Generator + token string + enabled bool +} + +// NewClient returns a Client. When enabled is false, GetReport always returns Unchanged. +func NewClient(numPackages int, enabled bool) *Client { + c := &Client{enabled: enabled} + if !enabled { + return c + } + c.gen = vmindexreport.NewGeneratorWithSeed(numPackages, reportGeneratorSeed) + c.token = fmt.Sprintf("fake-%d", numPackages) + return c +} + +// GetReport ignores stream and returns a generated report, or Unchanged when the token matches. +func (c *Client) GetReport(ctx context.Context, _ io.ReadWriteCloser, lastKnownToken string) (*vsockclient.GetReportResult, error) { + if err := ctx.Err(); err != nil { + return nil, fmt.Errorf("fake vm get report: %w", err) + } + if !c.enabled { + return &vsockclient.GetReportResult{Unchanged: true}, nil + } + if lastKnownToken != "" && lastKnownToken == c.token { + return &vsockclient.GetReportResult{ + Unchanged: true, + Meta: &pb.ResponseMeta{ReportToken: c.token, AgentVersion: agentVersion}, + }, nil + } + return &vsockclient.GetReportResult{ + IndexReport: c.gen.GenerateV4IndexReport(), + Meta: &pb.ResponseMeta{ + ReportToken: c.token, + AgentVersion: agentVersion, + }, + }, nil +} + +// SyncRepoCPEMapping is a no-op: fake reports do not advertise a Sensor-managed mapping. +func (c *Client) SyncRepoCPEMapping(ctx context.Context, _ io.ReadWriteCloser, _ []byte) (bool, *pb.ResponseMeta, error) { + if err := ctx.Err(); err != nil { + return false, nil, fmt.Errorf("fake vm sync mapping: %w", err) + } + return false, nil, nil +} diff --git a/sensor/kubernetes/fake/vmagent/client_test.go b/sensor/kubernetes/fake/vmagent/client_test.go new file mode 100644 index 0000000000000..e3bbfe4730b35 --- /dev/null +++ b/sensor/kubernetes/fake/vmagent/client_test.go @@ -0,0 +1,128 @@ +package vmagent_test + +import ( + "context" + "testing" + + "github.com/stackrox/rox/sensor/kubernetes/fake/vmagent" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_GetReport(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + numPackages int + enabled bool + lastKnownToken string + wantUnchanged bool + wantPackages int + wantAgent string + }{ + "disabled returns unchanged": { + numPackages: 10, + enabled: false, + wantUnchanged: true, + }, + "empty token returns full report": { + numPackages: 7, + enabled: true, + wantPackages: 7, + wantAgent: "fake", + }, + "mismatched token returns full report": { + numPackages: 7, + enabled: true, + lastKnownToken: "stale", + wantPackages: 7, + wantAgent: "fake", + }, + "zero packages still returns a report": { + numPackages: 0, + enabled: true, + wantPackages: 0, + wantAgent: "fake", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + result, err := vmagent.NewClient(tt.numPackages, tt.enabled).GetReport(t.Context(), nil, tt.lastKnownToken) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, tt.wantUnchanged, result.Unchanged) + + if tt.wantUnchanged { + assert.Nil(t, result.IndexReport) + return + } + + require.NotNil(t, result.IndexReport) + assert.Equal(t, tt.wantPackages, len(result.IndexReport.GetContents().GetPackages())) + require.NotNil(t, result.Meta) + assert.NotEmpty(t, result.Meta.GetReportToken()) + assert.Equal(t, tt.wantAgent, result.Meta.GetAgentVersion()) + }) + } +} + +func TestClient_GetReport_MatchingTokenReturnsUnchanged(t *testing.T) { + t.Parallel() + + client := vmagent.NewClient(7, true) + first, err := client.GetReport(t.Context(), nil, "") + require.NoError(t, err) + require.NotNil(t, first) + require.NotEmpty(t, first.Meta.GetReportToken()) + + second, err := client.GetReport(t.Context(), nil, first.Meta.GetReportToken()) + require.NoError(t, err) + require.NotNil(t, second) + assert.True(t, second.Unchanged) + assert.Nil(t, second.IndexReport) +} + +func TestClient_GetReport_TokenStableAcrossFullReports(t *testing.T) { + t.Parallel() + + client := vmagent.NewClient(7, true) + a, err := client.GetReport(t.Context(), nil, "stale") + require.NoError(t, err) + require.NotNil(t, a) + b, err := client.GetReport(t.Context(), nil, "stale") + require.NoError(t, err) + require.NotNil(t, b) + assert.Equal(t, a.Meta.GetReportToken(), b.Meta.GetReportToken()) +} + +func TestClient_GetReport_CancelledContext(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + _, err := vmagent.NewClient(7, true).GetReport(ctx, nil, "") + assert.ErrorIs(t, err, context.Canceled) +} + +func TestDialThenGetReport(t *testing.T) { + t.Parallel() + + stream, err := vmagent.NewDialer().Dial(t.Context(), "default", "vm-0", 1, true) + require.NoError(t, err) + require.NotNil(t, stream) + t.Cleanup(func() { + if stream != nil { + _ = stream.Close() + } + }) + + result, err := vmagent.NewClient(3, true).GetReport(t.Context(), stream, "") + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.IndexReport) + assert.Equal(t, 3, len(result.IndexReport.GetContents().GetPackages())) +} diff --git a/sensor/kubernetes/fake/vmagent/dialer.go b/sensor/kubernetes/fake/vmagent/dialer.go new file mode 100644 index 0000000000000..030cfe5a0f6da --- /dev/null +++ b/sensor/kubernetes/fake/vmagent/dialer.go @@ -0,0 +1,35 @@ +package vmagent + +import ( + "context" + "fmt" + "io" + + "github.com/stackrox/rox/sensor/common/virtualmachine/vmscraper" +) + +var _ vmscraper.VMDialer = (*Dialer)(nil) + +// Dialer is a VMDialer that returns a nop stream instead of opening VSOCK. +type Dialer struct{} + +// NewDialer returns a Dialer. +func NewDialer() *Dialer { + return &Dialer{} +} + +// Dial returns a nop stream so VMScraper can run its poll path without VSOCK. +func (*Dialer) Dial(ctx context.Context, _, _ string, _ uint32, _ bool) (io.ReadWriteCloser, error) { + if err := ctx.Err(); err != nil { + return nil, fmt.Errorf("fake vm dial: %w", err) + } + return nopStream{}, nil +} + +type nopStream struct{} + +func (nopStream) Read([]byte) (int, error) { return 0, io.EOF } + +func (nopStream) Write(p []byte) (int, error) { return len(p), nil } + +func (nopStream) Close() error { return nil } diff --git a/sensor/kubernetes/fake/vmagent/dialer_test.go b/sensor/kubernetes/fake/vmagent/dialer_test.go new file mode 100644 index 0000000000000..8bd0930298571 --- /dev/null +++ b/sensor/kubernetes/fake/vmagent/dialer_test.go @@ -0,0 +1,39 @@ +package vmagent_test + +import ( + "context" + "io" + "testing" + + "github.com/stackrox/rox/sensor/kubernetes/fake/vmagent" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDialer_Dial_ReturnsCloseableStream(t *testing.T) { + t.Parallel() + + stream, err := vmagent.NewDialer().Dial(t.Context(), "default", "vm-1", 1, true) + require.NoError(t, err) + require.NotNil(t, stream) + + n, err := stream.Write([]byte("ignored")) + assert.NoError(t, err) + assert.Equal(t, len("ignored"), n) + + _, err = stream.Read(make([]byte, 1)) + assert.ErrorIs(t, err, io.EOF) + + require.NoError(t, stream.Close()) + require.NoError(t, stream.Close()) +} + +func TestDialer_Dial_CancelledContext(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + _, err := vmagent.NewDialer().Dial(ctx, "default", "vm-1", 1, true) + assert.ErrorIs(t, err, context.Canceled) +} diff --git a/sensor/kubernetes/fake/vmagent/doc.go b/sensor/kubernetes/fake/vmagent/doc.go new file mode 100644 index 0000000000000..9c1855fce8217 --- /dev/null +++ b/sensor/kubernetes/fake/vmagent/doc.go @@ -0,0 +1,4 @@ +// Package vmagent is an in-process stand-in for roxagent used by fake VM workloads. +// Dial returns a nop stream; GetReport ignores it and returns a generated v4 report +// so VMScraper poll, spread, backoff, and forward run without VSOCK or TLS. +package vmagent diff --git a/sensor/kubernetes/fake/workload.go b/sensor/kubernetes/fake/workload.go index bf00665e71990..eba851290f793 100644 --- a/sensor/kubernetes/fake/workload.go +++ b/sensor/kubernetes/fake/workload.go @@ -91,9 +91,7 @@ type SecretWorkload struct { } // VirtualMachineWorkload defines the workload for VirtualMachine and VirtualMachineInstance CRDs. -// This is the unified config for both VM/VMI informer events AND VM index reports. -// Index reports are only generated when ReportInterval > 0, and they follow the VM lifecycle -// (reports are sent only while the VM is "alive" in the informer simulation). +// ReportInterval > 0 enables the in-process fake agent; VMScraper poll and spread set cadence. type VirtualMachineWorkload struct { // PoolSize is the number of VM/VMI templates to maintain in the pool. // This controls how many unique VMs exist at any given time. @@ -104,13 +102,9 @@ type VirtualMachineWorkload struct { LifecycleDuration time.Duration `yaml:"lifecycleDuration"` // NumLifecycles is the number of times to recreate VMs/VMIs (0 = infinite) NumLifecycles int `yaml:"numLifecycles"` - // InitialReportDelay delays the first index report for each VM by a user-provided duration. - // A ±20% jitter is always applied to spread the initial burst; when unset, the first index - // report is sent immediately (no delay, no jitter) once prerequisites are ready. - InitialReportDelay time.Duration `yaml:"initialReportDelay"` - // ReportInterval is how often each VM sends an index report (0 = no reports). - // Index reports are only sent while the VM is alive in the informer simulation. + // ReportInterval greater than zero enables fake index reports. Cadence is + // VMScraper poll and spread, not this interval. ReportInterval time.Duration `yaml:"reportInterval"` // NumPackages is the number of fake packages to include in each index report NumPackages int `yaml:"numPackages"` diff --git a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go index 4a376b116d252..74a4fd58cc227 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go @@ -15,8 +15,6 @@ var ( type VirtualMachineStore struct { lock sync.RWMutex - cidToID map[uint32]virtualmachine.VMID - idToCID map[virtualmachine.VMID]uint32 namespaceToID map[string]set.Set[virtualmachine.VMID] virtualMachines map[virtualmachine.VMID]*virtualmachine.Info } @@ -26,8 +24,6 @@ func NewVirtualMachineStore() *VirtualMachineStore { return &VirtualMachineStore{ virtualMachines: make(map[virtualmachine.VMID]*virtualmachine.Info), namespaceToID: make(map[string]set.Set[virtualmachine.VMID]), - cidToID: make(map[uint32]virtualmachine.VMID), - idToCID: make(map[virtualmachine.VMID]uint32), } } @@ -82,8 +78,6 @@ func (s *VirtualMachineStore) Cleanup() { defer s.lock.Unlock() clear(s.virtualMachines) clear(s.namespaceToID) - clear(s.cidToID) - clear(s.idToCID) } // OnNamespaceDeleted removes the VirtualMachines in the given namespace. @@ -131,21 +125,7 @@ func (s *VirtualMachineStore) ListRunning() []*virtualmachine.Info { return out } -// GetFromCID returns the VirtualMachineInfo associated with a given VSOCK CID -func (s *VirtualMachineStore) GetFromCID(cid uint32) *virtualmachine.Info { - s.lock.RLock() - defer s.lock.RUnlock() - uid, ok := s.cidToID[cid] - if !ok { - return nil - } - return s.virtualMachines[uid].Copy() -} - func (s *VirtualMachineStore) addOrUpdateNoLock(vm *virtualmachine.Info) { - // Replace VSOCK info - // If the new VirtualMachineInfo (vm) does not have a VSOCK, - // then we use the previous value vm.VSOCKCID = s.replaceVSOCKInfoNoLock(vm) // Upsert the VirtualMachineInfo @@ -171,10 +151,7 @@ func (s *VirtualMachineStore) updateStatusOrCreateNoLock(updateInfo *virtualmach s.addOrUpdateNoLock(updateInfo) return } - // Remove previous VSOCK info - s.removeVSOCKInfoNoLock(prev.ID, prev.VSOCKCID) - // Update new VSOCK maps - prev.VSOCKCID = s.addOrUpdateVSOCKInfoNoLock(updateInfo.ID, updateInfo.VSOCKCID) + prev.VSOCKCID = copyVSOCKCID(updateInfo.VSOCKCID) prev.Running = updateInfo.Running prev.GuestOS = updateInfo.GuestOS prev.IPAddresses = copyStringSlice(updateInfo.IPAddresses) @@ -185,44 +162,22 @@ func (s *VirtualMachineStore) updateStatusOrCreateNoLock(updateInfo *virtualmach prev.CDRomDisks = copyStringSlice(updateInfo.CDRomDisks) } -func (s *VirtualMachineStore) addOrUpdateVSOCKInfoNoLock(id virtualmachine.VMID, vsockCID *uint32) *uint32 { - if vsockCID == nil { +// copyVSOCKCID returns a new pointer so later changes to the caller's value +// cannot change what the store holds. +func copyVSOCKCID(cid *uint32) *uint32 { + if cid == nil { return nil } - s.idToCID[id] = *vsockCID - s.cidToID[*vsockCID] = id - // copy value before return - val := *vsockCID - return &val -} - -func (s *VirtualMachineStore) removeVSOCKInfoNoLock(id virtualmachine.VMID, vsockCID *uint32) { - if vsockCID == nil { - return - } - delete(s.idToCID, id) - delete(s.cidToID, *vsockCID) + return new(*cid) } func (s *VirtualMachineStore) replaceVSOCKInfoNoLock(vm *virtualmachine.Info) *uint32 { - // Remove previous VSOCK info - // This is needed in case the VSOCK value updates - prev, found := s.virtualMachines[vm.ID] - if found { - s.removeVSOCKInfoNoLock(vm.ID, prev.VSOCKCID) - } - // Update VSOCKCID info + prev := s.virtualMachines[vm.ID] + // A VM update may omit CID; keep the last known value. if vm.VSOCKCID == nil && prev != nil { vm.VSOCKCID = prev.VSOCKCID } - // Upsert VSOCKCID info - // CRITICAL: addOrUpdateVSOCKInfoNoLock always returns a heap-allocated copy so the store owns - // its own pointer. Reusing vm.VSOCKCID would let the caller mutate the same pointer later. - // Added regression test: Test_replaceVSOCKInfoNoLockCopiesIncomingPointer. - if vm.VSOCKCID != nil { - return s.addOrUpdateVSOCKInfoNoLock(vm.ID, vm.VSOCKCID) - } - return vm.VSOCKCID + return copyVSOCKCID(vm.VSOCKCID) } func (s *VirtualMachineStore) removeNoLock(id virtualmachine.VMID) { @@ -231,7 +186,6 @@ func (s *VirtualMachineStore) removeNoLock(id virtualmachine.VMID) { return } delete(s.virtualMachines, vm.ID) - s.removeVSOCKInfoNoLock(vm.ID, vm.VSOCKCID) vmIDsByNamespace, found := s.namespaceToID[vm.Namespace] if !found { log.Errorf("namespace %q was not found", vm.Namespace) @@ -248,7 +202,6 @@ func (s *VirtualMachineStore) clearStatusNoLock(id virtualmachine.VMID) { if !ok { return } - s.removeVSOCKInfoNoLock(vm.ID, vm.VSOCKCID) vm.VSOCKCID = nil // If the instance is removed the VirtualMachine will transition to Stopped vm.Running = false diff --git a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go index 712678ad268eb..0c193d0c28e51 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go @@ -285,13 +285,6 @@ func (s *storeSuite) Test_UpdateVirtualMachine() { nsIOs, ok := s.store.namespaceToID[tCase.new.Namespace] s.Assert().True(ok) s.Assert().Len(nsIOs, 1) - if tCase.new.VSOCKCID == nil { - s.Assert().Len(s.store.cidToID, 0) - s.Assert().Len(s.store.idToCID, 0) - } else { - s.Assert().Len(s.store.cidToID, 1) - s.Assert().Len(s.store.idToCID, 1) - } }) } } @@ -451,9 +444,6 @@ func (s *storeSuite) Test_replaceVSOCKInfoNoLockCopiesIncomingPointer() { vsock = 456 s.Equal(uint32(123), *storePtr, "store-managed pointer should remain unchanged even if informer pointer is mutated") - - s.Equal(uint32(123), s.store.idToCID[vmID], "idToCID map should track the original value") - s.Equal(virtualmachine.VMID(vmID), s.store.cidToID[123], "cidToID map should continue to refer back to the VM") } func (s *storeSuite) Test_UpdateStateOrCreate() { @@ -1118,73 +1108,11 @@ func (s *storeSuite) Test_HasVirtualMachine() { }) } -func (s *storeSuite) Test_GetVirtualMachineFromCID() { - cases := map[string]struct { - vm *virtualmachine.Info - cid uint32 - expectedVM *virtualmachine.Info - }{ - "should find a valid CID": { - vm: &virtualmachine.Info{ - ID: vmID, - Name: vmName, - Namespace: vmNamespace, - VSOCKCID: new(uint32(1)), - Running: true, - }, - cid: 1, - expectedVM: &virtualmachine.Info{ - ID: vmID, - Name: vmName, - Namespace: vmNamespace, - VSOCKCID: new(uint32(1)), - Running: true, - }, - }, - "should return nil an invalid CID": { - vm: &virtualmachine.Info{ - ID: vmID, - Name: vmName, - Namespace: vmNamespace, - VSOCKCID: new(uint32(1)), - Running: true, - }, - cid: 2, // Invalid CID - expectedVM: nil, - }, - "should return nil if the VM does not have a Vsock CID yet": { - vm: &virtualmachine.Info{ - ID: vmID, - Name: vmName, - Namespace: vmNamespace, - VSOCKCID: nil, - Running: true, - }, - cid: 1, // VM does not have a cid - expectedVM: nil, - }, - } - for tName, tCase := range cases { - s.Run(tName, func() { - s.store.AddOrUpdate(tCase.vm) - s.assertVM(tCase.vm) - actual := s.store.GetFromCID(tCase.cid) - if tCase.expectedVM == nil { - s.Assert().Nil(actual) - } else { - assertVMs(s.T(), tCase.expectedVM, actual) - } - }) - } -} - func (s *storeSuite) assertEmpty() { s.store.lock.Lock() defer s.store.lock.Unlock() s.Assert().Len(s.store.virtualMachines, 0) s.Assert().Len(s.store.namespaceToID, 0) - s.Assert().Len(s.store.cidToID, 0) - s.Assert().Len(s.store.idToCID, 0) } func (s *storeSuite) assertVM(expected *virtualmachine.Info) { @@ -1201,15 +1129,6 @@ func (s *storeSuite) assertVM(expected *virtualmachine.Info) { nsIDs, ok := s.store.namespaceToID[expected.Namespace] s.Assert().True(ok) s.Assert().Contains(nsIDs, expected.ID) - if expected.VSOCKCID == nil { - return - } - cid, ok := s.store.idToCID[expected.ID] - s.Assert().True(ok) - s.Assert().Equal(*expected.VSOCKCID, cid) - id, ok := s.store.cidToID[*expected.VSOCKCID] - s.Assert().True(ok) - s.Assert().Equal(expected.ID, id) } func assertVMs(t *testing.T, expected *virtualmachine.Info, actual *virtualmachine.Info) { diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index e88ce9b2ca54c..06996112a0c7c 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -48,7 +48,6 @@ import ( "github.com/stackrox/rox/sensor/common/sensor" signalService "github.com/stackrox/rox/sensor/common/signal" "github.com/stackrox/rox/sensor/common/store" - vmIndex "github.com/stackrox/rox/sensor/common/virtualmachine/index" "github.com/stackrox/rox/sensor/common/virtualmachine/vmscraper" "github.com/stackrox/rox/sensor/common/virtualmachine/vsockclient" "github.com/stackrox/rox/sensor/kubernetes/certrefresh" @@ -58,6 +57,7 @@ import ( "github.com/stackrox/rox/sensor/kubernetes/complianceoperator" "github.com/stackrox/rox/sensor/kubernetes/enforcer" "github.com/stackrox/rox/sensor/kubernetes/eventpipeline" + "github.com/stackrox/rox/sensor/kubernetes/fake/vmagent" "github.com/stackrox/rox/sensor/kubernetes/helm" "github.com/stackrox/rox/sensor/kubernetes/listener/resources" "github.com/stackrox/rox/sensor/kubernetes/networkpolicies" @@ -180,32 +180,40 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { manager.NewManager(storeProvider.Entities(), externalsrcs.StoreInstance(), policyDetector, pubSub, internalMessageDispatcher, updatecomputer.New(), manager.WithEnrichTicker(cfg.networkFlowTicker)) enhancer := deploymentenhancer.CreateEnhancer(storeProvider) - var virtualMachineHandler vmIndex.Handler var vmScraper *vmscraper.VMScraper var vmStats clustermetrics.VMStatsSource var repo2CPE *scannerdefinitions.Repo2CPE if features.VirtualMachines.Enabled() { - virtualMachineHandler = vmIndex.NewHandler(storeProvider.VirtualMachines()) - pullMaxBytes := int64(env.VirtualMachinesPullMaxResponseSizeKB.IntegerSetting()) * 1024 - vmDial, err := vsockdialer.NewMultiDialer() - if err != nil { - log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) + var dialer vmscraper.VMDialer + var vmProtoClient vmscraper.ProtocolClient + if cfg.workloadManager != nil && cfg.workloadManager.HasFakeVMWorkload() { + numPackages := cfg.workloadManager.FakeVMNumPackages() + reportsEnabled := cfg.workloadManager.HasFakeVMIndexReports() + dialer = vmagent.NewDialer() + vmProtoClient = vmagent.NewClient(numPackages, reportsEnabled) + log.Infof("VMScraper using in-process fake agent (reports=%t packages=%d)", reportsEnabled, numPackages) } else { - vmProtoClient := vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) - repo2CPE, err = scannerdefinitions.NewRepo2CPE(env.CentralEndpoint.Setting(), cfg.certLoader()) + d, err := vsockdialer.NewMultiDialer() if err != nil { - log.Errorf("Failed to create repo-to-CPE refresher: %v", err) + log.Errorf("VSOCK pull mode disabled: failed to construct dialer: %v", err) + } else { + dialer = d } - // A typed-nil *Repo2CPE is a non-nil Repo2CPEFetcher, so - // maybeSyncRepoCPEMapping would call FetchRepo2CPE on a nil receiver. - var repo2CPEFetcher vmscraper.Repo2CPEFetcher - if repo2CPE != nil { - repo2CPEFetcher = repo2CPE - } - vmScraper = vmscraper.New(storeProvider.VirtualMachines(), virtualMachineHandler, vmDial, vmProtoClient, repo2CPEFetcher) - vmStats = vmScraper + vmProtoClient = vsockclient.NewClient([]string{vsockclient.CapabilityReportV1}, int(pullMaxBytes)) + } + repo2CPE, err = scannerdefinitions.NewRepo2CPE(env.CentralEndpoint.Setting(), cfg.certLoader()) + if err != nil { + log.Errorf("Failed to create repo-to-CPE refresher: %v", err) + } + // A typed-nil *Repo2CPE is a non-nil Repo2CPEFetcher, so + // maybeSyncRepoCPEMapping would call FetchRepo2CPE on a nil receiver. + var repo2CPEFetcher vmscraper.Repo2CPEFetcher + if repo2CPE != nil { + repo2CPEFetcher = repo2CPE } + vmScraper = vmscraper.New(storeProvider.VirtualMachines(), dialer, vmProtoClient, repo2CPEFetcher) + vmStats = vmScraper } components := []common.SensorComponent{ @@ -228,9 +236,6 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { enhancer, complianceService, } - if virtualMachineHandler != nil { - components = append(components, virtualMachineHandler) - } if repo2CPE != nil { components = append(components, repo2CPE) } @@ -305,12 +310,6 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { if cfg.workloadManager != nil { cfg.workloadManager.SetPubSubDispatcher(internalMessageDispatcher) cfg.workloadManager.SetSignalHandlers(processPipeline, networkFlowManager) - if features.VirtualMachines.Enabled() && virtualMachineHandler != nil { - cfg.workloadManager.SetVMIndexReportHandler(virtualMachineHandler) - cfg.workloadManager.SetVMStore(storeProvider.VirtualMachines()) - // Register WorkloadManager as a Notifiable so it receives SensorComponentEvent notifications - s.AddNotifiable(cfg.workloadManager) - } } var networkFlowService service.Service diff --git a/tools/local-sensor/main.go b/tools/local-sensor/main.go index a4a2996402042..2684583170dc6 100644 --- a/tools/local-sensor/main.go +++ b/tools/local-sensor/main.go @@ -487,8 +487,8 @@ func main() { }() } - // CreateSensor will set up the workload manager handlers (SetSignalHandlers, SetVMIndexReportHandler, SetVMStore) - // if workloadManager is not nil and VirtualMachines feature is enabled + // CreateSensor will set up the workload manager handlers (SetSignalHandlers) + // if workloadManager is not nil. s, err := sensor.CreateSensor(sensorConfig) if err != nil { panic(err) From f17ed10fa8aacbe63e50b798e8d8d20464082500 Mon Sep 17 00:00:00 2001 From: David Vail Date: Fri, 4 Sep 2026 14:20:33 -0400 Subject: [PATCH 206/638] chore(ui): Remove legacy platform cves UI when scanner v2 is disabled (#22649) --- .../vulnmanagement/clusterCves.test.js | 100 ------------------ .../vulnmanagement/clusters.test.js | 4 - .../vulnmanagement/dashboard.test.js | 17 --- .../dashboardToEntityPage.test.js | 14 --- .../VulnMgmt/Dashboard/CvesMenu.jsx | 19 ++-- .../Dashboard/VulnMgmtDashboardPage.jsx | 10 +- .../Cluster/VulnMgmtClusterOverview.jsx | 24 +++-- .../Containers/VulnMgmt/Entity/EntityTabs.tsx | 5 +- .../Entity/RelatedEntitiesSideList.tsx | 9 +- .../Entity/WorkflowEntityPageLayout.jsx | 6 +- .../List/Clusters/VulnMgmtListClusters.jsx | 72 +++++++------ .../VulnMgmt/List/WorkflowListPageLayout.jsx | 6 +- .../platform/src/utils/entityRelationships.ts | 10 +- 13 files changed, 96 insertions(+), 200 deletions(-) delete mode 100644 ui/apps/platform/cypress/integration/vulnmanagement/clusterCves.test.js diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/clusterCves.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/clusterCves.test.js deleted file mode 100644 index 26f80dd53bdff..0000000000000 --- a/ui/apps/platform/cypress/integration/vulnmanagement/clusterCves.test.js +++ /dev/null @@ -1,100 +0,0 @@ -import withAuth from '../../helpers/basicAuth'; -import { hasOrchestratorFlavor } from '../../helpers/features'; -import { - assertSortedItems, - callbackForPairOfAscendingNumberValuesFromElements, - callbackForPairOfDescendingNumberValuesFromElements, -} from '../../helpers/sort'; -import { - hasTableColumnHeadings, - interactAndWaitForVulnerabilityManagementEntities, - verifySecondaryEntities, - visitVulnerabilityManagementEntities, -} from './VulnerabilityManagement.helpers'; -import { selectors } from './VulnerabilityManagement.selectors'; - -const entitiesKey = 'cluster-cves'; - -describe('Vulnerability Management Cluster (Platform) CVEs', () => { - withAuth(); - - it('should display table columns', () => { - visitVulnerabilityManagementEntities(entitiesKey); - - hasTableColumnHeadings([ - '', // checkbox - '', // hidden - 'CVE', - 'Type', - 'Fixable', - 'CVSS Score', - 'Env. Impact', - 'Impact Score', - 'Entities', - 'Published', - '', // hidden - ]); - }); - - it('should sort the CVSS Score column', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - visitVulnerabilityManagementEntities(entitiesKey); - - const thSelector = '.rt-th:contains("CVSS Score")'; - const tdSelector = '.rt-td:nth-child(6) [data-testid="label-chip"]'; - - // 0. Initial table state indicates that the column is sorted descending. - cy.get(thSelector).should('have.class', '-sort-desc'); - cy.get(tdSelector).then((items) => { - assertSortedItems(items, callbackForPairOfDescendingNumberValuesFromElements); - }); - - // 1. Sort ascending by the column. - interactAndWaitForVulnerabilityManagementEntities(() => { - cy.get(thSelector).click(); - }, entitiesKey); - cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=false'); - - cy.get(thSelector).should('have.class', '-sort-asc'); - cy.get(tdSelector).then((items) => { - assertSortedItems(items, callbackForPairOfAscendingNumberValuesFromElements); - }); - - // 2. Sort descending by the column. - cy.get(thSelector).click(); // no request because initial response has been cached - cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=true'); - - cy.get(thSelector).should('have.class', '-sort-desc'); - // Do not assert because of potential timing problem: get td elements before table re-renders. - }); - - // Cluster (Platform) CVEs does not yet have Severity column. - - it('should display vulnerability descriptions', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - visitVulnerabilityManagementEntities(entitiesKey); - - // Balance positive and negative assertions. - cy.get(selectors.cveDescription).should('exist'); - cy.get(`${selectors.cveDescription}:contains("No description available")`).should( - 'not.exist' - ); - }); - - // Argument 3 in verify functions is index of column which has the links. - // The one-based index includes checkbox, hidden, invisible. - - it('should display links for clusters', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - verifySecondaryEntities(entitiesKey, 'clusters', 9); - }); -}); diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js index 42f2532f321f0..a5759effc87ed 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js @@ -81,10 +81,6 @@ describe('Vulnerability Management Clusters', () => { verifyConditionalCVEs(entitiesKey, 'node-cves', 4, 'nodeVulnerabilityCounter'); }); - it('should display either links for cluster CVEs or text for No CVEs', () => { - verifyConditionalCVEs(entitiesKey, 'cluster-cves', 5, 'clusterVulnerabilityCounter'); - }); - it('should display links for namespaces', () => { verifySecondaryEntities(entitiesKey, 'namespaces', 7); }); diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/dashboard.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/dashboard.test.js index 25ba8dde138df..bbd5ffe090629 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/dashboard.test.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/dashboard.test.js @@ -56,10 +56,6 @@ describe('Vulnerability Management Dashboard', () => { verifyVulnerabilityManagementDashboardCVEs('node-cves', /^\d+ Node CVEs?$/); }); - it('should navigate from menu item Cluster (Platform) CVEs to entities list', () => { - verifyVulnerabilityManagementDashboardCVEs('cluster-cves', /^\d+ Platform CVEs?$/); - }); - it('should navigate from images link to images list', () => { visitVulnerabilityManagementDashboard(); @@ -178,19 +174,6 @@ describe('Vulnerability Management Dashboard', () => { ); }); - it('should go to clusters list from View all link of Clusters with most orchestrator and Istio vulnerabilities', () => { - visitVulnerabilityManagementDashboard(); - - const entitiesKey = 'clusters'; - const widgetHeading = 'Clusters with most orchestrator and Istio vulnerabilities'; - - interactAndWaitForVulnerabilityManagementEntities(() => { - cy.get(getViewAllSelectorForWidget(widgetHeading)).click(); - }, entitiesKey); - - cy.location('search').should('eq', ''); - }); - it('should to to deployments list from View all link of Top risky deployments by CVE count and CVSS score', () => { visitVulnerabilityManagementDashboard(); diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/dashboardToEntityPage.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/dashboardToEntityPage.test.js index 62f63dd834d57..f393560c3fef1 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/dashboardToEntityPage.test.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/dashboardToEntityPage.test.js @@ -25,7 +25,6 @@ function getItemTextSelectorForWidget(widgetHeading, itemTextSelector) { const itemTextSelectorForNumberedList = '[data-testid="numbered-list-item-name"]'; const itemTextSelectorForLabelText = '.rv-xy-plot__series--label text'; -const itemTextSelectorForClusters = 'li > div > div > a'; function getHeaderTextFromItemTextWithColonSeparators(itemText) { const [, itemTextAfterNumberBeforeSlash] = /^\d+\.([^:]+):.*$/.exec(itemText); @@ -136,17 +135,4 @@ describe('Vulnerability Management Dashboard', () => { getHeaderTextFromItemTextWithSlashSeparators ); }); - - it('has item link to cluster single page from Clusters with most orchestrator and Istio vulnerabilities', () => { - visitVulnerabilityManagementDashboard(); - - const entitiesKey = 'clusters'; - const widgetHeading = 'Clusters with most orchestrator and Istio vulnerabilities'; - - verifyItemLinkToEntityPage( - entitiesKey, - getItemTextSelectorForWidget(widgetHeading, itemTextSelectorForClusters), - (itemText) => itemText - ); - }); }); diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/CvesMenu.jsx b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/CvesMenu.jsx index a96718de1638f..17bdcf56d1087 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/CvesMenu.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/CvesMenu.jsx @@ -5,6 +5,7 @@ import Loader from 'Components/Loader'; import Menu from 'Components/Menu'; import entityTypes from 'constants/entityTypes'; import workflowStateContext from 'Containers/workflowStateContext'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import queryService from 'utils/queryService'; function getURL(workflowState, entityType) { @@ -26,6 +27,7 @@ const cveCountsQuery = gql` const CvesMenu = () => { const workflowState = useContext(workflowStateContext); + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); const { loading, data = {} } = useQuery(cveCountsQuery, { variables: { @@ -45,17 +47,20 @@ const CvesMenu = () => { label: `${data.nodeVulnerabilityCount} Node CVEs`, link: getURL(workflowState, entityTypes.NODE_CVE), }, - { - label: `${data.clusterVulnerabilityCount} Platform CVEs`, - link: getURL(workflowState, entityTypes.CLUSTER_CVE), - }, + ...(isLegacyScannerEnabled + ? [ + { + label: `${data.clusterVulnerabilityCount} Platform CVEs`, + link: getURL(workflowState, entityTypes.CLUSTER_CVE), + }, + ] + : []), ] : []; + const clusterVulnCount = isLegacyScannerEnabled ? data.clusterVulnerabilityCount : 0; const totalCveCount = - data.imageVulnerabilityCount + - data.nodeVulnerabilityCount + - data.clusterVulnerabilityCount || 0; + data.imageVulnerabilityCount + data.nodeVulnerabilityCount + clusterVulnCount || 0; const menuTitle = `${totalCveCount} CVEs`; return ( diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.jsx b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.jsx index 173485b8a42d9..55b8a3edbeefe 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Dashboard/VulnMgmtDashboardPage.jsx @@ -1,6 +1,7 @@ import { useContext } from 'react'; import { useNavigate } from 'react-router-dom-v5-compat'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import usePermissions from 'hooks/usePermissions'; import entityTypes from 'constants/entityTypes'; import { createOptions } from 'utils/workflowUtils'; @@ -30,6 +31,7 @@ const entityMenuTypes = [ const VulnMgmtDashboardPage = () => { const navigate = useNavigate(); + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); const { hasReadAccess } = usePermissions(); const hasReadAccessForIntegration = hasReadAccess('Integration'); const workflowState = useContext(workflowStateContext); @@ -118,9 +120,11 @@ const VulnMgmtDashboardPage = () => {
    -
    - -
    + {isLegacyScannerEnabled && ( +
    + +
    + )} ); diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Entity/Cluster/VulnMgmtClusterOverview.jsx b/ui/apps/platform/src/Containers/VulnMgmt/Entity/Cluster/VulnMgmtClusterOverview.jsx index 77c7c3c7bfc5d..66fe1b0f3f7ff 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Entity/Cluster/VulnMgmtClusterOverview.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Entity/Cluster/VulnMgmtClusterOverview.jsx @@ -8,6 +8,7 @@ import BinderTabs from 'Components/BinderTabs'; import Tab from 'Components/Tab'; import workflowStateContext from 'Containers/workflowStateContext'; import entityTypes from 'constants/entityTypes'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import { OVERVIEW_LIMIT } from 'constants/workflowPages.constants'; import { entityGridContainerClassName } from '../WorkflowEntityPage'; @@ -35,6 +36,7 @@ const emptyCluster = { const VulnMgmtClusterOverview = ({ data, entityContext }) => { const workflowState = useContext(workflowStateContext); + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); // guard against incomplete GraphQL-cached data const safeData = { ...emptyCluster, ...data }; @@ -140,16 +142,18 @@ const VulnMgmtClusterOverview = ({ data, entityContext }) => { id={safeData?.id} /> - - - + {isLegacyScannerEnabled && ( + + + + )} diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Entity/EntityTabs.tsx b/ui/apps/platform/src/Containers/VulnMgmt/Entity/EntityTabs.tsx index 0e919f7202a81..2215086f4ba37 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Entity/EntityTabs.tsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Entity/EntityTabs.tsx @@ -8,6 +8,7 @@ import { entityGroupMap, } from 'utils/entityRelationships'; import type { VulnerabilityManagementEntityType } from 'utils/entityRelationships'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import workflowStateContext from '../../workflowStateContext'; import { entityNounSentenceCasePlural } from '../entitiesForVulnerabilityManagement'; @@ -18,6 +19,8 @@ export type EntityTabsProps = { function EntityTabs({ entityType, activeTab }: EntityTabsProps): ReactElement { const workflowState = useContext(workflowStateContext); + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); + function getTab(tabType) { return { group: entityGroups[entityGroupMap[tabType]], @@ -30,7 +33,7 @@ function EntityTabs({ entityType, activeTab }: EntityTabsProps): ReactElement { const relationships = [ ...getVulnerabilityManagementEntityTypesByRelationship(entityType, 'MATCHES'), ...getVulnerabilityManagementEntityTypesByRelationship(entityType, 'CONTAINS'), - ]; + ].filter((rel) => isLegacyScannerEnabled || rel !== 'CLUSTER_CVE'); const entityTabs = relationships.map((entityTypeByRelationship) => getTab(entityTypeByRelationship) diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Entity/RelatedEntitiesSideList.tsx b/ui/apps/platform/src/Containers/VulnMgmt/Entity/RelatedEntitiesSideList.tsx index 624b40389dc63..3ee2f2c8626e5 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Entity/RelatedEntitiesSideList.tsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Entity/RelatedEntitiesSideList.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from 'react'; import { defaultCountKeyMap as countKeyMap } from 'constants/workflowPages.constants'; import workflowStateContext from 'Containers/workflowStateContext'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import { getVulnerabilityManagementEntityTypesByRelationship } from 'utils/entityRelationships'; import type { VulnerabilityManagementEntityType } from 'utils/entityRelationships'; @@ -20,12 +21,18 @@ function RelatedEntitiesSideList({ entityContext, }: RelatedEntitiesSideListProps): ReactNode { const workflowState = useContext(workflowStateContext); + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); const { useCase } = workflowState; if (!useCase) { return null; } - const matches = getVulnerabilityManagementEntityTypesByRelationship(entityType, 'MATCHES') + const matchRelationships = getVulnerabilityManagementEntityTypesByRelationship( + entityType, + 'MATCHES' + ).filter((rel) => isLegacyScannerEnabled || rel !== 'CLUSTER_CVE'); + + const matches = matchRelationships .map((matchEntity) => { const count = data[countKeyMap[matchEntity]]; diff --git a/ui/apps/platform/src/Containers/VulnMgmt/Entity/WorkflowEntityPageLayout.jsx b/ui/apps/platform/src/Containers/VulnMgmt/Entity/WorkflowEntityPageLayout.jsx index c68e5da194d96..1754d84423292 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/Entity/WorkflowEntityPageLayout.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/Entity/WorkflowEntityPageLayout.jsx @@ -10,6 +10,7 @@ import { searchParams, sortParams, pagingParams } from 'constants/searchParams'; import { WorkflowState } from 'utils/WorkflowState'; import { getVulnerabilityManagementEntityTypes } from 'utils/entityRelationships'; import useEntityName from 'hooks/useEntityName'; +import useFeatureFlags from 'hooks/useFeatureFlags'; import { entityNounSentenceCaseSingular } from '../entitiesForVulnerabilityManagement'; import getSidePanelEntity from '../getSidePanelEntity'; @@ -19,6 +20,7 @@ import EntityTabs from './EntityTabs'; const WorkflowEntityPageLayout = () => { const location = useLocation(); + const { isFeatureFlagEnabled } = useFeatureFlags(); const workflowState = parseURL(location); const { stateStack, useCase, search } = workflowState; @@ -79,7 +81,9 @@ const WorkflowEntityPageLayout = () => {
    diff --git a/ui/apps/platform/src/Containers/VulnMgmt/List/Clusters/VulnMgmtListClusters.jsx b/ui/apps/platform/src/Containers/VulnMgmt/List/Clusters/VulnMgmtListClusters.jsx index 78ded8414b4ee..9863f1600d9b1 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/List/Clusters/VulnMgmtListClusters.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/List/Clusters/VulnMgmtListClusters.jsx @@ -15,6 +15,7 @@ import { CLUSTER_LIST_FRAGMENT_UPDATED } from 'Containers/VulnMgmt/VulnMgmt.frag import { workflowListPropTypes, workflowListDefaultProps } from 'constants/entityPageProps'; import { clusterSortFields } from 'constants/sortFields'; import { LIST_PAGE_SIZE } from 'constants/workflowPages.constants'; +import useIsLegacyScannerEnabled from 'hooks/useIsLegacyScannerEnabled'; import removeEntityContextColumns from '../tableUtils'; import { vulMgmtPolicyQuery } from '../../Entity/VulnMgmtPolicyQueryUtil'; import { getVulnMgmtPathForEntitiesAndId } from '../../VulnMgmt.utils/entities'; @@ -29,6 +30,7 @@ export const defaultClusterSort = [ ]; const VulnMgmtListClusters = ({ selectedRowId, search, sort, page, data, totalResults }) => { + const isLegacyScannerEnabled = useIsLegacyScannerEnabled(); const query = gql` query getClusters( $query: String @@ -135,39 +137,41 @@ const VulnMgmtListClusters = ({ selectedRowId, search, sort, page, data, totalRe accessor: 'vulnCounter.all.total', sortField: clusterSortFields.CVE_COUNT, }, - { - Header: `Platform CVEs`, - entityType: entityTypes.CLUSTER_CVE, - headerClassName: `w-1/8 ${defaultHeaderClassName}`, - className: `w-1/8 ${defaultColumnClassName}`, - Cell: ({ original, pdf }) => { - const { clusterVulnerabilityCounter, id } = original; - if ( - !clusterVulnerabilityCounter || - clusterVulnerabilityCounter.all.total === 0 - ) { - return 'No CVEs'; - } - - const newState = workflowState - .pushListItem(id) - .pushList(entityTypes.CLUSTER_CVE); - const url = newState.toUrl(); - const fixableUrl = newState.setSearch({ Fixable: true }).toUrl(); - - return ( - - ); - }, - id: clusterSortFields.CVE_COUNT, - accessor: 'vulnCounter.all.total', - sortField: clusterSortFields.CVE_COUNT, - }, + isLegacyScannerEnabled + ? { + Header: `Platform CVEs`, + entityType: entityTypes.CLUSTER_CVE, + headerClassName: `w-1/8 ${defaultHeaderClassName}`, + className: `w-1/8 ${defaultColumnClassName}`, + Cell: ({ original, pdf }) => { + const { clusterVulnerabilityCounter, id } = original; + if ( + !clusterVulnerabilityCounter || + clusterVulnerabilityCounter.all.total === 0 + ) { + return 'No CVEs'; + } + + const newState = workflowState + .pushListItem(id) + .pushList(entityTypes.CLUSTER_CVE); + const url = newState.toUrl(); + const fixableUrl = newState.setSearch({ Fixable: true }).toUrl(); + + return ( + + ); + }, + id: clusterSortFields.CVE_COUNT, + accessor: 'vulnCounter.all.total', + sortField: clusterSortFields.CVE_COUNT, + } + : null, { Header: `K8S Version`, headerClassName: `w-1/10 ${nonSortableHeaderClassName}`, @@ -220,7 +224,7 @@ const VulnMgmtListClusters = ({ selectedRowId, search, sort, page, data, totalRe }, ]; - return removeEntityContextColumns(tableColumns, workflowState); + return removeEntityContextColumns(tableColumns.filter(Boolean), workflowState); } return ( diff --git a/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPageLayout.jsx b/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPageLayout.jsx index 22c206a94c8f0..6fecb901ea4af 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPageLayout.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/List/WorkflowListPageLayout.jsx @@ -9,6 +9,7 @@ import parseURL from 'utils/URLParser'; import workflowStateContext from 'Containers/workflowStateContext'; import { WorkflowState } from 'utils/WorkflowState'; import { getVulnerabilityManagementEntityTypes } from 'utils/entityRelationships'; +import useFeatureFlags from 'hooks/useFeatureFlags'; import { entityNounSentenceCasePlural } from '../entitiesForVulnerabilityManagement'; import getSidePanelEntity from '../getSidePanelEntity'; @@ -18,6 +19,7 @@ import ListComponent from './VulnMgmtList'; const WorkflowListPageLayout = () => { const location = useLocation(); + const { isFeatureFlagEnabled } = useFeatureFlags(); const workflowState = parseURL(location); const { useCase, search, sort, paging } = workflowState; @@ -66,7 +68,9 @@ const WorkflowListPageLayout = () => {
    diff --git a/ui/apps/platform/src/utils/entityRelationships.ts b/ui/apps/platform/src/utils/entityRelationships.ts index ac76512e9ffea..ead738dddd948 100644 --- a/ui/apps/platform/src/utils/entityRelationships.ts +++ b/ui/apps/platform/src/utils/entityRelationships.ts @@ -65,14 +65,10 @@ export function getVulnerabilityManagementEntityTypes( isFeatureFlagEnabled?: IsFeatureFlagEnabled ): VulnerabilityManagementEntityType[] { if (isFeatureFlagEnabled) { - // Arrays include all possible entity types for use case. - return vulnerabilityManagementEntityTypes.filter((/* entityType */) => { - /* - // Pattern to filter out an entity type if a feature flag is not enabled. - if (entityType === 'WHICHEVER' && !isFeatureFlagEnabled(ROX_WHATEVER)) { + return vulnerabilityManagementEntityTypes.filter((entityType) => { + if (entityType === 'CLUSTER_CVE' && !isFeatureFlagEnabled('ROX_LEGACY_SCANNER')) { return false; - } - */ + } return true; }); } From 221143b86cc680ccf6693584000cf703a73e6d62 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 4 Sep 2026 14:28:45 -0400 Subject: [PATCH 207/638] ROX-36773: Update Breadcrumb in ImageVulnerabilityReports (#22631) --- .../vulnerabilityReportingNav.test.ts | 19 ++++++++------- .../Wizard/ImageVulnerabilityReportWizard.tsx | 10 +++++--- .../ImageVulnerabilityReportWizardPage.tsx | 7 ++++-- .../imageVulnerabilityReports.utils.ts | 4 ++-- .../ViewVulnReport/ViewVulnReportPage.tsx | 18 +++++++++----- .../VulnReports/ConfigReportsTab.tsx | 10 ++++---- .../VulnReports/VulnReportingLayout.tsx | 24 ++++++++++++++++--- .../pathsForVulnerabilityReporting.ts | 3 --- ui/apps/platform/src/routePaths.ts | 3 ++- 9 files changed, 66 insertions(+), 32 deletions(-) delete mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/vulnerabilityReportingNav.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/vulnerabilityReportingNav.test.ts index 4b7d66b4c0b33..e3319e9304caa 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/vulnerabilityReportingNav.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/VulnerabilityReporting/vulnerabilityReportingNav.test.ts @@ -23,8 +23,7 @@ describe('Vulnerability Reporting Navigation', () => { it('navigates to the index page', () => { visitVulnerabilityReports(staticResponseMap); cy.location('pathname').should('eq', vulnerabilityReportsConfigurationPath); - // Because of mixed headings, comment out until we delete ROX_VULNERABILITY_REPORTS_ENHANCED_FILTERING - // cy.get('h1').should('contain', 'Image vulnerability reports'); + cy.get('h1').should('contain', 'Image vulnerability reports'); }); it('navigates to the create page', () => { @@ -67,9 +66,11 @@ describe('Vulnerability Reporting Navigation', () => { ); expect(location.search).to.include('action=edit'); }); - // cy.get('h1').should('contain', 'Edit report'); + cy.get('h1').should('contain', firstReport.name); - cy.get('nav[aria-label="Breadcrumb"] a').click(); + cy.get( + 'nav[aria-label="Breadcrumb"] a:contains("Image vulnerability reports")' + ).click(); cy.get('td').contains('a', firstReport.name).click(); cy.contains('button', 'Actions').click(); @@ -81,7 +82,7 @@ describe('Vulnerability Reporting Navigation', () => { ); expect(location.search).to.include('action=edit'); }); - // cy.get('h1').should('contain', 'Edit report'); + cy.get('h1').should('contain', firstReport.name); }); }); @@ -99,9 +100,11 @@ describe('Vulnerability Reporting Navigation', () => { ); expect(location.search).to.include('action=clone'); }); - // cy.get('h1').should('contain', 'Clone report'); + cy.get('h1').should('contain', `${firstReport.name} (COPY)`); - cy.get('nav[aria-label="Breadcrumb"] a').click(); + cy.get( + 'nav[aria-label="Breadcrumb"] a:contains("Image vulnerability reports")' + ).click(); cy.get('td').contains('a', firstReport.name).click(); cy.contains('button', 'Actions').click(); @@ -113,7 +116,7 @@ describe('Vulnerability Reporting Navigation', () => { ); expect(location.search).to.include('action=clone'); }); - // cy.get('h1').should('contain', 'Clone report'); + cy.get('h1').should('contain', `${firstReport.name} (COPY)`); }); }); }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx index 673e8d2167e56..59343c86727a5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; -import { useNavigate } from 'react-router-dom-v5-compat'; +import { generatePath, useNavigate } from 'react-router-dom-v5-compat'; import { Wizard, WizardFooter, WizardStep, useWizardContext } from '@patternfly/react-core'; import type { WizardStepType } from '@patternfly/react-core'; import { useFormik } from 'formik'; @@ -23,7 +23,7 @@ import type { ImageVulnerabilityReportConfigurationForCollection, ImageVulnerabilityReportConfigurationForEntity, } from 'services/ReportsService.types'; -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; +import { vulnerabilityImageConfigurationsReportsDetailsPath } from 'routePaths'; import { getValueForCvesSince } from '../imageVulnerabilityReports.utils'; @@ -138,7 +138,11 @@ function ImageVulnerabilityReportWizard({ navigate(-1); } else { // Go to page for new report. - navigate(`${vulnerabilityConfigurationsReportsPath}/${data.id}`); + navigate( + generatePath(vulnerabilityImageConfigurationsReportsDetailsPath, { + reportId: data.id, + }) + ); } }) .catch((errorCaught) => { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx index ae260a2e9e118..48861a85a5c1f 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizardPage.tsx @@ -37,7 +37,7 @@ import { getValueByCaseInsensitiveKey, searchValueAsArray, } from 'utils/searchUtils'; -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; +import { vulnerabilityImageConfigurationsReportsPath, vulnerabilityReportsPath } from 'routePaths'; import { attributesSeparateFromConfigForImageVulnerabilityReport, @@ -175,7 +175,10 @@ function ImageVulnerabilityReportWizardPage({ <> - + + Vulnerability reports + + Image vulnerability reports {heading} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts index fe724775e96d8..285062891cc05 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/imageVulnerabilityReports.utils.ts @@ -19,7 +19,7 @@ import { searchValueAsArray, } from 'utils/searchUtils'; import { ensureExhaustive } from 'utils/type.utils'; -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; +import { vulnerabilityImageConfigurationsReportsPath } from 'routePaths'; // TypeScript needs help because entityScope and vulnReportFilters have mutual dependencies. // entityScope implies query but not fixability nore severities @@ -197,5 +197,5 @@ export const imageTypeLabelMap: Record = { // 3. from search filter to request query (save report configuration) export function createScheduledReportForImageVulnerabilitiesURL(searchFilter: SearchFilter) { const query = getUrlQueryStringForSearchFilter(searchFilter); - return `${vulnerabilityConfigurationsReportsPath}?action=createFromFilters&${query}`; + return `${vulnerabilityImageConfigurationsReportsPath}?action=createFromFilters&${query}`; } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx index fa31bd15e73fb..46c54cc6b4aa4 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx @@ -19,7 +19,11 @@ import { Title, } from '@patternfly/react-core'; -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; +import { + vulnerabilityImageConfigurationsReportsDetailsPath, + vulnerabilityImageConfigurationsReportsPath, + vulnerabilityReportsPath, +} from 'routePaths'; import DeleteModal from 'Components/PatternFly/DeleteModal'; import PageTitle from 'Components/PageTitle'; @@ -46,7 +50,6 @@ import useFetchReport from '../api/useFetchReport'; import useRunReport from '../api/useRunReport'; import { useWatchLastSnapshotForReports } from '../api/useWatchLastSnapshotForReports'; import useDeleteModal, { isErrorDeleteResult } from '../hooks/useDeleteModal'; -import { vulnerabilityConfigurationReportDetailsPath } from '../pathsForVulnerabilityReporting'; // resourceScope: {} after roll back to previous version that does not support a newer resource scope. // Do not let user clone or edit report configuration which might cause worse problems after roll forward. @@ -82,7 +85,7 @@ function ViewVulnReportPage() { } = useDeleteModal({ deleteFunction: deleteReportConfiguration, onCompleted: () => { - navigate(vulnerabilityConfigurationsReportsPath); + navigate(vulnerabilityImageConfigurationsReportsPath); }, }); @@ -115,12 +118,12 @@ function ViewVulnReportPage() { title="Error fetching the report configuration" message={fetchError || 'No data available'} actionText="Go to reports" - url={vulnerabilityConfigurationsReportsPath} + url={vulnerabilityImageConfigurationsReportsPath} /> ); } - const vulnReportPageURL = generatePath(vulnerabilityConfigurationReportDetailsPath, { + const vulnReportPageURL = generatePath(vulnerabilityImageConfigurationsReportsDetailsPath, { reportId: reportConfiguration.id, }); @@ -155,7 +158,10 @@ function ViewVulnReportPage() { - + + Vulnerability reports + + Image vulnerability reports {reportConfiguration.name} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx index 0714b35338a88..9f8fd1935508b 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx @@ -24,7 +24,10 @@ import { import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { ExclamationCircleIcon, FileIcon, SearchIcon } from '@patternfly/react-icons'; -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; +import { + vulnerabilityImageConfigurationsReportsDetailsPath, + vulnerabilityImageConfigurationsReportsPath, +} from 'routePaths'; import useIsRouteEnabled from 'hooks/useIsRouteEnabled'; import usePermissions from 'hooks/usePermissions'; import useURLPagination from 'hooks/useURLPagination'; @@ -53,7 +56,6 @@ import DeleteReportsModal from '../../Reports/components/DeleteReportsModal'; import useFetchReports from '../api/useFetchReports'; import useRunReport from '../api/useRunReport'; import { useWatchLastSnapshotForReports } from '../api/useWatchLastSnapshotForReports'; -import { vulnerabilityConfigurationReportDetailsPath } from '../pathsForVulnerabilityReporting'; // resourceScope: {} after roll back to previous version that does not support a newer resource scope. // Do not let user clone or edit report configuration which might cause worse problems after roll forward. @@ -63,7 +65,7 @@ function isResourceScopeAbsent({ resourceScope }: ImageVulnerabilityReportConfig const CreateReportsButton = () => { return ( - + ); @@ -382,7 +384,7 @@ function ConfigReportsTab() { )} {reportConfigurations.map((report, rowIndex) => { const vulnReportURL = generatePath( - vulnerabilityConfigurationReportDetailsPath, + vulnerabilityImageConfigurationsReportsDetailsPath, { reportId: report.id, } diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx index 207571a810cdb..51b40315dc985 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/VulnReportingLayout.tsx @@ -1,11 +1,21 @@ -import { PageSection, Tab, TabContent, Tabs, Title } from '@patternfly/react-core'; +import { + Breadcrumb, + BreadcrumbItem, + PageSection, + Tab, + TabContent, + Tabs, + Title, +} from '@patternfly/react-core'; import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; +import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; import { - vulnerabilityConfigurationsReportsPath, + vulnerabilityImageConfigurationsReportsPath, vulnerabilityImageViewBasedJobsPath, + vulnerabilityReportsPath, } from 'routePaths'; import ConfigReportsTab from './ConfigReportsTab'; @@ -24,7 +34,7 @@ function VulnReportingLayout() { { id: 'report-configuration', title: 'Report configurations', - path: vulnerabilityConfigurationsReportsPath, + path: vulnerabilityImageConfigurationsReportsPath, content: , }, ] @@ -47,6 +57,14 @@ function VulnReportingLayout() { return ( <> + + + + Vulnerability reports + + Image vulnerability reports + + Image vulnerability reports diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts deleted file mode 100644 index 652070c8615f0..0000000000000 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/pathsForVulnerabilityReporting.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { vulnerabilityConfigurationsReportsPath } from 'routePaths'; - -export const vulnerabilityConfigurationReportDetailsPath = `${vulnerabilityConfigurationsReportsPath}/:reportId`; diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 8775d09796f03..15a3f0f002533 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -100,7 +100,8 @@ export const vulnerabilitiesViewPath = `${vulnerabilitiesBasePath}/results/:view export const vulnerabilityReportsPath = `${vulnerabilitiesBasePath}/reports`; export const vulnerabilityImageReportsPath = `${vulnerabilityReportsPath}/images`; -export const vulnerabilityConfigurationsReportsPath = `${vulnerabilityImageReportsPath}/configurations`; +export const vulnerabilityImageConfigurationsReportsPath = `${vulnerabilityImageReportsPath}/configurations`; +export const vulnerabilityImageConfigurationsReportsDetailsPath = `${vulnerabilityImageConfigurationsReportsPath}/:reportId`; export const vulnerabilityImageViewBasedJobsPath = `${vulnerabilityImageReportsPath}/view-based-jobs`; export const vulnerabilityNodeReportsPath = `${vulnerabilityReportsPath}/nodes`; From 11c649429298d9cc9d6951767e312d8ae6cb1284 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:52:48 -0500 Subject: [PATCH 208/638] ROX-36793: Node report configuration details page (#22634) --- .../View/NodeVulnerabilityReportViewPage.tsx | 181 ++++++++++++++++++ .../NodeVulnerabilityReportsLayout.tsx | 4 +- .../NodeVulnerabilityReportsPage.tsx | 42 +++- 3 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx new file mode 100644 index 0000000000000..0eb49df23eb2c --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx @@ -0,0 +1,181 @@ +import { useCallback } from 'react'; +import { generatePath, useNavigate, useParams } from 'react-router-dom-v5-compat'; +import { + Breadcrumb, + BreadcrumbItem, + Bullseye, + Divider, + DropdownItem, + Flex, + FlexItem, + PageSection, + Spinner, + Title, +} from '@patternfly/react-core'; + +import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; +import NotFoundMessage from 'Components/NotFoundMessage/NotFoundMessage'; +import PageTitle from 'Components/PageTitle'; +import MenuDropdown from 'Components/PatternFly/MenuDropdown'; +import usePermissions from 'hooks/usePermissions'; +import useRestQuery from 'hooks/useRestQuery'; +import { + vulnerabilityNodeConfigurationsReportsDetailsPath, + vulnerabilityNodeConfigurationsReportsPath, + vulnerabilityReportsPath, +} from 'routePaths'; +import { + deleteNodeReportConfiguration, + fetchNodeReportConfiguration, +} from 'services/NodeReportsService'; +import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; + +import DeleteReportsModal from '../../../components/DeleteReportsModal'; +import NodeVulnerabilityReportView from './NodeVulnerabilityReportView'; +import { + attributesSeparateFromConfigForNodeVulnerabilityReport, + searchFilterConfigForNodeVulnerabilityReport, +} from '../../../../searchFilterConfig'; +import useDeleteModal from '../../../../VulnerablityReporting/hooks/useDeleteModal'; + +function NodeVulnerabilityReportViewPage() { + const navigate = useNavigate(); + const { reportId } = useParams() as { reportId: string }; + + const { hasReadWriteAccess, hasReadAccess } = usePermissions(); + const hasDeleteAccessForReport = hasReadWriteAccess('WorkflowAdministration'); + const hasWriteAccessForReport = + hasReadWriteAccess('WorkflowAdministration') && hasReadAccess('Integration'); + + const fetchReportConfigurationCallback = useCallback( + () => fetchNodeReportConfiguration(reportId), + [reportId] + ); + const { + data: reportConfiguration, + isLoading, + error: fetchError, + } = useRestQuery(fetchReportConfigurationCallback); + + const { + openDeleteModal, + isDeleteModalOpen, + closeDeleteModal, + isDeleting, + onDelete, + deleteResults, + reportIdsToDelete, + } = useDeleteModal({ + deleteFunction: deleteNodeReportConfiguration, + onCompleted: () => { + navigate(vulnerabilityNodeConfigurationsReportsPath); + }, + }); + + const reportPageURL = generatePath(vulnerabilityNodeConfigurationsReportsDetailsPath, { + reportId, + }); + + return ( + <> + + {isLoading ? ( + + + + ) : fetchError || !reportConfiguration ? ( + + ) : ( + <> + + + + Vulnerability reports + + + Node vulnerability reports + + {reportConfiguration.name} + + + + + + {reportConfiguration.name} + + {(hasWriteAccessForReport || hasDeleteAccessForReport) && ( + + + {hasWriteAccessForReport && ( + + navigate(`${reportPageURL}?action=edit`) + } + > + Edit report + + )} + {hasWriteAccessForReport && ( + + navigate(`${reportPageURL}?action=clone`) + } + > + Clone report + + )} + {hasDeleteAccessForReport && ( + <> + {hasWriteAccessForReport && ( + + )} + + openDeleteModal([reportConfiguration.id]) + } + > + Delete report + + + )} + + + )} + + + + + + + + )} + + ); +} + +export default NodeVulnerabilityReportViewPage; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx index 59dcd6b00c77b..5e80f069bd5de 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx @@ -13,7 +13,7 @@ function NodeVulnerabilityReportsLayout() { const navigate = useNavigate(); const { hasReadAccess } = usePermissions(); - const hasAccessForConfigurations = hasReadAccess('WorkflowAdministration'); + const hasReadAccessForReport = hasReadAccess('WorkflowAdministration'); const activeKey = location.pathname.startsWith(vulnerabilityNodeConfigurationsReportsPath) ? 'configurations' @@ -37,7 +37,7 @@ function NodeVulnerabilityReportsLayout() { }} usePageInsets > - {hasAccessForConfigurations && ( + {hasReadAccessForReport && ( (); + const { hasReadAccess, hasReadWriteAccess } = usePermissions(); + + const hasReadAccessForReport = hasReadAccess('WorkflowAdministration'); + const hasWriteAccessForReport = + hasReadWriteAccess('WorkflowAdministration') && hasReadAccess('Integration'); return ( }> - } /> - ) : ( - - ) + } /> + {hasReadAccessForReport && ( + } /> + )} } /> + {hasReadAccessForReport && ( + + ) : ( + + ) + } + /> + )} + } /> ); } From 686e6a2e2403e0ba136ec682ba2b415c663f0a55 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 05:01:06 +0000 Subject: [PATCH 209/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 9ad31aa (#22660) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 958438687eaf3..cb60cd7cf1bc5 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index eaa07341e9f65..d5887d4400d30 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index ad0cd4dbd7f74..105f569badee1 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 07740ca8fed8a..55f9ab8e8f8c2 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 32a37b4605a59..6dda6fe8218da 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index bb6756551c44d..747ce7eeb2495 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 71ee9192399f4..dd6c67b8dd8b6 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 3c424166d3b3a..031b40a7f3ee4 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:49b55850031874f99d9ed91b3a015df7789b56b1135f44c32447a0a91b4c930d + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f - name: kind value: task resolver: bundles From 81e07ca6865a524e5c7990118d696f9df8b5f7fd Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 09:27:52 +0000 Subject: [PATCH 210/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to d7f5f96 (#22661) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index cb60cd7cf1bc5..d398cb942e0b4 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index d5887d4400d30..e56ef0fb82e2f 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 105f569badee1..77fea10837a69 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 55f9ab8e8f8c2..ba8ba9d9646cb 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 6dda6fe8218da..88e960a36e850 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 747ce7eeb2495..3578e61357629 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index dd6c67b8dd8b6..bd501006485ab 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 031b40a7f3ee4..fbb6887093030 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:9ad31aa932db2662c85b065136cb4487835ba75a1b4a2627e687892cffc77b2f + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 - name: kind value: task resolver: bundles From 2fdba0d71253ff15d99e05413cd7b426555fb64f Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 03:05:27 +0000 Subject: [PATCH 211/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to f2820c3 (#22662) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index d398cb942e0b4..3765e7aeac21f 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index e56ef0fb82e2f..2b65bbbe65c15 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 77fea10837a69..d3644d58d188c 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index ba8ba9d9646cb..bd7b6102c8bc0 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 88e960a36e850..5706e7d74d4b3 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 3578e61357629..42eb6058aa788 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index bd501006485ab..701183c317080 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index fbb6887093030..6db000cf96609 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:d7f5f9686fbc23d4556486d49ac2873c5ca950c244b2587adb8f86db7f430343 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd - name: kind value: task resolver: bundles From f7e5ebd7b64b5c08ac3d98f6e9dec54bbfa655eb Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Sun, 6 Sep 2026 11:31:12 -0400 Subject: [PATCH 212/638] ROX-36774: Update Breadcrumb in NodeVulnerabilityReports (#22656) --- .../NodeVulnerabilityReportWizardPage.tsx | 5 ++++- .../NodeVulnerabilityReportsLayout.tsx | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizardPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizardPage.tsx index 79e68669738bf..a813baec4dc92 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizardPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizardPage.tsx @@ -32,7 +32,7 @@ import { getRequestQueryStringForSearchFilter, // getValueByCaseInsensitiveKey, } from 'utils/searchUtils'; -import { vulnerabilityNodeConfigurationsReportsPath } from 'routePaths'; +import { vulnerabilityNodeConfigurationsReportsPath, vulnerabilityReportsPath } from 'routePaths'; import { attributesSeparateFromConfigForNodeVulnerabilityReport, @@ -148,6 +148,9 @@ function NodeVulnerabilityReportWizardPage({ <> + + Vulnerability reports + Node vulnerability reports diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx index 5e80f069bd5de..22d162bcb6d20 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx @@ -1,11 +1,21 @@ -import { PageSection, Tab, TabContent, Tabs, Title } from '@patternfly/react-core'; +import { + Breadcrumb, + BreadcrumbItem, + PageSection, + Tab, + TabContent, + Tabs, + Title, +} from '@patternfly/react-core'; import { Outlet, useLocation, useNavigate } from 'react-router-dom-v5-compat'; +import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import PageTitle from 'Components/PageTitle'; import usePermissions from 'hooks/usePermissions'; import { vulnerabilityNodeConfigurationsReportsPath, vulnerabilityNodeViewBasedJobsPath, + vulnerabilityReportsPath, } from 'routePaths'; function NodeVulnerabilityReportsLayout() { @@ -22,6 +32,14 @@ function NodeVulnerabilityReportsLayout() { return ( <> + + + + Vulnerability reports + + Node vulnerability reports + + Node vulnerability reports From e41b2d3ff7ed048f17441f8d7f607a81d4286996 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Sun, 6 Sep 2026 22:09:12 +0200 Subject: [PATCH 213/638] ROX-36250: add conversion layer for node report API (#22507) Co-authored-by: Claude Sonnet 4.5 Co-authored-by: Cursor --- central/reports/service/v2/convert.go | 53 +- central/reports/service/v2/convert_test.go | 129 ++- central/reports/service/v2/node/convert.go | 373 ++++++++ .../reports/service/v2/node/convert_test.go | 860 ++++++++++++++++++ .../reports/service/v2/node/service_impl.go | 23 + 5 files changed, 1411 insertions(+), 27 deletions(-) create mode 100644 central/reports/service/v2/node/convert.go create mode 100644 central/reports/service/v2/node/convert_test.go create mode 100644 central/reports/service/v2/node/service_impl.go diff --git a/central/reports/service/v2/convert.go b/central/reports/service/v2/convert.go index ced98185678bf..9c25a59fe7c3d 100644 --- a/central/reports/service/v2/convert.go +++ b/central/reports/service/v2/convert.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + notifierDS "github.com/stackrox/rox/central/notifier/datastore" "github.com/stackrox/rox/central/reports/common" apiV2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" @@ -56,7 +57,7 @@ func (s *serviceImpl) convertV2ReportConfigurationToProto(config *apiV2.ReportCo Name: config.GetName(), Description: config.GetDescription(), Type: storage.ReportConfiguration_ReportType(config.GetType()), - Schedule: s.convertV2ScheduleToProto(config.GetSchedule()), + Schedule: ConvertV2ScheduleToProto(config.GetSchedule()), ResourceScope: s.convertV2ResourceScopeToProto(config.GetResourceScope()), Creator: creator, Version: 2, @@ -69,7 +70,7 @@ func (s *serviceImpl) convertV2ReportConfigurationToProto(config *apiV2.ReportCo } for _, notifier := range config.GetNotifiers() { - ret.Notifiers = append(ret.Notifiers, s.convertV2NotifierConfigToProto(notifier)) + ret.Notifiers = append(ret.Notifiers, ConvertV2NotifierConfigToProto(notifier)) } return ret @@ -199,7 +200,8 @@ func v2MatchTypeToStorage(m apiV2.MatchType) storage.MatchType { } } -func (s *serviceImpl) convertV2NotifierConfigToProto(notifier *apiV2.NotifierConfiguration) *storage.NotifierConfiguration { +// ConvertV2NotifierConfigToProto converts apiV2.NotifierConfiguration to storage.NotifierConfiguration. +func ConvertV2NotifierConfigToProto(notifier *apiV2.NotifierConfiguration) *storage.NotifierConfiguration { if notifier == nil { return nil } @@ -222,8 +224,8 @@ func (s *serviceImpl) convertV2NotifierConfigToProto(notifier *apiV2.NotifierCon return ret } -// convertV2ScheduleToProto converts v2.ReportSchedule to storage.Schedule. Does not validate v2.ReportSchedule -func (s *serviceImpl) convertV2ScheduleToProto(schedule *apiV2.ReportSchedule) *storage.Schedule { +// ConvertV2ScheduleToProto converts v2.ReportSchedule to storage.Schedule. Does not validate v2.ReportSchedule. +func ConvertV2ScheduleToProto(schedule *apiV2.ReportSchedule) *storage.Schedule { if schedule == nil { return nil } @@ -266,7 +268,7 @@ func (s *serviceImpl) convertProtoReportConfigurationToV2(config *storage.Report Name: config.GetName(), Description: config.GetDescription(), Type: apiV2.ReportConfiguration_ReportType(config.GetType()), - Schedule: s.convertProtoScheduleToV2(config.GetSchedule()), + Schedule: ConvertProtoScheduleToV2(config.GetSchedule()), ResourceScope: resourceScope, } @@ -285,11 +287,13 @@ func (s *serviceImpl) convertProtoReportConfigurationToV2(config *storage.Report } for _, notifier := range config.GetNotifiers() { - converted, err := s.convertProtoNotifierConfigToV2(notifier) + converted, err := ConvertProtoNotifierConfigToV2(s.notifierDatastore, notifier) if err != nil { return nil, err } - ret.Notifiers = append(ret.Notifiers, converted) + if converted != nil { + ret.Notifiers = append(ret.Notifiers, converted) + } } return ret, nil @@ -430,8 +434,8 @@ func storageMatchTypeToV2(m storage.MatchType) apiV2.MatchType { } } -// convertProtoNotifierConfigToV2 converts storage.NotifierConfiguration to apiV2.NotifierConfiguration -func (s *serviceImpl) convertProtoNotifierConfigToV2(notifierConfig *storage.NotifierConfiguration) (*apiV2.NotifierConfiguration, error) { +// ConvertProtoNotifierConfigToV2 converts storage.NotifierConfiguration to apiV2.NotifierConfiguration. +func ConvertProtoNotifierConfigToV2(ds notifierDS.DataStore, notifierConfig *storage.NotifierConfiguration) (*apiV2.NotifierConfiguration, error) { if notifierConfig == nil { return nil, nil } @@ -440,7 +444,7 @@ func (s *serviceImpl) convertProtoNotifierConfigToV2(notifierConfig *storage.Not return nil, nil } - notifier, found, err := s.notifierDatastore.GetNotifier(allAccessCtx, notifierConfig.GetId()) + notifier, found, err := ds.GetNotifier(allAccessCtx, notifierConfig.GetId()) if err != nil { return nil, err } @@ -461,8 +465,8 @@ func (s *serviceImpl) convertProtoNotifierConfigToV2(notifierConfig *storage.Not }, nil } -// convertProtoScheduleToV2 converts storage.Schedule to v2.ReportSchedule. Does not validate storage.Schedule -func (s *serviceImpl) convertProtoScheduleToV2(schedule *storage.Schedule) *apiV2.ReportSchedule { +// ConvertProtoScheduleToV2 converts storage.Schedule to v2.ReportSchedule +func ConvertProtoScheduleToV2(schedule *storage.Schedule) *apiV2.ReportSchedule { if schedule == nil { return nil } @@ -510,13 +514,15 @@ func (s *serviceImpl) convertProtoReportCollectiontoV2(collection *storage.Colle } } -// convertProtoNotifierSnapshotToV2 converts notifiersnapshot proto to v2 -func (s *serviceImpl) convertProtoNotifierSnapshotToV2(notifierSnapshot *storage.NotifierSnapshot) *apiV2.NotifierConfiguration { +// ConvertProtoNotifierSnapshotToV2 converts storage.NotifierSnapshot to apiV2.NotifierConfiguration. +func ConvertProtoNotifierSnapshotToV2(notifierSnapshot *storage.NotifierSnapshot) *apiV2.NotifierConfiguration { if notifierSnapshot == nil { return nil } if notifierSnapshot.GetEmailConfig() == nil { - return &apiV2.NotifierConfiguration{} + return &apiV2.NotifierConfiguration{ + NotifierName: notifierSnapshot.GetNotifierName(), + } } return &apiV2.NotifierConfiguration{ @@ -586,26 +592,31 @@ func (s *serviceImpl) convertProtoReportSnapshotstoV2(snapshots []*storage.Repor return nil, err } } + parentDir := snapshot.GetReportConfigurationId() + if snapshot.GetViewBasedVulnReportFilters() != nil { + parentDir = "view-based-report" + } snapshotv2 := &apiV2.ReportSnapshot{ ReportStatus: s.convertPrototoV2Reportstatus(snapshot.GetReportStatus()), ReportConfigId: snapshot.GetReportConfigurationId(), ReportJobId: snapshot.GetReportId(), Name: snapshot.GetName(), Description: snapshot.GetDescription(), + Type: apiV2.ReportSnapshot_ReportType(snapshot.GetType()), CollectionSnapshot: s.convertProtoReportCollectiontoV2(snapshot.GetCollection()), User: &apiV2.SlimUser{ Id: snapshot.GetRequester().GetId(), Name: snapshot.GetRequester().GetName(), }, - Schedule: s.convertProtoScheduleToV2(snapshot.GetSchedule()), + Schedule: ConvertProtoScheduleToV2(snapshot.GetSchedule()), + ResourceScope: resourceScope, + IsDownloadAvailable: blobNames.Contains(common.GetReportBlobPath(parentDir, snapshot.GetReportId())), Filter: &apiV2.ReportSnapshot_VulnReportFilters{ VulnReportFilters: s.convertProtoVulnReportFiltersToV2(snapshot.GetVulnReportFilters()), }, - ResourceScope: resourceScope, - IsDownloadAvailable: blobNames.Contains(common.GetReportBlobPath(snapshot.GetReportConfigurationId(), snapshot.GetReportId())), } for _, notifier := range snapshot.GetNotifiers() { - converted := s.convertProtoNotifierSnapshotToV2(notifier) + converted := ConvertProtoNotifierSnapshotToV2(notifier) if converted != nil { snapshotv2.Notifiers = append(snapshotv2.Notifiers, converted) } @@ -620,7 +631,7 @@ func (s *serviceImpl) getExistingBlobNames(snapshots []*storage.ReportSnapshot) blobNames := make([]string, 0) for _, snap := range snapshots { status := snap.GetReportStatus() - if status.GetReportNotificationMethod() == storage.ReportStatus_DOWNLOAD { + if status != nil && status.GetReportNotificationMethod() == storage.ReportStatus_DOWNLOAD { if status.GetRunState() == storage.ReportStatus_GENERATED || status.GetRunState() == storage.ReportStatus_DELIVERED { parentDir := snap.GetReportConfigurationId() diff --git a/central/reports/service/v2/convert_test.go b/central/reports/service/v2/convert_test.go index 1e8d673c59b1f..d362061f463ab 100644 --- a/central/reports/service/v2/convert_test.go +++ b/central/reports/service/v2/convert_test.go @@ -479,7 +479,7 @@ func (s *typeConversionTestSuite) TestConvertProtoScheduleToV2() { for _, c := range cases { s.T().Run(c.testname, func(t *testing.T) { - converted := s.service.convertProtoScheduleToV2(c.schedule) + converted := ConvertProtoScheduleToV2(c.schedule) protoassert.Equal(t, c.result, converted) }) } @@ -515,7 +515,7 @@ func (s *typeConversionTestSuite) TestConvertSchedulePreservesTime() { Hour: c.hour, Minute: c.minute, } - converted := s.service.convertV2ScheduleToProto(v2Schedule) + converted := ConvertV2ScheduleToProto(v2Schedule) assert.Equal(t, c.hour, converted.GetHour()) assert.Equal(t, c.minute, converted.GetMinute()) assert.Equal(t, storage.Schedule_DAILY, converted.GetIntervalType()) @@ -527,7 +527,7 @@ func (s *typeConversionTestSuite) TestConvertSchedulePreservesTime() { Hour: c.hour, Minute: c.minute, } - converted := s.service.convertProtoScheduleToV2(storageSchedule) + converted := ConvertProtoScheduleToV2(storageSchedule) assert.Equal(t, c.hour, converted.GetHour()) assert.Equal(t, c.minute, converted.GetMinute()) assert.Equal(t, apiV2.ReportSchedule_DAILY, converted.GetIntervalType()) @@ -542,8 +542,8 @@ func (s *typeConversionTestSuite) TestConvertSchedulePreservesTime() { DaysOfWeek: &apiV2.ReportSchedule_DaysOfWeek{Days: []int32{1}}, }, } - storage := s.service.convertV2ScheduleToProto(v2Schedule) - roundTripped := s.service.convertProtoScheduleToV2(storage) + storage := ConvertV2ScheduleToProto(v2Schedule) + roundTripped := ConvertProtoScheduleToV2(storage) assert.Equal(t, c.hour, roundTripped.GetHour()) assert.Equal(t, c.minute, roundTripped.GetMinute()) }) @@ -580,7 +580,7 @@ func (s *typeConversionTestSuite) TestConvertV2ScheduleToProto() { for _, c := range cases { s.T().Run(c.testname, func(t *testing.T) { - converted := s.service.convertV2ScheduleToProto(c.schedule) + converted := ConvertV2ScheduleToProto(c.schedule) protoassert.Equal(t, c.result, converted) }) } @@ -647,3 +647,120 @@ func newScheduleV2(minute int32, hour int32, weekdays []int32, daysOfMonth []int } return &sched } + +func TestEnumConversions(t *testing.T) { + t.Run("v2EntityTypeToStorage", func(t *testing.T) { + tests := map[string]struct { + input apiV2.ScopeEntity + expected storage.EntityType + }{ + "deployment": {apiV2.ScopeEntity_SCOPE_ENTITY_DEPLOYMENT, storage.EntityType_ENTITY_TYPE_DEPLOYMENT}, + "namespace": {apiV2.ScopeEntity_SCOPE_ENTITY_NAMESPACE, storage.EntityType_ENTITY_TYPE_NAMESPACE}, + "cluster": {apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, storage.EntityType_ENTITY_TYPE_CLUSTER}, + "unset": {apiV2.ScopeEntity_SCOPE_ENTITY_UNSET, storage.EntityType_ENTITY_TYPE_UNSET}, + "unknown": {apiV2.ScopeEntity(999), storage.EntityType_ENTITY_TYPE_UNSET}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := v2EntityTypeToStorage(tc.input) + assert.Equal(t, tc.expected, result) + }) + } + }) + + t.Run("v2EntityFieldToStorage", func(t *testing.T) { + tests := map[string]struct { + input apiV2.ScopeField + expected storage.EntityField + }{ + "id": {apiV2.ScopeField_FIELD_ID, storage.EntityField_FIELD_ID}, + "name": {apiV2.ScopeField_FIELD_NAME, storage.EntityField_FIELD_NAME}, + "label": {apiV2.ScopeField_FIELD_LABEL, storage.EntityField_FIELD_LABEL}, + "annotation": {apiV2.ScopeField_FIELD_ANNOTATION, storage.EntityField_FIELD_ANNOTATION}, + "unset": {apiV2.ScopeField_FIELD_UNSET, storage.EntityField_FIELD_UNSET}, + "unknown": {apiV2.ScopeField(999), storage.EntityField_FIELD_UNSET}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := v2EntityFieldToStorage(tc.input) + assert.Equal(t, tc.expected, result) + }) + } + }) + + t.Run("storageEntityTypeToV2", func(t *testing.T) { + tests := map[string]struct { + input storage.EntityType + expected apiV2.ScopeEntity + }{ + "deployment": {storage.EntityType_ENTITY_TYPE_DEPLOYMENT, apiV2.ScopeEntity_SCOPE_ENTITY_DEPLOYMENT}, + "namespace": {storage.EntityType_ENTITY_TYPE_NAMESPACE, apiV2.ScopeEntity_SCOPE_ENTITY_NAMESPACE}, + "cluster": {storage.EntityType_ENTITY_TYPE_CLUSTER, apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER}, + "unset": {storage.EntityType_ENTITY_TYPE_UNSET, apiV2.ScopeEntity_SCOPE_ENTITY_UNSET}, + "unknown": {storage.EntityType(999), apiV2.ScopeEntity_SCOPE_ENTITY_UNSET}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := storageEntityTypeToV2(tc.input) + assert.Equal(t, tc.expected, result) + }) + } + }) + + t.Run("storageEntityFieldToV2", func(t *testing.T) { + tests := map[string]struct { + input storage.EntityField + expected apiV2.ScopeField + }{ + "id": {storage.EntityField_FIELD_ID, apiV2.ScopeField_FIELD_ID}, + "name": {storage.EntityField_FIELD_NAME, apiV2.ScopeField_FIELD_NAME}, + "label": {storage.EntityField_FIELD_LABEL, apiV2.ScopeField_FIELD_LABEL}, + "annotation": {storage.EntityField_FIELD_ANNOTATION, apiV2.ScopeField_FIELD_ANNOTATION}, + "unset": {storage.EntityField_FIELD_UNSET, apiV2.ScopeField_FIELD_UNSET}, + "unknown": {storage.EntityField(999), apiV2.ScopeField_FIELD_UNSET}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := storageEntityFieldToV2(tc.input) + assert.Equal(t, tc.expected, result) + }) + } + }) +} + +func TestConvertProtoNotifierSnapshotToV2(t *testing.T) { + t.Run("nil snapshot", func(t *testing.T) { + result := ConvertProtoNotifierSnapshotToV2(nil) + assert.Nil(t, result) + }) + + t.Run("snapshot without email config", func(t *testing.T) { + snapshot := &storage.NotifierSnapshot{ + NotifierName: "test-notifier", + } + result := ConvertProtoNotifierSnapshotToV2(snapshot) + assert.NotNil(t, result) + assert.Equal(t, "test-notifier", result.GetNotifierName()) + }) + + t.Run("snapshot with email config", func(t *testing.T) { + snapshot := &storage.NotifierSnapshot{ + NotifierName: "test-notifier", + NotifierConfig: &storage.NotifierSnapshot_EmailConfig{ + EmailConfig: &storage.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{"user@example.com"}, + CustomSubject: "Test Subject", + CustomBody: "Test Body", + }, + }, + } + result := ConvertProtoNotifierSnapshotToV2(snapshot) + assert.NotNil(t, result) + assert.Equal(t, "test-notifier", result.GetNotifierName()) + assert.Equal(t, "notifier-1", result.GetEmailConfig().GetNotifierId()) + assert.Equal(t, []string{"user@example.com"}, result.GetEmailConfig().GetMailingLists()) + assert.Equal(t, "Test Subject", result.GetEmailConfig().GetCustomSubject()) + assert.Equal(t, "Test Body", result.GetEmailConfig().GetCustomBody()) + }) +} diff --git a/central/reports/service/v2/node/convert.go b/central/reports/service/v2/node/convert.go new file mode 100644 index 0000000000000..00e8671837ee7 --- /dev/null +++ b/central/reports/service/v2/node/convert.go @@ -0,0 +1,373 @@ +package node + +import ( + "context" + + "github.com/pkg/errors" + "github.com/stackrox/rox/central/reports/common" + v2 "github.com/stackrox/rox/central/reports/service/v2" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/set" +) + +var ( + storageRunStateToV2 = map[storage.ReportStatus_RunState]apiV2.ReportStatus_RunState{ + storage.ReportStatus_WAITING: apiV2.ReportStatus_WAITING, + storage.ReportStatus_PREPARING: apiV2.ReportStatus_PREPARING, + storage.ReportStatus_GENERATED: apiV2.ReportStatus_GENERATED, + storage.ReportStatus_DELIVERED: apiV2.ReportStatus_DELIVERED, + storage.ReportStatus_FAILURE: apiV2.ReportStatus_FAILURE, + } + + // Use this context only to populate notifier names and IsDownloadAvailable fields in converted responses + allAccessCtx = sac.WithAllAccess(context.Background()) +) + +// convertV2ReportConfigurationToProto converts v2.ReportConfiguration to storage.ReportConfiguration for node reports +func (s *serviceImpl) convertV2ReportConfigurationToProto(config *apiV2.ReportConfiguration, creator *storage.SlimUser, + accessScopeRules []*storage.SimpleAccessScope_Rules) (*storage.ReportConfiguration, error) { + if config == nil { + return nil, nil + } + + resourceScope, err := s.convertV2ResourceScopeToProto(config.GetResourceScope()) + if err != nil { + return nil, err + } + + ret := &storage.ReportConfiguration{ + Id: config.GetId(), + Name: config.GetName(), + Description: config.GetDescription(), + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Schedule: v2.ConvertV2ScheduleToProto(config.GetSchedule()), + ResourceScope: resourceScope, + Creator: creator, + Version: 2, + } + + if config.GetNodeVulnReportFilters() != nil { + ret.Filter = &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: convertV2NodeReportFiltersToProto(config.GetNodeVulnReportFilters(), accessScopeRules), + } + } + + for _, notifier := range config.GetNotifiers() { + ret.Notifiers = append(ret.Notifiers, v2.ConvertV2NotifierConfigToProto(notifier)) + } + + return ret, nil +} + +func convertV2NodeReportFiltersToProto(filters *apiV2.NodeVulnerabilityReportFilters, + accessScopeRules []*storage.SimpleAccessScope_Rules) *storage.NodeVulnerabilityReportFilters { + if filters == nil { + return nil + } + + ret := &storage.NodeVulnerabilityReportFilters{ + AccessScopeRules: accessScopeRules, + Query: filters.GetQuery(), + } + + switch filters.GetCvesSince().(type) { + case *apiV2.NodeVulnerabilityReportFilters_AllVuln: + ret.CvesSince = &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: filters.GetAllVuln(), + } + } + + return ret +} + +func (s *serviceImpl) convertV2ResourceScopeToProto(scope *apiV2.ResourceScope) (*storage.ResourceScope, error) { + if scope == nil { + return nil, nil + } + + ret := &storage.ResourceScope{} + switch ref := scope.GetScopeReference().(type) { + case *apiV2.ResourceScope_EntityScope: + ret.ScopeReference = &storage.ResourceScope_EntityScope{ + EntityScope: convertV2EntityScopeToStorage(ref.EntityScope), + } + case nil: + return nil, errors.New("resource scope reference is required") + default: + return nil, errors.Errorf("unsupported resource scope type: %T", ref) + } + return ret, nil +} + +func convertV2EntityScopeToStorage(es *apiV2.EntityScope) *storage.EntityScope { + if es == nil { + return nil + } + rules := make([]*storage.EntityScopeRule, 0, len(es.GetRules())) + for _, rule := range es.GetRules() { + sr := &storage.EntityScopeRule{ + Entity: v2EntityTypeToStorage(rule.GetEntity()), + Field: v2EntityFieldToStorage(rule.GetField()), + } + for _, rv := range rule.GetValues() { + sr.Values = append(sr.Values, &storage.RuleValue{ + Value: rv.GetValue(), + MatchType: v2MatchTypeToStorage(rv.GetMatchType()), + }) + } + rules = append(rules, sr) + } + return &storage.EntityScope{Rules: rules} +} + +func v2EntityTypeToStorage(e apiV2.ScopeEntity) storage.EntityType { + switch e { + case apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER: + return storage.EntityType_ENTITY_TYPE_CLUSTER + default: + return storage.EntityType_ENTITY_TYPE_UNSET + } +} + +func v2EntityFieldToStorage(f apiV2.ScopeField) storage.EntityField { + switch f { + case apiV2.ScopeField_FIELD_ID: + return storage.EntityField_FIELD_ID + case apiV2.ScopeField_FIELD_NAME: + return storage.EntityField_FIELD_NAME + case apiV2.ScopeField_FIELD_LABEL: + return storage.EntityField_FIELD_LABEL + default: + return storage.EntityField_FIELD_UNSET + } +} + +func v2MatchTypeToStorage(m apiV2.MatchType) storage.MatchType { + switch m { + case apiV2.MatchType_REGEX: + return storage.MatchType_REGEX + default: + return storage.MatchType_EXACT + } +} + +// Proto to V2 conversions + +func (s *serviceImpl) convertProtoReportConfigurationToV2(config *storage.ReportConfiguration) (*apiV2.ReportConfiguration, error) { + if config == nil { + return nil, nil + } + + resourceScope := convertProtoResourceScopeToV2(config.GetResourceScope()) + + ret := &apiV2.ReportConfiguration{ + Id: config.GetId(), + Name: config.GetName(), + Description: config.GetDescription(), + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + Schedule: v2.ConvertProtoScheduleToV2(config.GetSchedule()), + ResourceScope: resourceScope, + } + + if config.GetNodeVulnReportFilters() != nil { + ret.Filter = &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: convertProtoNodeReportFiltersToV2(config.GetNodeVulnReportFilters()), + } + } + + for _, notifier := range config.GetNotifiers() { + converted, err := v2.ConvertProtoNotifierConfigToV2(s.notifierDatastore, notifier) + if err != nil { + return nil, err + } + if converted != nil { + ret.Notifiers = append(ret.Notifiers, converted) + } + } + + return ret, nil +} + +func convertProtoNodeReportFiltersToV2(filters *storage.NodeVulnerabilityReportFilters) *apiV2.NodeVulnerabilityReportFilters { + if filters == nil { + return nil + } + + ret := &apiV2.NodeVulnerabilityReportFilters{ + Query: filters.GetQuery(), + } + + switch filters.GetCvesSince().(type) { + case *storage.NodeVulnerabilityReportFilters_AllVuln: + ret.CvesSince = &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: filters.GetAllVuln(), + } + } + + return ret +} + +func convertProtoResourceScopeToV2(scope *storage.ResourceScope) *apiV2.ResourceScope { + if scope == nil { + return nil + } + + ret := &apiV2.ResourceScope{} + if ref, ok := scope.GetScopeReference().(*storage.ResourceScope_EntityScope); ok { + ret.ScopeReference = &apiV2.ResourceScope_EntityScope{EntityScope: convertStorageEntityScopeToV2(ref.EntityScope)} + } + return ret +} + +func convertStorageEntityScopeToV2(es *storage.EntityScope) *apiV2.EntityScope { + if es == nil { + return nil + } + rules := make([]*apiV2.EntityScopeRule, 0, len(es.GetRules())) + for _, rule := range es.GetRules() { + sr := &apiV2.EntityScopeRule{ + Entity: storageEntityTypeToV2(rule.GetEntity()), + Field: storageEntityFieldToV2(rule.GetField()), + } + for _, rv := range rule.GetValues() { + sr.Values = append(sr.Values, &apiV2.RuleValue{ + Value: rv.GetValue(), + MatchType: storageMatchTypeToV2(rv.GetMatchType()), + }) + } + rules = append(rules, sr) + } + return &apiV2.EntityScope{Rules: rules} +} + +func storageEntityTypeToV2(e storage.EntityType) apiV2.ScopeEntity { + switch e { + case storage.EntityType_ENTITY_TYPE_CLUSTER: + return apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER + default: + return apiV2.ScopeEntity_SCOPE_ENTITY_UNSET + } +} + +func storageEntityFieldToV2(f storage.EntityField) apiV2.ScopeField { + switch f { + case storage.EntityField_FIELD_ID: + return apiV2.ScopeField_FIELD_ID + case storage.EntityField_FIELD_NAME: + return apiV2.ScopeField_FIELD_NAME + case storage.EntityField_FIELD_LABEL: + return apiV2.ScopeField_FIELD_LABEL + default: + return apiV2.ScopeField_FIELD_UNSET + } +} + +func storageMatchTypeToV2(m storage.MatchType) apiV2.MatchType { + switch m { + case storage.MatchType_REGEX: + return apiV2.MatchType_REGEX + default: + return apiV2.MatchType_EXACT + } +} + +// reportBlobParentDir returns the blob directory for a snapshot. View-based +// report blobs are stored under "view-based-report", not under a report config ID. +func reportBlobParentDir(snapshot *storage.ReportSnapshot) string { + if snapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_VIEW_BASED { + return "view-based-report" + } + return snapshot.GetReportConfigurationId() +} + +func isDownloadAvailable(blobNames set.FrozenStringSet, snapshot *storage.ReportSnapshot) bool { + return blobNames.Contains(common.GetReportBlobPath(reportBlobParentDir(snapshot), snapshot.GetReportId())) +} + +func (s *serviceImpl) convertProtoReportSnapshotToV2(snapshot *storage.ReportSnapshot, blobNames set.FrozenStringSet) (*apiV2.ReportSnapshot, error) { + if snapshot == nil { + return nil, nil + } + + resourceScope := convertProtoResourceScopeToV2(snapshot.GetResourceScope()) + + ret := &apiV2.ReportSnapshot{ + ReportStatus: convertPrototoV2Reportstatus(snapshot.GetReportStatus()), + ReportConfigId: snapshot.GetReportConfigurationId(), + ReportJobId: snapshot.GetReportId(), + Name: snapshot.GetName(), + Description: snapshot.GetDescription(), + AreaOfConcern: snapshot.GetAreaOfConcern(), + Type: apiV2.ReportSnapshot_NODE_VULNERABILITY, + User: &apiV2.SlimUser{ + Id: snapshot.GetRequester().GetId(), + Name: snapshot.GetRequester().GetName(), + }, + Schedule: v2.ConvertProtoScheduleToV2(snapshot.GetSchedule()), + ResourceScope: resourceScope, + IsDownloadAvailable: isDownloadAvailable(blobNames, snapshot), + } + + if snapshot.GetNodeVulnReportFilters() != nil { + ret.Filter = &apiV2.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: convertProtoNodeReportFiltersToV2(snapshot.GetNodeVulnReportFilters()), + } + } + + for _, notifier := range snapshot.GetNotifiers() { + converted := v2.ConvertProtoNotifierSnapshotToV2(notifier) + if converted != nil { + ret.Notifiers = append(ret.Notifiers, converted) + } + } + + return ret, nil +} + +func convertPrototoV2Reportstatus(status *storage.ReportStatus) *apiV2.ReportStatus { + if status == nil { + return nil + } + + ret := &apiV2.ReportStatus{ + ReportNotificationMethod: apiV2.NotificationMethod(status.GetReportNotificationMethod()), + ReportRequestType: apiV2.ReportStatus_ReportMethod(status.GetReportRequestType()), + RunState: storageRunStateToV2[status.GetRunState()], + ErrorMsg: status.GetErrorMsg(), + CompletedAt: status.GetCompletedAt(), + } + + return ret +} + +func (s *serviceImpl) getExistingBlobNames(snapshots []*storage.ReportSnapshot) (set.FrozenStringSet, error) { + if len(snapshots) == 0 { + return set.NewFrozenStringSet(), nil + } + + blobNames := make([]string, 0, len(snapshots)) + for _, snapshot := range snapshots { + status := snapshot.GetReportStatus() + if status.GetReportNotificationMethod() == storage.ReportStatus_DOWNLOAD { + if status.GetRunState() == storage.ReportStatus_GENERATED || + status.GetRunState() == storage.ReportStatus_DELIVERED { + blobNames = append(blobNames, common.GetReportBlobPath(reportBlobParentDir(snapshot), snapshot.GetReportId())) + } + } + } + + if len(blobNames) == 0 { + return set.NewFrozenStringSet(), nil + } + + query := search.NewQueryBuilder().AddExactMatches(search.BlobName, blobNames...).ProtoQuery() + results, err := s.blobStore.Search(allAccessCtx, query) + if err != nil { + return set.NewFrozenStringSet(), errors.Wrap(err, "failed to search for report blobs") + } + + return search.ResultsToIDSet(results).Freeze(), nil +} diff --git a/central/reports/service/v2/node/convert_test.go b/central/reports/service/v2/node/convert_test.go new file mode 100644 index 0000000000000..6457e95310323 --- /dev/null +++ b/central/reports/service/v2/node/convert_test.go @@ -0,0 +1,860 @@ +package node + +import ( + "testing" + + blobDSMocks "github.com/stackrox/rox/central/blob/datastore/mocks" + notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" + "github.com/stackrox/rox/central/reports/common" + reportConfigDSMocks "github.com/stackrox/rox/central/reports/config/datastore/mocks" + schedulerMocks "github.com/stackrox/rox/central/reports/scheduler/v2/mocks" + v2 "github.com/stackrox/rox/central/reports/service/v2" + reportSnapshotDSMocks "github.com/stackrox/rox/central/reports/snapshot/datastore/mocks" + "github.com/stackrox/rox/central/reports/validation" + collectionDSMocks "github.com/stackrox/rox/central/resourcecollection/datastore/mocks" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/protoassert" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/set" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +func TestConversions(t *testing.T) { + suite.Run(t, new(ConversionTestSuite)) +} + +type ConversionTestSuite struct { + suite.Suite + mockCtrl *gomock.Controller + service *serviceImpl +} + +func (s *ConversionTestSuite) SetupTest() { + s.mockCtrl = gomock.NewController(s.T()) + reportConfigDataStore := reportConfigDSMocks.NewMockDataStore(s.mockCtrl) + reportSnapshotDataStore := reportSnapshotDSMocks.NewMockDataStore(s.mockCtrl) + collectionDataStore := collectionDSMocks.NewMockDataStore(s.mockCtrl) + notifierDataStore := notifierDSMocks.NewMockDataStore(s.mockCtrl) + blobStore := blobDSMocks.NewMockDatastore(s.mockCtrl) + scheduler := schedulerMocks.NewMockScheduler(s.mockCtrl) + validator := validation.New(reportConfigDataStore, reportSnapshotDataStore, collectionDataStore, notifierDataStore) + + s.service = &serviceImpl{ + reportConfigStore: reportConfigDataStore, + snapshotDatastore: reportSnapshotDataStore, + collectionDatastore: collectionDataStore, + notifierDatastore: notifierDataStore, + scheduler: scheduler, + blobStore: blobStore, + validator: validator, + } +} + +func (s *ConversionTestSuite) TearDownTest() { + s.mockCtrl.Finish() +} + +func (s *ConversionTestSuite) TestScheduleConversions() { + testCases := []struct { + name string + v2Schedule *apiV2.ReportSchedule + }{ + { + name: "Daily schedule", + v2Schedule: &apiV2.ReportSchedule{ + IntervalType: apiV2.ReportSchedule_DAILY, + Hour: 14, + Minute: 30, + }, + }, + { + name: "Weekly schedule", + v2Schedule: &apiV2.ReportSchedule{ + IntervalType: apiV2.ReportSchedule_WEEKLY, + Hour: 9, + Minute: 0, + Interval: &apiV2.ReportSchedule_DaysOfWeek_{ + DaysOfWeek: &apiV2.ReportSchedule_DaysOfWeek{ + Days: []int32{1, 3, 5}, + }, + }, + }, + }, + { + name: "Monthly schedule", + v2Schedule: &apiV2.ReportSchedule{ + IntervalType: apiV2.ReportSchedule_MONTHLY, + Hour: 10, + Minute: 15, + Interval: &apiV2.ReportSchedule_DaysOfMonth_{ + DaysOfMonth: &apiV2.ReportSchedule_DaysOfMonth{ + Days: []int32{1, 15}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + protoSchedule := v2.ConvertV2ScheduleToProto(tc.v2Schedule) + assert.NotNil(s.T(), protoSchedule) + assert.Equal(s.T(), tc.v2Schedule.GetHour(), protoSchedule.GetHour()) + assert.Equal(s.T(), tc.v2Schedule.GetMinute(), protoSchedule.GetMinute()) + + v2Schedule := v2.ConvertProtoScheduleToV2(protoSchedule) + assert.NotNil(s.T(), v2Schedule) + assert.Equal(s.T(), tc.v2Schedule.GetIntervalType(), v2Schedule.GetIntervalType()) + assert.Equal(s.T(), tc.v2Schedule.GetHour(), v2Schedule.GetHour()) + assert.Equal(s.T(), tc.v2Schedule.GetMinute(), v2Schedule.GetMinute()) + + if tc.v2Schedule.GetDaysOfWeek() != nil { + assert.Equal(s.T(), tc.v2Schedule.GetDaysOfWeek().GetDays(), v2Schedule.GetDaysOfWeek().GetDays()) + } + if tc.v2Schedule.GetDaysOfMonth() != nil { + assert.Equal(s.T(), tc.v2Schedule.GetDaysOfMonth().GetDays(), v2Schedule.GetDaysOfMonth().GetDays()) + } + }) + } +} + +func (s *ConversionTestSuite) TestNotifierConversions() { + v2Notifier := &apiV2.NotifierConfiguration{ + NotifierConfig: &apiV2.NotifierConfiguration_EmailConfig{ + EmailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier-123", + MailingLists: []string{"test@example.com", "team@example.com"}, + CustomSubject: "Custom Subject", + CustomBody: "Custom Body", + }, + }, + } + + protoNotifier := v2.ConvertV2NotifierConfigToProto(v2Notifier) + assert.NotNil(s.T(), protoNotifier) + assert.Equal(s.T(), "notifier-123", protoNotifier.GetId()) + assert.Equal(s.T(), "Custom Subject", protoNotifier.GetEmailConfig().GetCustomSubject()) + + // Mock GetNotifier for the conversion back + notifierDataStore := s.service.notifierDatastore.(*notifierDSMocks.MockDataStore) + notifierDataStore.EXPECT().GetNotifier(gomock.Any(), "notifier-123").Return(&storage.Notifier{ + Id: "notifier-123", + Name: "Test Notifier", + }, true, nil).Times(1) + + v2NotifierBack, err := v2.ConvertProtoNotifierConfigToV2(s.service.notifierDatastore, protoNotifier) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), v2NotifierBack) + assert.Equal(s.T(), "Test Notifier", v2NotifierBack.GetNotifierName()) + assert.Equal(s.T(), v2Notifier.GetEmailConfig().GetNotifierId(), v2NotifierBack.GetEmailConfig().GetNotifierId()) + assert.Equal(s.T(), v2Notifier.GetEmailConfig().GetMailingLists(), v2NotifierBack.GetEmailConfig().GetMailingLists()) +} + +func (s *ConversionTestSuite) TestNotifierSnapshotConversion() { + protoSnapshot := &storage.NotifierSnapshot{ + NotifierName: "Email Notifier", + NotifierConfig: &storage.NotifierSnapshot_EmailConfig{ + EmailConfig: &storage.EmailNotifierConfiguration{ + NotifierId: "notifier-456", + MailingLists: []string{"alerts@example.com"}, + CustomSubject: "Snapshot Subject", + CustomBody: "Snapshot Body", + }, + }, + } + + v2Config := v2.ConvertProtoNotifierSnapshotToV2(protoSnapshot) + assert.NotNil(s.T(), v2Config) + assert.Equal(s.T(), "Email Notifier", v2Config.GetNotifierName()) + assert.Equal(s.T(), "notifier-456", v2Config.GetEmailConfig().GetNotifierId()) + assert.Equal(s.T(), "Snapshot Subject", v2Config.GetEmailConfig().GetCustomSubject()) + assert.Equal(s.T(), []string{"alerts@example.com"}, v2Config.GetEmailConfig().GetMailingLists()) +} + +func (s *ConversionTestSuite) TestEntityScopeConversions() { + testCases := []struct { + name string + entityType apiV2.ScopeEntity + field apiV2.ScopeField + matchType apiV2.MatchType + }{ + { + name: "Cluster ID exact match", + entityType: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_ID, + matchType: apiV2.MatchType_EXACT, + }, + { + name: "Cluster name regex match", + entityType: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_NAME, + matchType: apiV2.MatchType_REGEX, + }, + { + name: "Cluster label exact match", + entityType: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_LABEL, + matchType: apiV2.MatchType_EXACT, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + storageType := v2EntityTypeToStorage(tc.entityType) + v2Type := storageEntityTypeToV2(storageType) + assert.Equal(s.T(), tc.entityType, v2Type) + + storageField := v2EntityFieldToStorage(tc.field) + v2Field := storageEntityFieldToV2(storageField) + assert.Equal(s.T(), tc.field, v2Field) + + storageMatch := v2MatchTypeToStorage(tc.matchType) + v2Match := storageMatchTypeToV2(storageMatch) + assert.Equal(s.T(), tc.matchType, v2Match) + }) + } +} + +func (s *ConversionTestSuite) TestReportStatusConversion() { + testCases := map[string]struct { + requestType storage.ReportStatus_RunMethod + want apiV2.ReportStatus_ReportMethod + }{ + "on-demand": { + requestType: storage.ReportStatus_ON_DEMAND, + want: apiV2.ReportStatus_ON_DEMAND, + }, + "scheduled": { + requestType: storage.ReportStatus_SCHEDULED, + want: apiV2.ReportStatus_SCHEDULED, + }, + "view-based": { + requestType: storage.ReportStatus_VIEW_BASED, + want: apiV2.ReportStatus_ReportMethod(storage.ReportStatus_VIEW_BASED), + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + protoStatus := &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + ReportRequestType: tc.requestType, + ErrorMsg: "test error", + } + + v2Status := convertPrototoV2Reportstatus(protoStatus) + assert.NotNil(s.T(), v2Status) + assert.Equal(s.T(), apiV2.ReportStatus_GENERATED, v2Status.GetRunState()) + assert.Equal(s.T(), apiV2.NotificationMethod_DOWNLOAD, v2Status.GetReportNotificationMethod()) + assert.Equal(s.T(), "test error", v2Status.GetErrorMsg()) + assert.Equal(s.T(), tc.want, v2Status.GetReportRequestType()) + }) + } +} + +func (s *ConversionTestSuite) TestNodeFiltersConversion() { + v2Filters := &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:prod", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + } + + accessScopeRules := []*storage.SimpleAccessScope_Rules{ + { + IncludedClusters: []string{"cluster-1"}, + }, + } + + protoFilters := convertV2NodeReportFiltersToProto(v2Filters, accessScopeRules) + assert.NotNil(s.T(), protoFilters) + assert.Equal(s.T(), "Cluster:prod", protoFilters.GetQuery()) + assert.True(s.T(), protoFilters.GetAllVuln()) + protoassert.SlicesEqual(s.T(), accessScopeRules, protoFilters.GetAccessScopeRules()) + + v2FiltersBack := convertProtoNodeReportFiltersToV2(protoFilters) + assert.NotNil(s.T(), v2FiltersBack) + assert.Equal(s.T(), v2Filters.GetQuery(), v2FiltersBack.GetQuery()) + assert.True(s.T(), v2FiltersBack.GetAllVuln()) +} + +func (s *ConversionTestSuite) TestReportConfigurationRoundTrip() { + creator := &storage.SlimUser{ + Id: "user-123", + Name: "Test User", + } + + accessScopeRules := []*storage.SimpleAccessScope_Rules{ + { + IncludedClusters: []string{"cluster-1", "cluster-2"}, + }, + } + + v2Config := &apiV2.ReportConfiguration{ + Id: "config-123", + Name: "Node Report", + Description: "Test node vulnerability report", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_ID, + Values: []*apiV2.RuleValue{ + { + Value: "cluster-1", + MatchType: apiV2.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:prod", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + Schedule: &apiV2.ReportSchedule{ + IntervalType: apiV2.ReportSchedule_WEEKLY, + Hour: 9, + Minute: 0, + Interval: &apiV2.ReportSchedule_DaysOfWeek_{ + DaysOfWeek: &apiV2.ReportSchedule_DaysOfWeek{ + Days: []int32{1, 3, 5}, + }, + }, + }, + Notifiers: []*apiV2.NotifierConfiguration{ + { + NotifierConfig: &apiV2.NotifierConfiguration_EmailConfig{ + EmailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "email-1", + MailingLists: []string{"team@example.com"}, + }, + }, + }, + }, + } + + protoConfig, err := s.service.convertV2ReportConfigurationToProto(v2Config, creator, accessScopeRules) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), protoConfig) + assert.Equal(s.T(), v2Config.GetId(), protoConfig.GetId()) + assert.Equal(s.T(), v2Config.GetName(), protoConfig.GetName()) + assert.Equal(s.T(), storage.ReportConfiguration_NODE_VULNERABILITY, protoConfig.GetType()) + protoassert.Equal(s.T(), creator, protoConfig.GetCreator()) + + // Mock GetNotifier for the conversion back + notifierDataStore := s.service.notifierDatastore.(*notifierDSMocks.MockDataStore) + notifierDataStore.EXPECT().GetNotifier(gomock.Any(), "email-1").Return(&storage.Notifier{ + Id: "email-1", + Name: "Email Notifier", + }, true, nil).Times(1) + + v2ConfigBack, err := s.service.convertProtoReportConfigurationToV2(protoConfig) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), v2ConfigBack) + assert.Equal(s.T(), v2Config.GetId(), v2ConfigBack.GetId()) + assert.Equal(s.T(), v2Config.GetName(), v2ConfigBack.GetName()) + assert.Equal(s.T(), apiV2.ReportConfiguration_NODE_VULNERABILITY, v2ConfigBack.GetType()) + assert.Len(s.T(), v2ConfigBack.GetNotifiers(), 1) + assert.Equal(s.T(), "Email Notifier", v2ConfigBack.GetNotifiers()[0].GetNotifierName()) +} + +func (s *ConversionTestSuite) TestNilConversions() { + assert.Nil(s.T(), v2.ConvertV2ScheduleToProto(nil)) + assert.Nil(s.T(), v2.ConvertProtoScheduleToV2(nil)) + assert.Nil(s.T(), v2.ConvertV2NotifierConfigToProto(nil)) + + result, err := v2.ConvertProtoNotifierConfigToV2(s.service.notifierDatastore, nil) + assert.NoError(s.T(), err) + assert.Nil(s.T(), result) + + assert.Nil(s.T(), v2.ConvertProtoNotifierSnapshotToV2(nil)) + assert.Nil(s.T(), convertPrototoV2Reportstatus(nil)) + assert.Nil(s.T(), convertV2NodeReportFiltersToProto(nil, nil)) + assert.Nil(s.T(), convertProtoNodeReportFiltersToV2(nil)) +} + +func (s *ConversionTestSuite) TestEntityTypeEdgeCases() { + assert.Equal(s.T(), storage.EntityType_ENTITY_TYPE_UNSET, v2EntityTypeToStorage(apiV2.ScopeEntity_SCOPE_ENTITY_UNSET)) + assert.Equal(s.T(), apiV2.ScopeEntity_SCOPE_ENTITY_UNSET, storageEntityTypeToV2(storage.EntityType_ENTITY_TYPE_UNSET)) + + assert.Equal(s.T(), storage.EntityField_FIELD_UNSET, v2EntityFieldToStorage(apiV2.ScopeField_FIELD_UNSET)) + assert.Equal(s.T(), apiV2.ScopeField_FIELD_UNSET, storageEntityFieldToV2(storage.EntityField_FIELD_UNSET)) + + assert.Equal(s.T(), storage.MatchType_EXACT, v2MatchTypeToStorage(apiV2.MatchType_EXACT)) + assert.Equal(s.T(), apiV2.MatchType_EXACT, storageMatchTypeToV2(storage.MatchType_EXACT)) +} + +func (s *ConversionTestSuite) TestConvertProtoReportSnapshotToV2() { + configID := "test-config" + reportID := "test-report" + + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: configID, + ReportId: reportID, + Name: "Test Report", + Description: "Test description", + AreaOfConcern: "User Workloads", + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + Requester: &storage.SlimUser{ + Id: "user-1", + Name: "Test User", + }, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{}, + }, + }, + Filter: &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "test", + }, + }, + } + + blobStore := s.service.blobStore.(*blobDSMocks.MockDatastore) + blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + + result, err := s.service.convertProtoReportSnapshotToV2(snapshot, set.NewFrozenStringSet()) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), result) + assert.Equal(s.T(), reportID, result.GetReportJobId()) + assert.Equal(s.T(), configID, result.GetReportConfigId()) + assert.Equal(s.T(), "Test Report", result.GetName()) + assert.Equal(s.T(), "Test description", result.GetDescription()) + assert.Equal(s.T(), "User Workloads", result.GetAreaOfConcern()) + assert.False(s.T(), result.GetIsDownloadAvailable()) +} + +func (s *ConversionTestSuite) TestReportSnapshotDownloadAvailable() { + configID := "test-config" + reportID := "test-report" + + testCases := map[string]struct { + snapshot *storage.ReportSnapshot + blobNames set.FrozenStringSet + available bool + }{ + "config-based with matching blob": { + snapshot: &storage.ReportSnapshot{ + ReportConfigurationId: configID, + ReportId: reportID, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + }, + blobNames: set.NewFrozenStringSet(common.GetReportBlobPath(configID, reportID)), + available: true, + }, + "view-based with matching blob": { + snapshot: &storage.ReportSnapshot{ + ReportId: reportID, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + }, + blobNames: set.NewFrozenStringSet(common.GetReportBlobPath("view-based-report", reportID)), + available: true, + }, + "view-based without blob": { + snapshot: &storage.ReportSnapshot{ + ReportId: reportID, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + }, + blobNames: set.NewFrozenStringSet(), + available: false, + }, + "view-based does not match config-id blob path": { + snapshot: &storage.ReportSnapshot{ + ReportConfigurationId: configID, + ReportId: reportID, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + }, + blobNames: set.NewFrozenStringSet(common.GetReportBlobPath(configID, reportID)), + available: false, + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + result, err := s.service.convertProtoReportSnapshotToV2(tc.snapshot, tc.blobNames) + assert.NoError(s.T(), err) + assert.Equal(s.T(), tc.available, result.GetIsDownloadAvailable()) + }) + } +} + +func (s *ConversionTestSuite) TestRunStateConversions() { + assert.Equal(s.T(), apiV2.ReportStatus_WAITING, storageRunStateToV2[storage.ReportStatus_WAITING]) + assert.Equal(s.T(), apiV2.ReportStatus_PREPARING, storageRunStateToV2[storage.ReportStatus_PREPARING]) + assert.Equal(s.T(), apiV2.ReportStatus_GENERATED, storageRunStateToV2[storage.ReportStatus_GENERATED]) + assert.Equal(s.T(), apiV2.ReportStatus_DELIVERED, storageRunStateToV2[storage.ReportStatus_DELIVERED]) + assert.Equal(s.T(), apiV2.ReportStatus_FAILURE, storageRunStateToV2[storage.ReportStatus_FAILURE]) +} + +func (s *ConversionTestSuite) TestResourceScopeConversions() { + v2Scope := &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + { + Value: "prod-cluster", + MatchType: apiV2.MatchType_REGEX, + }, + }, + }, + }, + }, + }, + } + + protoScope, err := s.service.convertV2ResourceScopeToProto(v2Scope) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), protoScope) + + v2ScopeBack := convertProtoResourceScopeToV2(protoScope) + assert.NotNil(s.T(), v2ScopeBack) + assert.NotNil(s.T(), v2ScopeBack.GetEntityScope()) + assert.Len(s.T(), v2ScopeBack.GetEntityScope().GetRules(), 1) +} + +func (s *ConversionTestSuite) TestEmptyResourceScope() { + result, err := s.service.convertV2ResourceScopeToProto(nil) + assert.NoError(s.T(), err) + assert.Nil(s.T(), result) + + v2Result := convertProtoResourceScopeToV2(nil) + assert.Nil(s.T(), v2Result) +} + +func (s *ConversionTestSuite) TestReportConfigurationWithoutNotifiers() { + creator := &storage.SlimUser{ + Id: "user-123", + Name: "Test User", + } + + v2Config := &apiV2.ReportConfiguration{ + Id: "config-no-notifiers", + Name: "Node Report No Notifiers", + Description: "Test without notifiers", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_ID, + Values: []*apiV2.RuleValue{ + { + Value: "cluster-1", + MatchType: apiV2.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:prod", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + Notifiers: nil, + } + + protoConfig, err := s.service.convertV2ReportConfigurationToProto(v2Config, creator, nil) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), protoConfig) + assert.Empty(s.T(), protoConfig.GetNotifiers()) +} + +func (s *ConversionTestSuite) TestProtoReportConfigurationToV2() { + protoConfig := &storage.ReportConfiguration{ + Id: "config-proto", + Name: "Proto Config", + Description: "Test proto to v2", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{ + { + Value: "cluster-1", + MatchType: storage.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "test query", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + Notifiers: []*storage.NotifierConfiguration{ + { + Ref: &storage.NotifierConfiguration_Id{ + Id: "notifier-1", + }, + NotifierConfig: &storage.NotifierConfiguration_EmailConfig{ + EmailConfig: &storage.EmailNotifierConfiguration{ + NotifierId: "notifier-1", + MailingLists: []string{"team@example.com"}, + }, + }, + }, + }, + } + + // Mock GetNotifier for the conversion + notifierDataStore := s.service.notifierDatastore.(*notifierDSMocks.MockDataStore) + notifierDataStore.EXPECT().GetNotifier(gomock.Any(), "notifier-1").Return(&storage.Notifier{ + Id: "notifier-1", + Name: "Notifier 1", + }, true, nil).Times(1) + + v2Config, err := s.service.convertProtoReportConfigurationToV2(protoConfig) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), v2Config) + assert.Equal(s.T(), "config-proto", v2Config.GetId()) + assert.Equal(s.T(), "Proto Config", v2Config.GetName()) + assert.Equal(s.T(), apiV2.ReportConfiguration_NODE_VULNERABILITY, v2Config.GetType()) + assert.Len(s.T(), v2Config.GetNotifiers(), 1) + assert.Equal(s.T(), "Notifier 1", v2Config.GetNotifiers()[0].GetNotifierName()) +} + +func (s *ConversionTestSuite) TestProtoReportConfigurationToV2_SkipsNotifierWithoutEmail() { + protoConfig := &storage.ReportConfiguration{ + Id: "config-proto", + Name: "Proto Config", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Notifiers: []*storage.NotifierConfiguration{ + { + Ref: &storage.NotifierConfiguration_Id{Id: "notifier-1"}, + }, + }, + } + + v2Config, err := s.service.convertProtoReportConfigurationToV2(protoConfig) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), v2Config) + assert.Empty(s.T(), v2Config.GetNotifiers()) +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_NotDownloadMethod() { + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_EMAIL, + }, + } + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.NoError(s.T(), err) + assert.Equal(s.T(), 0, result.Cardinality()) +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_WrongRunState() { + testCases := []struct { + name string + runState storage.ReportStatus_RunState + }{ + {"Waiting", storage.ReportStatus_WAITING}, + {"Preparing", storage.ReportStatus_PREPARING}, + {"Failure", storage.ReportStatus_FAILURE}, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: tc.runState, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + } + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.NoError(s.T(), err) + assert.Equal(s.T(), 0, result.Cardinality()) + }) + } +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_BlobSearchError() { + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + } + + blobStore := s.service.blobStore.(*blobDSMocks.MockDatastore) + blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return(nil, assert.AnError).Times(1) + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.Error(s.T(), err) + assert.Equal(s.T(), 0, result.Cardinality()) +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_NoBlobFound() { + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + } + + blobStore := s.service.blobStore.(*blobDSMocks.MockDatastore) + blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).Times(1) + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.NoError(s.T(), err) + assert.Equal(s.T(), 0, result.Cardinality()) +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_BlobFound() { + testCases := []struct { + name string + runState storage.ReportStatus_RunState + }{ + {"Generated state", storage.ReportStatus_GENERATED}, + {"Delivered state", storage.ReportStatus_DELIVERED}, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + snapshot := &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: tc.runState, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + } + + blobStore := s.service.blobStore.(*blobDSMocks.MockDatastore) + blobStore.EXPECT().Search(allAccessCtx, gomock.Any()).Return([]search.Result{ + {ID: common.GetReportBlobPath("config-1", "report-1")}, + }, nil).Times(1) + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.NoError(s.T(), err) + assert.True(s.T(), result.Contains(common.GetReportBlobPath("config-1", "report-1"))) + }) + } +} + +func (s *ConversionTestSuite) TestGetExistingBlobNames_ViewBased() { + snapshot := &storage.ReportSnapshot{ + ReportId: "report-1", + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + } + + blobStore := s.service.blobStore.(*blobDSMocks.MockDatastore) + blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{ + {ID: common.GetReportBlobPath("view-based-report", "report-1")}, + }, nil).Times(1) + + result, err := s.service.getExistingBlobNames([]*storage.ReportSnapshot{snapshot}) + assert.NoError(s.T(), err) + assert.True(s.T(), result.Contains(common.GetReportBlobPath("view-based-report", "report-1"))) +} + +func (s *ConversionTestSuite) TestReportBlobParentDir() { + testCases := map[string]struct { + snapshot *storage.ReportSnapshot + want string + }{ + "config-based on-demand": { + snapshot: &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_ON_DEMAND, + }, + }, + want: "config-1", + }, + "config-based scheduled": { + snapshot: &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_SCHEDULED, + }, + }, + want: "config-1", + }, + "view-based uses shared blob directory": { + snapshot: &storage.ReportSnapshot{ + ReportConfigurationId: "config-1", + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + }, + want: "view-based-report", + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + assert.Equal(s.T(), tc.want, reportBlobParentDir(tc.snapshot)) + }) + } +} diff --git a/central/reports/service/v2/node/service_impl.go b/central/reports/service/v2/node/service_impl.go new file mode 100644 index 0000000000000..06ced53a44669 --- /dev/null +++ b/central/reports/service/v2/node/service_impl.go @@ -0,0 +1,23 @@ +package node + +import ( + blobDS "github.com/stackrox/rox/central/blob/datastore" + notifierDS "github.com/stackrox/rox/central/notifier/datastore" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + snapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/central/reports/validation" + collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" + apiV2 "github.com/stackrox/rox/generated/api/v2" +) + +type serviceImpl struct { + apiV2.UnimplementedNodeReportServiceServer + reportConfigStore reportConfigDS.DataStore + snapshotDatastore snapshotDS.DataStore + collectionDatastore collectionDS.DataStore + notifierDatastore notifierDS.DataStore + scheduler schedulerV2.Scheduler + blobStore blobDS.Datastore + validator *validation.Validator +} From 04b7a8828c296c75a090e23bfd2e452f8c2a4320 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 03:31:34 +0000 Subject: [PATCH 214/638] Update Konflux references (#22664) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/main-pipeline.yaml | 6 +++--- .tekton/operator-bundle-pipeline.yaml | 6 +++--- .tekton/scanner-v4-pipeline.yaml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 3765e7aeac21f..ccfe1f830f048 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -459,7 +459,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d9b01530ce3c20287714e64980f154e28c0e94d17e2dbfbaf3c8bf77b1844b9e - name: kind value: task resolver: bundles @@ -485,7 +485,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:381750451fbcc86d90fa83579a48e0e6555d7cf374fe2c4af3f9262a17fc3c89 - name: kind value: task resolver: bundles @@ -511,7 +511,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index bd7b6102c8bc0..eda4361a298bf 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -481,7 +481,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d9b01530ce3c20287714e64980f154e28c0e94d17e2dbfbaf3c8bf77b1844b9e - name: kind value: task resolver: bundles @@ -507,7 +507,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:381750451fbcc86d90fa83579a48e0e6555d7cf374fe2c4af3f9262a17fc3c89 - name: kind value: task resolver: bundles @@ -533,7 +533,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 5706e7d74d4b3..520591a80ec06 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -664,7 +664,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d9b01530ce3c20287714e64980f154e28c0e94d17e2dbfbaf3c8bf77b1844b9e - name: kind value: task resolver: bundles @@ -690,7 +690,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:381750451fbcc86d90fa83579a48e0e6555d7cf374fe2c4af3f9262a17fc3c89 - name: kind value: task resolver: bundles @@ -716,7 +716,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 6db000cf96609..8975fa48da6a1 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -479,7 +479,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:afa8ba8859739e48b672f66fa2af357d27f4d96846a7c0ad84e38f21b043f695 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d9b01530ce3c20287714e64980f154e28c0e94d17e2dbfbaf3c8bf77b1844b9e - name: kind value: task resolver: bundles @@ -505,7 +505,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:69d5fca2fb94dcc7df32e36e4828e6fb24b9ba55b1837c331a83e20d8dfd479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:381750451fbcc86d90fa83579a48e0e6555d7cf374fe2c4af3f9262a17fc3c89 - name: kind value: task resolver: bundles @@ -531,7 +531,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:67a409de3c99aeaee4596da3081f26955ca6201a7f12cb6a9912659bdbcc4d01 + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e - name: kind value: task resolver: bundles From 7c0f7ed7c28a1e08aef0438fdf5dc7bbcafe74c4 Mon Sep 17 00:00:00 2001 From: Stephan Hesselmann Date: Mon, 7 Sep 2026 10:21:41 +0200 Subject: [PATCH 215/638] =?UTF-8?q?ROX-36598:=20use=20empty=20trusted=20ro?= =?UTF-8?q?ot=20for=20sigstore=20bundle=20verification=20wh=E2=80=A6=20(#2?= =?UTF-8?q?2514)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 4.6 (1M context) --- pkg/signatures/cosign_sig_verifier.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/signatures/cosign_sig_verifier.go b/pkg/signatures/cosign_sig_verifier.go index 8771f770273d6..e9c6f271a7dd5 100644 --- a/pkg/signatures/cosign_sig_verifier.go +++ b/pkg/signatures/cosign_sig_verifier.go @@ -564,9 +564,13 @@ func verifySigstoreBundle(ctx context.Context, sigstoreBundle []byte, ) ([]string, error) { if cosignOpts.TrustedMaterial == nil { var err error - cosignOpts.TrustedMaterial, err = sigstoreTrustedRoot() + if cosignOpts.IgnoreTlog { + cosignOpts.TrustedMaterial, err = root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, nil, nil) + } else { + cosignOpts.TrustedMaterial, err = sigstoreTrustedRoot() + } if err != nil { - return nil, fmt.Errorf("loading fallback trusted material for bundle verification: %w", err) + return nil, fmt.Errorf("loading trusted material for bundle verification: %w", err) } } From f1979dedd19be0ed6a4c3a5f8071a48392444ab6 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Mon, 7 Sep 2026 12:22:25 +0200 Subject: [PATCH 216/638] ROX-36683: pin Rekor/CTFE keys in cosign verifier test (#22620) --- pkg/signatures/cosign_sig_verifier_test.go | 35 ++++++++++++++++++---- pkg/signatures/testdata/ctfe_2022_pub.pem | 4 +++ pkg/signatures/testdata/rekor_pub.pem | 4 +++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 pkg/signatures/testdata/ctfe_2022_pub.pem create mode 100644 pkg/signatures/testdata/rekor_pub.pem diff --git a/pkg/signatures/cosign_sig_verifier_test.go b/pkg/signatures/cosign_sig_verifier_test.go index c84be6dcaa2b7..198cefd0cd412 100644 --- a/pkg/signatures/cosign_sig_verifier_test.go +++ b/pkg/signatures/cosign_sig_verifier_test.go @@ -492,6 +492,18 @@ func TestCosignVerifier_VerifySignature_Certificate(t *testing.T) { bundle, err := os.ReadFile("testdata/bundle.json") require.NoError(t, err) + // Pin the Rekor and CT log public keys used to sign the fixtures above instead of + // relying on cosign's default behaviour of fetching them from the live Sigstore TUF + // root (tuf-repo-cdn.sigstore.dev). That live fetch makes the test flaky/non-hermetic + // whenever the public CDN is unreachable or rate-limits CI (see ROX-36683). These are + // the same public keys embedded in github.com/sigstore/sigstore's vendored TUF + // repository (pkg/tuf/repository/targets/{rekor,ctfe_2022}.pub) and correspond exactly + // to the log IDs recorded in the fixtures (verified via the log's SHA-256 key ID). + rekorPubKey, err := os.ReadFile("testdata/rekor_pub.pem") + require.NoError(t, err) + ctfePubKey, err := os.ReadFile("testdata/ctfe_2022_pub.pem") + require.NoError(t, err) + imgWithTlog, err := generateImageWithCosignSignature(imgStringWithTlog, b64SignatureWithTlog, b64PayloadWithTlog, certPEMWithTlog, chainPEMWithTlog, bundle) require.NoError(t, err, "creating image with signature") @@ -603,10 +615,15 @@ func TestCosignVerifier_VerifySignature_Certificate(t *testing.T) { &storage.SignatureIntegration{ CosignCertificates: []*storage.CosignCertificateVerification{ { - CertificateIdentity: ".*", - CertificateOidcIssuer: ".*", + // Pin the real Fulcio intermediate/root chain (the same data cosign would + // otherwise fetch live from the Sigstore TUF root) so that certificate chain + // validation does not require network access to tuf-repo-cdn.sigstore.dev. + CertificateChainPemEnc: string(chainPEMWithTlog), + CertificateIdentity: ".*", + CertificateOidcIssuer: ".*", CertificateTransparencyLog: &storage.CertificateTransparencyLogVerification{ - Enabled: true, + Enabled: true, + PublicKeyPemEnc: string(ctfePubKey), }, }, }, @@ -614,6 +631,7 @@ func TestCosignVerifier_VerifySignature_Certificate(t *testing.T) { TransparencyLog: &storage.TransparencyLogVerification{ Enabled: true, ValidateOffline: true, + PublicKeyPemEnc: string(rekorPubKey), }, }, ) @@ -633,7 +651,8 @@ func TestCosignVerifier_VerifySignature_Certificate(t *testing.T) { CertificateIdentity: ".*", CertificateOidcIssuer: ".*", CertificateTransparencyLog: &storage.CertificateTransparencyLogVerification{ - Enabled: true, + Enabled: true, + PublicKeyPemEnc: string(ctfePubKey), }, }, }, @@ -649,13 +668,17 @@ func TestCosignVerifier_VerifySignature_Certificate(t *testing.T) { &storage.SignatureIntegration{ CosignCertificates: []*storage.CosignCertificateVerification{ { - CertificateIdentity: ".*", - CertificateOidcIssuer: ".*", + // See comment above on pinning the Fulcio chain locally instead of relying + // on a live TUF fetch. + CertificateChainPemEnc: string(chainPEMWithTlog), + CertificateIdentity: ".*", + CertificateOidcIssuer: ".*", }, }, TransparencyLog: &storage.TransparencyLogVerification{ Enabled: true, ValidateOffline: true, + PublicKeyPemEnc: string(rekorPubKey), }, }, ) diff --git a/pkg/signatures/testdata/ctfe_2022_pub.pem b/pkg/signatures/testdata/ctfe_2022_pub.pem new file mode 100644 index 0000000000000..32fa2ad101da5 --- /dev/null +++ b/pkg/signatures/testdata/ctfe_2022_pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNK +AaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw== +-----END PUBLIC KEY----- diff --git a/pkg/signatures/testdata/rekor_pub.pem b/pkg/signatures/testdata/rekor_pub.pem new file mode 100644 index 0000000000000..050ef601497b2 --- /dev/null +++ b/pkg/signatures/testdata/rekor_pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY----- From 3e6e76eff6d93335613ac85e18cacde9efee2f4c Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Mon, 7 Sep 2026 13:42:31 +0200 Subject: [PATCH 217/638] ROX-36250: implement node report service API (#22508) Co-authored-by: Claude Opus 4.6 Co-authored-by: Charmik Sheth Co-authored-by: Cursor --- central/main.go | 2 +- .../scheduler/v2/scheduler_impl_test.go | 6 +- central/reports/service/v2/download.go | 20 +- central/reports/service/v2/download_test.go | 30 + .../reports/service/v2/node/convert_test.go | 13 +- central/reports/service/v2/node/service.go | 38 + .../reports/service/v2/node/service_impl.go | 610 +++++++- .../service/v2/node/service_impl_test.go | 1388 +++++++++++++++++ central/reports/service/v2/node/singleton.go | 33 + central/reports/service/v2/service_impl.go | 117 +- .../reports/service/v2/service_impl_test.go | 188 ++- central/reports/validation/validate.go | 12 +- .../validation/validate_comprehensive_test.go | 97 ++ 13 files changed, 2505 insertions(+), 49 deletions(-) create mode 100644 central/reports/service/v2/node/service.go create mode 100644 central/reports/service/v2/node/service_impl_test.go create mode 100644 central/reports/service/v2/node/singleton.go diff --git a/central/main.go b/central/main.go index f17a88ab54cf0..3fd9cc1d8ac6d 100755 --- a/central/main.go +++ b/central/main.go @@ -1004,7 +1004,7 @@ func customRoutes() (customRoutes []routes.CustomRoute) { customRoutes = append(customRoutes, routes.CustomRoute{ Route: "/api/reports/node/jobs/download", Authorizer: user.With(permissions.View(resources.Node), permissions.View(resources.Cluster)), - ServerHandler: v2Service.NewDownloadHandler(), + ServerHandler: v2Service.NewNodeDownloadHandler(), Compression: true, }) } diff --git a/central/reports/scheduler/v2/scheduler_impl_test.go b/central/reports/scheduler/v2/scheduler_impl_test.go index 5a61a6ec102ee..0eb70524f3ee5 100644 --- a/central/reports/scheduler/v2/scheduler_impl_test.go +++ b/central/reports/scheduler/v2/scheduler_impl_test.go @@ -174,7 +174,7 @@ func TestCancelReportRequestCancelsRunningReport(t *testing.T) { defer cronScheduler.Stop() s := newSchedulerImpl(nil, nil, nil, nil, mockReportGen, nil, nil, cronScheduler) - imageQueue := s.queues[0].queue + imageQueue := s.queueByType[storage.ReportSnapshot_VULNERABILITY] started := make(chan struct{}) done := make(chan struct{}) @@ -192,6 +192,7 @@ func TestCancelReportRequestCancelsRunningReport(t *testing.T) { ReportSnapshot: &storage.ReportSnapshot{ ReportId: "running-report-id", ReportConfigurationId: "test-config-id", + Type: storage.ReportSnapshot_VULNERABILITY, ReportStatus: &storage.ReportStatus{ RunState: storage.ReportStatus_WAITING, }, @@ -306,13 +307,14 @@ func TestCancelReportRequestUpdatesWaitingReportToFailure(t *testing.T) { defer cronScheduler.Stop() s := newSchedulerImpl(nil, mockSnapshotStore, nil, nil, nil, nil, nil, cronScheduler) - imageQueue := s.queues[0].queue + imageQueue := s.queueByType[storage.ReportSnapshot_VULNERABILITY] req := &reportGen.ReportRequest{ ReportSnapshot: &storage.ReportSnapshot{ ReportId: "waiting-report-id", ReportConfigurationId: "test-config-id", Name: "test-report", + Type: storage.ReportSnapshot_VULNERABILITY, ReportStatus: &storage.ReportStatus{ RunState: storage.ReportStatus_WAITING, }, diff --git a/central/reports/service/v2/download.go b/central/reports/service/v2/download.go index 71b28d12dca0a..8d974ec6e1cfc 100644 --- a/central/reports/service/v2/download.go +++ b/central/reports/service/v2/download.go @@ -34,17 +34,27 @@ func parseJobID(r *http.Request) (id string, err error) { return } -// NewDownloadHandler is an HTTP handler for downloading reports +// NewDownloadHandler is an HTTP handler for downloading image vulnerability reports. func NewDownloadHandler() http.HandlerFunc { + return newTypedDownloadHandler(storage.ReportSnapshot_VULNERABILITY) +} + +// NewNodeDownloadHandler is an HTTP handler for downloading node vulnerability reports. +func NewNodeDownloadHandler() http.HandlerFunc { + return newTypedDownloadHandler(storage.ReportSnapshot_NODE_VULNERABILITY) +} + +func newTypedDownloadHandler(expectedType storage.ReportSnapshot_ReportType) http.HandlerFunc { snapshotStore := snapshotDataStore.Singleton() blobStore := blobDS.Singleton() - handler := &downloadHandler{snapshotStore: snapshotStore, blobStore: blobStore} + handler := &downloadHandler{snapshotStore: snapshotStore, blobStore: blobStore, expectedType: expectedType} return handler.handle } type downloadHandler struct { snapshotStore snapshotDataStore.DataStore blobStore blobDS.Datastore + expectedType storage.ReportSnapshot_ReportType } func (h *downloadHandler) handle(w http.ResponseWriter, r *http.Request) { @@ -77,6 +87,12 @@ func (h *downloadHandler) handle(w http.ResponseWriter, r *http.Request) { return } + if rep.GetType() != h.expectedType { + httputil.WriteGRPCStyleError(w, codes.InvalidArgument, + errors.Errorf("report job %q is not a %s report; use the matching download endpoint", id, h.expectedType)) + return + } + if slimUser.GetId() != rep.GetRequester().GetId() { httputil.WriteGRPCStyleError(w, codes.PermissionDenied, errors.Errorf("Report cannot be downloaded by a user %q who did not request the report.", slimUser.GetId())) diff --git a/central/reports/service/v2/download_test.go b/central/reports/service/v2/download_test.go index 3e41d17c36fb0..005e6c4710644 100644 --- a/central/reports/service/v2/download_test.go +++ b/central/reports/service/v2/download_test.go @@ -44,6 +44,7 @@ func (s *handlerTestSuite) SetupSuite() { s.handler = &downloadHandler{ snapshotStore: s.reportSnapshotDataStore, blobStore: s.blobStore, + expectedType: storage.ReportSnapshot_VULNERABILITY, } } @@ -116,6 +117,18 @@ func (s *handlerTestSuite) TestDownloadReport() { }, statusCode: http.StatusBadRequest, }, + { + desc: "Node vulnerability snapshot is rejected", + id: reportSnapshot.GetReportId(), + ctx: userContext, + mockGen: func() { + snap := reportSnapshot.CloneVT() + snap.Type = storage.ReportSnapshot_NODE_VULNERABILITY + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportSnapshot.GetReportId()). + Return(snap, true, nil).Times(1) + }, + statusCode: http.StatusBadRequest, + }, { desc: "Report is not ready yet", id: reportSnapshot.GetReportId(), @@ -201,6 +214,23 @@ func (s *handlerTestSuite) TestDownloadReport() { } } +func (s *handlerTestSuite) TestDownloadNodeReport_RejectsImageType() { + orig := s.handler.expectedType + s.handler.expectedType = storage.ReportSnapshot_NODE_VULNERABILITY + defer func() { s.handler.expectedType = orig }() + + reportSnapshot := fixtures.GetReportSnapshot() + reportSnapshot.ReportId = uuid.NewV4().String() + reportSnapshot.ReportStatus.RunState = storage.ReportStatus_GENERATED + reportSnapshot.ReportStatus.ReportNotificationMethod = storage.ReportStatus_DOWNLOAD + userContext := s.getContextForUser(reportSnapshot.GetRequester()) + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportSnapshot.GetReportId()). + Return(reportSnapshot, true, nil).Times(1) + + s.downloadAndVerify(userContext, reportSnapshot.GetReportId(), http.StatusBadRequest, nil) +} + func (s *handlerTestSuite) downloadAndVerify(ctx context.Context, id string, code int, expectData []byte) { req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("https://example.com/api/reports/jobs/download?id=%s", id), nil) req = req.WithContext(ctx) diff --git a/central/reports/service/v2/node/convert_test.go b/central/reports/service/v2/node/convert_test.go index 6457e95310323..e5401b88c7875 100644 --- a/central/reports/service/v2/node/convert_test.go +++ b/central/reports/service/v2/node/convert_test.go @@ -43,13 +43,12 @@ func (s *ConversionTestSuite) SetupTest() { validator := validation.New(reportConfigDataStore, reportSnapshotDataStore, collectionDataStore, notifierDataStore) s.service = &serviceImpl{ - reportConfigStore: reportConfigDataStore, - snapshotDatastore: reportSnapshotDataStore, - collectionDatastore: collectionDataStore, - notifierDatastore: notifierDataStore, - scheduler: scheduler, - blobStore: blobStore, - validator: validator, + reportConfigStore: reportConfigDataStore, + snapshotDatastore: reportSnapshotDataStore, + notifierDatastore: notifierDataStore, + scheduler: scheduler, + blobStore: blobStore, + validator: validator, } } diff --git a/central/reports/service/v2/node/service.go b/central/reports/service/v2/node/service.go new file mode 100644 index 0000000000000..ddffe2c64c06c --- /dev/null +++ b/central/reports/service/v2/node/service.go @@ -0,0 +1,38 @@ +package node + +import ( + "context" + + blobDS "github.com/stackrox/rox/central/blob/datastore" + notifierDS "github.com/stackrox/rox/central/notifier/datastore" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + snapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/central/reports/validation" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/grpc" + "github.com/stackrox/rox/pkg/postgres" +) + +// Service provides the interface to the gRPC service for node vulnerability reports. +type Service interface { + grpc.APIService + + AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) + apiV2.NodeReportServiceServer +} + +// New returns a new instance of the node report service. +func New(reportConfigStore reportConfigDS.DataStore, snapshotDatastore snapshotDS.DataStore, + notifierDatastore notifierDS.DataStore, scheduler schedulerV2.Scheduler, blobStore blobDS.Datastore, + validator *validation.Validator, db postgres.DB) Service { + return &serviceImpl{ + reportConfigStore: reportConfigStore, + snapshotDatastore: snapshotDatastore, + notifierDatastore: notifierDatastore, + scheduler: scheduler, + blobStore: blobStore, + validator: validator, + db: db, + } +} diff --git a/central/reports/service/v2/node/service_impl.go b/central/reports/service/v2/node/service_impl.go index 06ced53a44669..57e12d15e4b77 100644 --- a/central/reports/service/v2/node/service_impl.go +++ b/central/reports/service/v2/node/service_impl.go @@ -1,23 +1,617 @@ package node import ( + "context" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/pkg/errors" blobDS "github.com/stackrox/rox/central/blob/datastore" notifierDS "github.com/stackrox/rox/central/notifier/datastore" + "github.com/stackrox/rox/central/reports/common" reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + reportsv2 "github.com/stackrox/rox/central/reports/service/v2" snapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" "github.com/stackrox/rox/central/reports/validation" - collectionDS "github.com/stackrox/rox/central/resourcecollection/datastore" apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/errox" + "github.com/stackrox/rox/pkg/grpc/authn" + "github.com/stackrox/rox/pkg/grpc/authz" + "github.com/stackrox/rox/pkg/grpc/authz/perrpc" + "github.com/stackrox/rox/pkg/grpc/authz/user" + "github.com/stackrox/rox/pkg/postgres" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/search/paginated" + "github.com/stackrox/rox/pkg/stringutils" + "google.golang.org/grpc" +) + +const ( + maxPaginationLimit = 1000 +) + +var ( + workflowSAC = sac.ForResource(resources.WorkflowAdministration) + + authorizer = perrpc.FromMap(map[authz.Authorizer][]string{ + user.With(permissions.View(resources.WorkflowAdministration), permissions.View(resources.Node), permissions.View(resources.Cluster)): { + apiV2.NodeReportService_ListNodeReportConfigurations_FullMethodName, + apiV2.NodeReportService_GetNodeReportConfiguration_FullMethodName, + apiV2.NodeReportService_CountNodeReportConfigurations_FullMethodName, + apiV2.NodeReportService_GetNodeReportHistory_FullMethodName, + apiV2.NodeReportService_GetMyNodeReportHistory_FullMethodName, + }, + user.With(permissions.Modify(resources.WorkflowAdministration), permissions.View(resources.Integration), permissions.View(resources.Node), permissions.View(resources.Cluster)): { + apiV2.NodeReportService_PostNodeReportConfiguration_FullMethodName, + apiV2.NodeReportService_UpdateNodeReportConfiguration_FullMethodName, + }, + user.With(permissions.Modify(resources.WorkflowAdministration), permissions.View(resources.Node), permissions.View(resources.Cluster)): { + apiV2.NodeReportService_DeleteNodeReportConfiguration_FullMethodName, + apiV2.NodeReportService_RunNodeReport_FullMethodName, + }, + // Cancel/Delete job match the image service: View is enough for the requester's own job. + // Modify(WorkflowAdministration) is checked in-handler when cancelling another user's job. + // Design table listed Modify(WorkflowAdministration) on those RPCs; image-parity won in review. + // GetNodeReportStatus uses the same View(Node)+View(Cluster) set so view-based users can poll jobs. + user.With(permissions.View(resources.Node), permissions.View(resources.Cluster)): { + apiV2.NodeReportService_GetNodeReportStatus_FullMethodName, + apiV2.NodeReportService_GetViewBasedNodeReportHistory_FullMethodName, + apiV2.NodeReportService_GetViewBasedMyNodeReportHistory_FullMethodName, + apiV2.NodeReportService_PostViewBasedNodeReport_FullMethodName, + apiV2.NodeReportService_DeleteNodeReport_FullMethodName, + apiV2.NodeReportService_CancelNodeReport_FullMethodName, + }, + }) ) type serviceImpl struct { apiV2.UnimplementedNodeReportServiceServer - reportConfigStore reportConfigDS.DataStore - snapshotDatastore snapshotDS.DataStore - collectionDatastore collectionDS.DataStore - notifierDatastore notifierDS.DataStore - scheduler schedulerV2.Scheduler - blobStore blobDS.Datastore - validator *validation.Validator + reportConfigStore reportConfigDS.DataStore + snapshotDatastore snapshotDS.DataStore + notifierDatastore notifierDS.DataStore + scheduler schedulerV2.Scheduler + blobStore blobDS.Datastore + validator *validation.Validator + db postgres.DB +} + +func (s *serviceImpl) RegisterServiceServer(grpcServer *grpc.Server) { + apiV2.RegisterNodeReportServiceServer(grpcServer, s) +} + +func (s *serviceImpl) RegisterServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return apiV2.RegisterNodeReportServiceHandler(ctx, mux, conn) +} + +func (s *serviceImpl) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) { + return ctx, authorizer.Authorized(ctx, fullMethodName) +} + +func (s *serviceImpl) PostNodeReportConfiguration(ctx context.Context, request *apiV2.ReportConfiguration) (*apiV2.ReportConfiguration, error) { + creatorID, err := identityFromContext(ctx) + if err != nil { + return nil, err + } + + if request.GetType() != apiV2.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedBy("report type must be NODE_VULNERABILITY") + } + + if err := s.validator.ValidateReportConfiguration(request); err != nil { + return nil, errors.Wrap(err, "validating report configuration") + } + + creator := &storage.SlimUser{ + Id: creatorID.UID(), + Name: stringutils.FirstNonEmpty(creatorID.FullName(), creatorID.FriendlyName()), + } + + protoReportConfig, err := s.convertV2ReportConfigurationToProto(request, creator, common.ExtractAccessScopeRules(creatorID)) + if err != nil { + return nil, errors.Wrap(err, "converting report configuration") + } + + id, err := s.reportConfigStore.AddReportConfiguration(ctx, protoReportConfig) + if err != nil { + return nil, err + } + + createdReportConfig, exists, err := s.reportConfigStore.GetReportConfiguration(ctx, id) + if err != nil { + return nil, err + } + if !exists { + return nil, errox.NotFound.CausedByf("report configuration with id '%s' was created but could not be read back", id) + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportsv2.NotifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, id) + } else if err := s.scheduler.UpsertReportSchedule(createdReportConfig); err != nil { + return nil, err + } + + resp, err := s.convertProtoReportConfigurationToV2(createdReportConfig) + if err != nil { + return nil, errors.Wrap(err, "report config created, but encountered error generating the response") + } + return resp, nil +} + +func (s *serviceImpl) UpdateNodeReportConfiguration(ctx context.Context, request *apiV2.ReportConfiguration) (*apiV2.Empty, error) { + if request.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("report configuration id is required") + } + + if request.GetType() != apiV2.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedBy("report type must be NODE_VULNERABILITY") + } + + if err := s.validator.ValidateReportConfiguration(request); err != nil { + return nil, errors.Wrap(err, "validating report configuration") + } + + currentConfig, exists, err := s.reportConfigStore.GetReportConfiguration(ctx, request.GetId()) + if err != nil { + return nil, err + } + if !exists { + return nil, errox.NotFound.CausedByf("report configuration with id '%s' does not exist", request.GetId()) + } + + if currentConfig.GetType() != storage.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report configuration '%s' is not a node vulnerability report", request.GetId()) + } + + query := search.NewQueryBuilder().AddExactMatches(search.ReportConfigID, request.GetId()).AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()).ProtoQuery() + reportSnapshots, err := s.snapshotDatastore.SearchReportSnapshots(ctx, query) + if err != nil { + return nil, err + } + slimUser, err := userFromContext(ctx) + if err != nil { + return nil, err + } + for _, reportSnapshot := range reportSnapshots { + if slimUser.GetId() == reportSnapshot.GetRequester().GetId() { + return nil, errox.InvalidArgs.CausedBy("user has a report job running for this configuration") + } + } + + var accessScopeRules []*storage.SimpleAccessScope_Rules + if filters := currentConfig.GetNodeVulnReportFilters(); filters != nil { + accessScopeRules = filters.GetAccessScopeRules() + } + updatedConfig, err := s.convertV2ReportConfigurationToProto(request, currentConfig.GetCreator(), accessScopeRules) + if err != nil { + return nil, errors.Wrap(err, "converting report configuration") + } + + err = s.reportConfigStore.UpdateReportConfiguration(ctx, updatedConfig) + if err != nil { + return nil, err + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportsv2.NotifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, updatedConfig.GetId()) + } else if err := s.scheduler.UpsertReportSchedule(updatedConfig); err != nil { + return nil, err + } + return &apiV2.Empty{}, nil +} + +func (s *serviceImpl) ListNodeReportConfigurations(ctx context.Context, query *apiV2.RawQuery) (*apiV2.ListReportConfigurationsResponse, error) { + parsedQuery, err := search.ParseQuery(query.GetQuery(), search.MatchAllIfEmpty()) + if err != nil { + return nil, errox.InvalidArgs.CausedBy(err.Error()) + } + + // Filter for NODE_VULNERABILITY type + parsedQuery = search.ConjunctionQuery( + parsedQuery, + search.NewQueryBuilder().AddExactMatches(search.ReportType, storage.ReportConfiguration_NODE_VULNERABILITY.String()).ProtoQuery(), + ) + + paginated.FillPaginationV2(parsedQuery, query.GetPagination(), maxPaginationLimit) + + reportConfigs, err := s.reportConfigStore.GetReportConfigurations(ctx, parsedQuery) + if err != nil { + return nil, errors.Wrap(err, "failed to retrieve report configurations") + } + v2Configs := make([]*apiV2.ReportConfiguration, 0, len(reportConfigs)) + + for _, config := range reportConfigs { + converted, err := s.convertProtoReportConfigurationToV2(config) + if err != nil { + return nil, errors.Wrapf(err, "error converting storage report configuration with id %s to response", config.GetId()) + } + v2Configs = append(v2Configs, converted) + } + return &apiV2.ListReportConfigurationsResponse{ReportConfigs: v2Configs}, nil +} + +func (s *serviceImpl) GetNodeReportConfiguration(ctx context.Context, req *apiV2.ResourceByID) (*apiV2.ReportConfiguration, error) { + if req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("report configuration id is required") + } + config, exists, err := s.reportConfigStore.GetReportConfiguration(ctx, req.GetId()) + if err != nil { + return nil, err + } + if !exists { + return nil, errox.NotFound.CausedByf("report configuration with id '%s' does not exist", req.GetId()) + } + + if config.GetType() != storage.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report configuration '%s' is not a node vulnerability report", req.GetId()) + } + + if !common.HasValidResourceScope(config.GetResourceScope()) { + return nil, errox.InvalidArgs.CausedByf("report configuration '%s' has an empty resource scope (no entity scope)", req.GetId()) + } + + converted, err := s.convertProtoReportConfigurationToV2(config) + if err != nil { + return nil, errors.Wrapf(err, "error converting storage report configuration with id %s to response", config.GetId()) + } + return converted, nil +} + +func (s *serviceImpl) CountNodeReportConfigurations(ctx context.Context, request *apiV2.RawQuery) (*apiV2.CountReportConfigurationsResponse, error) { + parsedQuery, err := search.ParseQuery(request.GetQuery(), search.MatchAllIfEmpty()) + if err != nil { + return nil, errox.InvalidArgs.CausedBy(err.Error()) + } + + // Filter for NODE_VULNERABILITY type + parsedQuery = search.ConjunctionQuery( + parsedQuery, + search.NewQueryBuilder().AddExactMatches(search.ReportType, storage.ReportConfiguration_NODE_VULNERABILITY.String()).ProtoQuery(), + ) + + numReportConfigs, err := s.reportConfigStore.Count(ctx, parsedQuery) + if err != nil { + return nil, err + } + return &apiV2.CountReportConfigurationsResponse{Count: int32(numReportConfigs)}, nil +} + +func (s *serviceImpl) DeleteNodeReportConfiguration(ctx context.Context, id *apiV2.ResourceByID) (*apiV2.Empty, error) { + if id.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("report configuration id is required for deletion") + } + config, found, err := s.reportConfigStore.GetReportConfiguration(ctx, id.GetId()) + if err != nil { + return nil, errors.Wrap(err, "error finding report config") + } + if !found { + return nil, errox.NotFound.CausedByf("report config ID '%s' not found", id.GetId()) + } + + if config.GetType() != storage.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report configuration '%s' is not a node vulnerability report", id.GetId()) + } + + query := search.NewQueryBuilder().AddExactMatches(search.ReportConfigID, id.GetId()).AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()).ProtoQuery() + reportSnapshots, err := s.snapshotDatastore.SearchReportSnapshots(ctx, query) + if err != nil { + return nil, errors.Wrap(err, "failed to search for active report snapshots") + } + if len(reportSnapshots) > 0 { + return nil, errox.InvalidArgs.CausedByf("report config ID '%s' has job in preparing or waiting state", id.GetId()) + } + + if err := s.reportConfigStore.RemoveReportConfiguration(ctx, id.GetId()); err != nil { + return nil, err + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportsv2.NotifyWithRetry(ctx, s.db, pgNotify.ReportConfigChanged, id.GetId()) + } else { + s.scheduler.RemoveReportSchedule(id.GetId()) + } + return &apiV2.Empty{}, nil } + +func (s *serviceImpl) RunNodeReport(ctx context.Context, req *apiV2.RunReportRequest) (*apiV2.RunReportResponse, error) { + if req.GetReportConfigId() == "" { + return nil, errox.InvalidArgs.CausedBy("report configuration id is required") + } + + config, exists, err := s.reportConfigStore.GetReportConfiguration(ctx, req.GetReportConfigId()) + if err != nil { + return nil, err + } + if !exists { + return nil, errox.NotFound.CausedByf("report configuration with id '%s' does not exist", req.GetReportConfigId()) + } + + if config.GetType() != storage.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report configuration '%s' is not a node vulnerability report", req.GetReportConfigId()) + } + + requesterID, err := identityFromContext(ctx) + if err != nil { + return nil, err + } + + reportRequest, err := s.validator.ValidateAndGenerateReportRequest( + req.GetReportConfigId(), + storage.ReportStatus_NotificationMethod(req.GetReportNotificationMethod()), + storage.ReportStatus_ON_DEMAND, + requesterID, + ) + if err != nil { + return nil, errors.Wrap(err, "error validating report request") + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportID, err := reportsv2.PersistSnapshotAndNotify(ctx, s.validator, s.db, reportRequest, pgNotify.ReportRequestSubmitted) + if err != nil { + return nil, err + } + return &apiV2.RunReportResponse{ReportConfigId: req.GetReportConfigId(), ReportId: reportID}, nil + } + + reportID, err := s.scheduler.SubmitReportRequest(ctx, reportRequest, false) + if err != nil { + return nil, errox.ServerError.CausedByf("scheduler error: %s", err) + } + + return &apiV2.RunReportResponse{ReportConfigId: req.GetReportConfigId(), ReportId: reportID}, nil +} + +// getReportHistory is a helper that fetches report snapshots with user filtering +func (s *serviceImpl) getReportHistory(ctx context.Context, queryBuilder *search.QueryBuilder, pagination *apiV2.Pagination, query string, userID string) (*apiV2.ReportHistoryResponse, error) { + parsedQuery, err := search.ParseQuery(query, search.MatchAllIfEmpty()) + if err != nil { + return nil, errox.InvalidArgs.CausedBy(err.Error()) + } + + baseQuery := queryBuilder.ProtoQuery() + if userID != "" { + if err := reportsv2.VerifyNoUserSearchLabels(parsedQuery); err != nil { + return nil, errox.InvalidArgs.CausedBy(err.Error()) + } + userIDQuery := search.NewQueryBuilder().AddExactMatches(search.UserID, userID).ProtoQuery() + baseQuery = search.ConjunctionQuery(baseQuery, userIDQuery) + } + + conjunctionQuery := search.ConjunctionQuery(baseQuery, parsedQuery) + paginated.FillPaginationV2(conjunctionQuery, pagination, maxPaginationLimit) + + results, err := s.snapshotDatastore.SearchReportSnapshots(ctx, conjunctionQuery) + if err != nil { + return nil, err + } + + blobNames, err := s.getExistingBlobNames(results) + if err != nil { + return nil, errors.Wrap(err, "failed to check blob availability") + } + + v2Snapshots := make([]*apiV2.ReportSnapshot, 0, len(results)) + for _, snapshot := range results { + converted, err := s.convertProtoReportSnapshotToV2(snapshot, blobNames) + if err != nil { + return nil, errors.Wrapf(err, "error converting snapshot with id %s", snapshot.GetReportId()) + } + v2Snapshots = append(v2Snapshots, converted) + } + + return &apiV2.ReportHistoryResponse{ReportSnapshots: v2Snapshots}, nil +} + +func (s *serviceImpl) GetNodeReportHistory(ctx context.Context, req *apiV2.GetReportHistoryRequest) (*apiV2.ReportHistoryResponse, error) { + if req == nil || req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("empty request or id") + } + + queryBuilder := search.NewQueryBuilder(). + AddExactMatches(search.ReportConfigID, req.GetId()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_NODE_VULNERABILITY.String()) + return s.getReportHistory(ctx, queryBuilder, req.GetReportParamQuery().GetPagination(), req.GetReportParamQuery().GetQuery(), "") +} + +func (s *serviceImpl) GetMyNodeReportHistory(ctx context.Context, req *apiV2.GetReportHistoryRequest) (*apiV2.ReportHistoryResponse, error) { + if req == nil || req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("empty request or id") + } + + slimUser, err := userFromContext(ctx) + if err != nil { + return nil, err + } + + queryBuilder := search.NewQueryBuilder(). + AddExactMatches(search.ReportConfigID, req.GetId()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_NODE_VULNERABILITY.String()) + return s.getReportHistory(ctx, queryBuilder, req.GetReportParamQuery().GetPagination(), req.GetReportParamQuery().GetQuery(), slimUser.GetId()) +} + +func (s *serviceImpl) GetNodeReportStatus(ctx context.Context, req *apiV2.ResourceByID) (*apiV2.ReportStatusResponse, error) { + if req == nil || req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("empty request or id") + } + rep, found, err := s.snapshotDatastore.Get(ctx, req.GetId()) + if err != nil { + return nil, err + } + if !found { + return nil, errox.NotFound.CausedByf("report snapshot not found for job id %s", req.GetId()) + } + if rep.GetType() != storage.ReportSnapshot_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report snapshot '%s' is not a node vulnerability report", req.GetId()) + } + status := convertPrototoV2Reportstatus(rep.GetReportStatus()) + return &apiV2.ReportStatusResponse{Status: status}, nil +} + +func (s *serviceImpl) CancelNodeReport(ctx context.Context, req *apiV2.ResourceByID) (*apiV2.Empty, error) { + if req == nil || req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("empty request or id") + } + snapshot, found, err := s.snapshotDatastore.Get(ctx, req.GetId()) + if err != nil { + return nil, err + } + if !found { + return nil, errox.NotFound.CausedByf("report snapshot not found for id %s", req.GetId()) + } + + if snapshot.GetType() != storage.ReportSnapshot_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report snapshot '%s' is not a node vulnerability report", req.GetId()) + } + + slimUser, err := userFromContext(ctx) + if err != nil { + return nil, err + } + if slimUser.GetId() != snapshot.GetRequester().GetId() { + if err := sac.VerifyAuthzOK(workflowSAC.WriteAllowed(ctx)); err != nil { + return nil, errors.Wrap(err, "user can only cancel a job created by the user unless they have Modify(WorkflowAdministration) permission") + } + } + + reportStatus := snapshot.GetReportStatus() + if reportStatus.GetRunState() != storage.ReportStatus_WAITING && reportStatus.GetRunState() != storage.ReportStatus_PREPARING { + return nil, errox.InvalidArgs.CausedBy("cannot cancel a job that is not in WAITING or PREPARING state") + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportsv2.NotifyWithRetry(ctx, s.db, pgNotify.ReportRequestCancelled, req.GetId()) + return &apiV2.Empty{}, nil + } + + cancelled, err := s.scheduler.CancelReportRequest(ctx, req.GetId()) + if err != nil { + return nil, err + } + if !cancelled { + return nil, errors.Errorf("failed to cancel report job %s", req.GetId()) + } + + return &apiV2.Empty{}, nil +} + +func (s *serviceImpl) DeleteNodeReport(ctx context.Context, req *apiV2.DeleteReportRequest) (*apiV2.Empty, error) { + if req == nil || req.GetId() == "" { + return nil, errox.InvalidArgs.CausedBy("empty request or id") + } + snapshot, found, err := s.snapshotDatastore.Get(ctx, req.GetId()) + if err != nil { + return nil, err + } + if !found { + return nil, errox.NotFound.CausedByf("report snapshot not found for id %s", req.GetId()) + } + + if snapshot.GetType() != storage.ReportSnapshot_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedByf("report snapshot '%s' is not a node vulnerability report", req.GetId()) + } + + slimUser, err := userFromContext(ctx) + if err != nil { + return nil, err + } + if slimUser.GetId() != snapshot.GetRequester().GetId() { + return nil, errox.NotAuthorized.CausedBy("report cannot be deleted by a user who did not request the report") + } + + status := snapshot.GetReportStatus() + if status.GetReportNotificationMethod() != storage.ReportStatus_DOWNLOAD { + return nil, errox.InvalidArgs.CausedByf("report job id %q did not generate a downloadable report and hence no report to delete", req.GetId()) + } + switch status.GetRunState() { + case storage.ReportStatus_FAILURE: + return nil, errox.InvalidArgs.CausedByf("report job %q has failed and no downloadable report to delete", req.GetId()) + case storage.ReportStatus_PREPARING, storage.ReportStatus_WAITING: + return nil, errox.InvalidArgs.CausedByf("report job %q is still running. Please cancel it or wait for its completion", req.GetId()) + } + + blobPath := common.GetReportBlobPath(reportBlobParentDir(snapshot), snapshot.GetReportId()) + blobCtx := sac.WithGlobalAccessScopeChecker(ctx, + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_WRITE_ACCESS), + sac.ResourceScopeKeys(resources.Administration), + ), + ) + if err := s.blobStore.Delete(blobCtx, blobPath); err != nil { + return nil, errox.InvariantViolation.CausedByf("failed to delete downloadable report %q", req.GetId()) + } + + return &apiV2.Empty{}, nil +} + +func (s *serviceImpl) PostViewBasedNodeReport(ctx context.Context, req *apiV2.ReportRequestViewBased) (*apiV2.RunReportResponseViewBased, error) { + if req.GetType() != apiV2.ReportRequestViewBased_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.CausedBy("report type must be NODE_VULNERABILITY") + } + + requesterID, err := identityFromContext(ctx) + if err != nil { + return nil, err + } + + reportRequest, err := s.validator.ValidateAndGenerateViewBasedReportRequest(req, requesterID) + if err != nil { + return nil, errors.Wrap(err, "error validating view-based report request") + } + + if env.CentralWorkerEnabled.BooleanSetting() { + reportID, err := reportsv2.PersistSnapshotAndNotify(ctx, s.validator, s.db, reportRequest, pgNotify.ReportRequestSubmitted) + if err != nil { + return nil, err + } + return &apiV2.RunReportResponseViewBased{ReportID: reportID, RequestName: reportRequest.ReportSnapshot.GetName()}, nil + } + + reportID, err := s.scheduler.SubmitReportRequest(ctx, reportRequest, false) + if err != nil { + return nil, errox.ServerError.CausedByf("scheduler error: %s", err) + } + + return &apiV2.RunReportResponseViewBased{ReportID: reportID, RequestName: reportRequest.ReportSnapshot.GetName()}, nil +} + +func (s *serviceImpl) GetViewBasedNodeReportHistory(ctx context.Context, req *apiV2.GetViewBasedReportHistoryRequest) (*apiV2.ReportHistoryResponse, error) { + queryBuilder := search.NewQueryBuilder(). + AddExactMatches(search.ReportType, storage.ReportSnapshot_NODE_VULNERABILITY.String()). + AddExactMatches(search.ReportRequestType, storage.ReportStatus_VIEW_BASED.String()) + return s.getReportHistory(reportsv2.SnapshotReadContext(ctx), queryBuilder, req.GetReportParamQuery().GetPagination(), req.GetReportParamQuery().GetQuery(), "") +} + +func (s *serviceImpl) GetViewBasedMyNodeReportHistory(ctx context.Context, req *apiV2.GetViewBasedReportHistoryRequest) (*apiV2.ReportHistoryResponse, error) { + slimUser, err := userFromContext(ctx) + if err != nil { + return nil, err + } + + queryBuilder := search.NewQueryBuilder(). + AddExactMatches(search.ReportType, storage.ReportSnapshot_NODE_VULNERABILITY.String()). + AddExactMatches(search.ReportRequestType, storage.ReportStatus_VIEW_BASED.String()) + return s.getReportHistory(reportsv2.SnapshotReadContext(ctx), queryBuilder, req.GetReportParamQuery().GetPagination(), req.GetReportParamQuery().GetQuery(), slimUser.GetId()) +} + +func identityFromContext(ctx context.Context) (authn.Identity, error) { + id := authn.IdentityFromContextOrNil(ctx) + if id == nil { + return nil, errox.NoCredentials.New("could not determine user identity from provided context") + } + return id, nil +} + +func userFromContext(ctx context.Context) (*storage.SlimUser, error) { + slimUser := authn.UserFromContext(ctx) + if slimUser == nil { + return nil, errox.NoCredentials.New("could not determine user identity from provided context") + } + return slimUser, nil +} + +var _ Service = (*serviceImpl)(nil) diff --git a/central/reports/service/v2/node/service_impl_test.go b/central/reports/service/v2/node/service_impl_test.go new file mode 100644 index 0000000000000..f641f171e586e --- /dev/null +++ b/central/reports/service/v2/node/service_impl_test.go @@ -0,0 +1,1388 @@ +package node + +import ( + "context" + "testing" + + "github.com/jackc/pgx/v5/pgconn" + blobDSMocks "github.com/stackrox/rox/central/blob/datastore/mocks" + notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" + "github.com/stackrox/rox/central/reports/common" + reportConfigDSMocks "github.com/stackrox/rox/central/reports/config/datastore/mocks" + schedulerMocks "github.com/stackrox/rox/central/reports/scheduler/v2/mocks" + reportSnapshotDSMocks "github.com/stackrox/rox/central/reports/snapshot/datastore/mocks" + "github.com/stackrox/rox/central/reports/validation" + collectionDSMocks "github.com/stackrox/rox/central/resourcecollection/datastore/mocks" + v1 "github.com/stackrox/rox/generated/api/v1" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/permissions" + permissionsMocks "github.com/stackrox/rox/pkg/auth/permissions/mocks" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/errox" + "github.com/stackrox/rox/pkg/grpc/authn" + mockIdentity "github.com/stackrox/rox/pkg/grpc/authn/mocks" + "github.com/stackrox/rox/pkg/grpc/testutils" + postgresMocks "github.com/stackrox/rox/pkg/postgres/mocks" + pgNotify "github.com/stackrox/rox/pkg/postgres/notify" + "github.com/stackrox/rox/pkg/protoassert" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" + "github.com/stackrox/rox/pkg/search" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" +) + +func TestNodeReportService(t *testing.T) { + suite.Run(t, new(NodeReportServiceTestSuite)) +} + +type NodeReportServiceTestSuite struct { + suite.Suite + mockCtrl *gomock.Controller + + ctx context.Context + reportConfigDataStore *reportConfigDSMocks.MockDataStore + reportSnapshotDataStore *reportSnapshotDSMocks.MockDataStore + collectionDataStore *collectionDSMocks.MockDataStore + notifierDataStore *notifierDSMocks.MockDataStore + blobStore *blobDSMocks.MockDatastore + scheduler *schedulerMocks.MockScheduler + db *postgresMocks.MockDB + service Service +} + +func (s *NodeReportServiceTestSuite) SetupTest() { + s.mockCtrl = gomock.NewController(s.T()) + s.ctx = sac.WithAllAccess(context.Background()) + s.reportConfigDataStore = reportConfigDSMocks.NewMockDataStore(s.mockCtrl) + s.reportSnapshotDataStore = reportSnapshotDSMocks.NewMockDataStore(s.mockCtrl) + s.collectionDataStore = collectionDSMocks.NewMockDataStore(s.mockCtrl) + s.notifierDataStore = notifierDSMocks.NewMockDataStore(s.mockCtrl) + s.blobStore = blobDSMocks.NewMockDatastore(s.mockCtrl) + s.scheduler = schedulerMocks.NewMockScheduler(s.mockCtrl) + s.db = postgresMocks.NewMockDB(s.mockCtrl) + validator := validation.New(s.reportConfigDataStore, s.reportSnapshotDataStore, s.collectionDataStore, s.notifierDataStore) + s.service = New(s.reportConfigDataStore, s.reportSnapshotDataStore, s.notifierDataStore, s.scheduler, s.blobStore, validator, s.db) +} + +func (s *NodeReportServiceTestSuite) TearDownSuite() { + s.mockCtrl.Finish() +} + +func (s *NodeReportServiceTestSuite) TearDownTest() { + s.mockCtrl.Finish() +} + +func (s *NodeReportServiceTestSuite) getContextForUser(user *storage.SlimUser) context.Context { + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + mockID.EXPECT().UID().Return(user.GetId()).AnyTimes() + mockID.EXPECT().FullName().Return(user.GetName()).AnyTimes() + mockID.EXPECT().FriendlyName().Return(user.GetName()).AnyTimes() + mockRole := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + mockRole.EXPECT().GetAccessScope().Return(&storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{"cluster-1"}, + }, + }).AnyTimes() + mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).AnyTimes() + return authn.ContextWithIdentity(s.ctx, mockID, s.T()) +} + +func (s *NodeReportServiceTestSuite) getValidNodeReportConfig() *apiV2.ReportConfiguration { + return &apiV2.ReportConfiguration{ + Id: uuid.NewV4().String(), + Name: "test node report", + Description: "test description", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_ID, + Values: []*apiV2.RuleValue{ + { + Value: "cluster-1", + MatchType: apiV2.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + Notifiers: []*apiV2.NotifierConfiguration{ + { + NotifierConfig: &apiV2.NotifierConfiguration_EmailConfig{ + EmailConfig: &apiV2.EmailNotifierConfiguration{ + NotifierId: "email-notifier-id", + MailingLists: []string{"test@example.com"}, + }, + }, + }, + }, + } +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + + accessScope := &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{"cluster-1"}, + }, + } + + requestConfig := s.getValidNodeReportConfig() + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + ctx := authn.ContextWithIdentity(s.ctx, mockID, s.T()) + + mockID.EXPECT().UID().Return(creator.GetId()).AnyTimes() + mockID.EXPECT().FullName().Return(creator.GetName()).AnyTimes() + mockID.EXPECT().FriendlyName().Return(creator.GetName()).AnyTimes() + + mockRole := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + mockRole.EXPECT().GetAccessScope().Return(accessScope).Times(1) + mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).Times(1) + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + + s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, cfg *storage.ReportConfiguration) (string, error) { + s.Equal(storage.ReportConfiguration_NODE_VULNERABILITY, cfg.GetType()) + protoassert.Equal(s.T(), creator, cfg.GetCreator()) + s.NotNil(cfg.GetNodeVulnReportFilters()) + return cfg.GetId(), nil + }).Times(1) + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), requestConfig.GetId()). + DoAndReturn(func(_ context.Context, _ string) (*storage.ReportConfiguration, bool, error) { + return &storage.ReportConfiguration{ + Id: requestConfig.GetId(), + Name: requestConfig.GetName(), + Description: requestConfig.GetDescription(), + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + }, true, nil + }).Times(1) + + s.scheduler.EXPECT().UpsertReportSchedule(gomock.Any()).Return(nil).Times(1) + + result, err := s.service.PostNodeReportConfiguration(ctx, requestConfig) + s.NoError(err) + s.Equal(requestConfig.GetId(), result.GetId()) + s.Equal(apiV2.ReportConfiguration_NODE_VULNERABILITY, result.GetType()) +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_GetAfterCreateMissing() { + creator := &storage.SlimUser{Id: "uid", Name: "name"} + requestConfig := s.getValidNodeReportConfig() + ctx := s.getContextForUser(creator) + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). + Return(requestConfig.GetId(), nil).Times(1) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), requestConfig.GetId()). + Return(nil, false, nil).Times(1) + + result, err := s.service.PostNodeReportConfiguration(ctx, requestConfig) + s.Error(err) + s.Nil(result) +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_ValidationError() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + invalidConfig := s.getValidNodeReportConfig() + invalidConfig.Filter = &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: nil, + }, + } + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + + _, err := s.service.PostNodeReportConfiguration(ctx, invalidConfig) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportConfiguration() { + protoReportConfig := &storage.ReportConfiguration{ + Id: "test-id", + Name: "test node report", + Description: "test description", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{ + { + Value: "cluster-1", + MatchType: storage.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + } + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(protoReportConfig, true, nil).Times(1) + + result, err := s.service.GetNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.NoError(err) + s.Equal("test-id", result.GetId()) + s.Equal(apiV2.ReportConfiguration_NODE_VULNERABILITY, result.GetType()) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportConfiguration_NotFound() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "nonexistent"). + Return(nil, false, nil).Times(1) + + _, err := s.service.GetNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "nonexistent"}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReportConfiguration() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(&storage.ReportConfiguration{ + Id: "test-id", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportConfigDataStore.EXPECT().RemoveReportConfiguration(gomock.Any(), "test-id"). + Return(nil).Times(1) + s.scheduler.EXPECT().RemoveReportSchedule("test-id").Times(1) + + _, err := s.service.DeleteNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.NoError(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReportConfiguration_HasRunningJob() { + runningSnapshot := &storage.ReportSnapshot{ + ReportId: "test-report-id", + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_PREPARING, + }, + } + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(&storage.ReportConfiguration{ + Id: "test-id", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{runningSnapshot}, nil).Times(1) + + resp, err := s.service.DeleteNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.Error(err) + s.Nil(resp) +} + +func (s *NodeReportServiceTestSuite) TestRunNodeReport() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + configID := "test-config-id" + protoReportConfig := &storage.ReportConfiguration{ + Id: configID, + Name: "test node report", + Description: "test description", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{ + { + Value: "cluster-1", + MatchType: storage.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + Notifiers: []*storage.NotifierConfiguration{ + { + NotifierConfig: &storage.NotifierConfiguration_EmailConfig{ + EmailConfig: &storage.EmailNotifierConfiguration{ + NotifierId: "email-notifier-id", + MailingLists: []string{"test@example.com"}, + }, + }, + }, + }, + } + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), configID). + Return(protoReportConfig, true, nil).AnyTimes() + + s.notifierDataStore.EXPECT().GetManyNotifiers(gomock.Any(), gomock.Any()). + Return([]*storage.Notifier{ + { + Id: "email-notifier-id", + Name: "Email Notifier", + Type: "email", + }, + }, nil).AnyTimes() + + s.scheduler.EXPECT().SubmitReportRequest(gomock.Any(), gomock.Any(), false). + DoAndReturn(func(_ context.Context, reportReq interface{}, _ bool) (string, error) { + return "on-demand-report-id", nil + }).Times(1) + + result, err := s.service.RunNodeReport(ctx, &apiV2.RunReportRequest{ + ReportConfigId: configID, + }) + s.NoError(err) + s.Equal(configID, result.GetReportConfigId()) + s.Equal("on-demand-report-id", result.GetReportId()) +} + +func (s *NodeReportServiceTestSuite) TestCancelNodeReport() { + reportID := "test-report-id" + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: "test-config-id", + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_PREPARING, + }, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + s.scheduler.EXPECT().CancelReportRequest(gomock.Any(), reportID). + Return(false, nil).Times(1) + + _, err := s.service.CancelNodeReport(ctx, &apiV2.ResourceByID{Id: reportID}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReport() { + reportID := "test-report-id" + configID := "test-config-id" + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: configID, + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + blobPath := common.GetReportBlobPath(configID, reportID) + s.blobStore.EXPECT().Delete(gomock.Any(), blobPath).Return(nil).Times(1) + + _, err := s.service.DeleteNodeReport(ctx, &apiV2.DeleteReportRequest{ + Id: reportID, + }) + s.NoError(err) +} + +func (s *NodeReportServiceTestSuite) TestPostViewBasedNodeReport() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + + req := &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + } + + mockRole := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + mockID.EXPECT().UID().Return(creator.GetId()).AnyTimes() + mockID.EXPECT().FullName().Return(creator.GetName()).AnyTimes() + mockID.EXPECT().FriendlyName().Return(creator.GetName()).AnyTimes() + mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).AnyTimes() + mockRole.EXPECT().GetAccessScope().Return(&storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{"cluster-1"}, + }, + }).AnyTimes() + + ctxWithIdentity := authn.ContextWithIdentity(s.ctx, mockID, s.T()) + + s.scheduler.EXPECT().SubmitReportRequest(gomock.Any(), gomock.Any(), false). + DoAndReturn(func(_ context.Context, reportReq interface{}, _ bool) (string, error) { + return "view-based-report-id", nil + }).Times(1) + + result, err := s.service.PostViewBasedNodeReport(ctxWithIdentity, req) + s.NoError(err) + s.NotEmpty(result.GetReportID()) + s.Equal("view-based-report-id", result.GetReportID()) +} + +func (s *NodeReportServiceTestSuite) TestListNodeReportConfigurations() { + protoReportConfigs := []*storage.ReportConfiguration{ + { + Id: "config-1", + Name: "Node Report 1", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, + { + Id: "config-2", + Name: "Node Report 2", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, + } + + s.reportConfigDataStore.EXPECT().GetReportConfigurations(gomock.Any(), gomock.Any()). + Return(protoReportConfigs, nil).Times(1) + + result, err := s.service.ListNodeReportConfigurations(s.ctx, &apiV2.RawQuery{Query: ""}) + s.NoError(err) + s.Len(result.GetReportConfigs(), 2) + assert.Equal(s.T(), apiV2.ReportConfiguration_NODE_VULNERABILITY, result.GetReportConfigs()[0].GetType()) +} + +func (s *NodeReportServiceTestSuite) TestCountNodeReportConfigurations() { + s.reportConfigDataStore.EXPECT().Count(gomock.Any(), gomock.Any()). + Return(5, nil).Times(1) + + result, err := s.service.CountNodeReportConfigurations(s.ctx, &apiV2.RawQuery{Query: ""}) + s.NoError(err) + s.Equal(int32(5), result.GetCount()) +} + +func (s *NodeReportServiceTestSuite) TestGetMyNodeReportHistory() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + configID := "test-config-id" + // Query-time filtering: only return creator's snapshot + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "report-1", + ReportConfigurationId: configID, + Name: "Report 1", + Requester: creator, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return(snapshots, nil).Times(1) + + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetMyNodeReportHistory(ctx, &apiV2.GetReportHistoryRequest{ + Id: configID, + ReportParamQuery: &apiV2.RawQuery{ + Query: "", + }, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) + s.Equal(creator.GetId(), result.GetReportSnapshots()[0].GetUser().GetId()) +} + +func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfiguration() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + + accessScope := &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{ + IncludedClusters: []string{"cluster-1"}, + }, + } + + updateConfig := s.getValidNodeReportConfig() + updateConfig.Name = "Updated Node Report" + + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + ctx := authn.ContextWithIdentity(s.ctx, mockID, s.T()) + + mockID.EXPECT().UID().Return(creator.GetId()).AnyTimes() + mockID.EXPECT().FullName().Return(creator.GetName()).AnyTimes() + mockID.EXPECT().FriendlyName().Return(creator.GetName()).AnyTimes() + + mockRole := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + mockRole.EXPECT().GetAccessScope().Return(accessScope).AnyTimes() + mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).AnyTimes() + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + + existingConfig := &storage.ReportConfiguration{ + Id: updateConfig.GetId(), + Name: "Old Name", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{ + { + Value: "cluster-1", + MatchType: storage.MatchType_EXACT, + }, + }, + }, + }, + }, + }, + }, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + } + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), updateConfig.GetId()). + Return(existingConfig, true, nil).Times(1) + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{}, nil).Times(1) + + s.reportConfigDataStore.EXPECT().UpdateReportConfiguration(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, cfg *storage.ReportConfiguration) error { + s.Equal("Updated Node Report", cfg.GetName()) + return nil + }).Times(1) + + s.scheduler.EXPECT().UpsertReportSchedule(gomock.Any()).Return(nil).Times(1) + + result, err := s.service.UpdateNodeReportConfiguration(ctx, updateConfig) + s.NoError(err) + s.NotNil(result) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportHistory() { + configID := "test-config-id" + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "report-1", + ReportConfigurationId: configID, + Name: "Report 1", + Requester: &storage.SlimUser{Id: "uid", Name: "name"}, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{}, + }, + }, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return(snapshots, nil).Times(1) + + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetNodeReportHistory(s.ctx, &apiV2.GetReportHistoryRequest{ + Id: configID, + ReportParamQuery: &apiV2.RawQuery{ + Query: "", + }, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) + s.Equal("report-1", result.GetReportSnapshots()[0].GetReportJobId()) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportStatus() { + reportID := "test-report-id" + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + result, err := s.service.GetNodeReportStatus(s.ctx, &apiV2.ResourceByID{Id: reportID}) + s.NoError(err) + s.NotNil(result.GetStatus()) + s.Equal(apiV2.ReportStatus_GENERATED, result.GetStatus().GetRunState()) +} + +func (s *NodeReportServiceTestSuite) TestGetViewBasedNodeReportHistory() { + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "view-report-1", + ReportConfigurationId: "", + Name: "View Report 1", + Requester: &storage.SlimUser{Id: "uid", Name: "name"}, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{}, + }, + }, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return(snapshots, nil).Times(1) + + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetViewBasedNodeReportHistory(s.ctx, &apiV2.GetViewBasedReportHistoryRequest{ + ReportParamQuery: &apiV2.RawQuery{ + Query: "", + }, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) +} + +func (s *NodeReportServiceTestSuite) TestGetViewBasedMyNodeReportHistory() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + // Query-time filtering: only return creator's snapshot + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "view-report-1", + ReportConfigurationId: "", + Name: "View Report 1", + Requester: creator, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{}, + }, + }, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return(snapshots, nil).Times(1) + + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetViewBasedMyNodeReportHistory(ctx, &apiV2.GetViewBasedReportHistoryRequest{ + ReportParamQuery: &apiV2.RawQuery{ + Query: "", + }, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) + s.Equal(creator.GetId(), result.GetReportSnapshots()[0].GetUser().GetId()) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportConfiguration_InvalidType() { + protoReportConfig := &storage.ReportConfiguration{ + Id: "test-id", + Type: storage.ReportConfiguration_VULNERABILITY, + } + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(protoReportConfig, true, nil).Times(1) + + _, err := s.service.GetNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReportConfiguration_WrongType() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(&storage.ReportConfiguration{ + Id: "test-id", + Type: storage.ReportConfiguration_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.DeleteNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestRunNodeReport_WrongType() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "config-id"). + Return(&storage.ReportConfiguration{ + Id: "config-id", + Type: storage.ReportConfiguration_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.RunNodeReport(ctx, &apiV2.RunReportRequest{ + ReportConfigId: "config-id", + }) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfiguration_WrongType() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + wrongConfig := s.getValidNodeReportConfig() + wrongConfig.Type = apiV2.ReportConfiguration_VULNERABILITY + + _, err := s.service.UpdateNodeReportConfiguration(ctx, wrongConfig) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_WrongType() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + wrongConfig := s.getValidNodeReportConfig() + wrongConfig.Type = apiV2.ReportConfiguration_VULNERABILITY + + _, err := s.service.PostNodeReportConfiguration(ctx, wrongConfig) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_RequiresIdentity() { + _, err := s.service.PostNodeReportConfiguration(s.ctx, s.getValidNodeReportConfig()) + s.Error(err) + s.ErrorIs(err, errox.NoCredentials) +} + +func (s *NodeReportServiceTestSuite) TestCancelNodeReport_NotFound() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), "nonexistent"). + Return(nil, false, nil).Times(1) + + _, err := s.service.CancelNodeReport(ctx, &apiV2.ResourceByID{Id: "nonexistent"}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportStatus_NotFound() { + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), "nonexistent"). + Return(nil, false, nil).Times(1) + + _, err := s.service.GetNodeReportStatus(s.ctx, &apiV2.ResourceByID{Id: "nonexistent"}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestCancelNodeReport_WrongState() { + reportID := "test-report-id" + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: "test-config-id", + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_DELIVERED, + }, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + _, err := s.service.CancelNodeReport(ctx, &apiV2.ResourceByID{Id: reportID}) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestCancelNodeReport_WrongType() { + reportID := "test-report-id" + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: "test-config-id", + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_PREPARING, + }, + Type: storage.ReportSnapshot_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + _, err := s.service.CancelNodeReport(ctx, &apiV2.ResourceByID{Id: reportID}) + s.Error(err) + s.Contains(err.Error(), "not a node vulnerability report") +} + +func (s *NodeReportServiceTestSuite) TestPostViewBasedNodeReport_WrongType() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + req := &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_VULNERABILITY, + } + + _, err := s.service.PostViewBasedNodeReport(ctx, req) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReport_NotFound() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), "nonexistent"). + Return(nil, false, nil).Times(1) + + _, err := s.service.DeleteNodeReport(ctx, &apiV2.DeleteReportRequest{ + Id: "nonexistent", + }) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReport_WrongType() { + reportID := "test-report-id" + configID := "test-config-id" + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + snapshot := &storage.ReportSnapshot{ + ReportId: reportID, + ReportConfigurationId: configID, + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_GENERATED, + ReportNotificationMethod: storage.ReportStatus_DOWNLOAD, + }, + Type: storage.ReportSnapshot_VULNERABILITY, + } + + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(snapshot, true, nil).Times(1) + + _, err := s.service.DeleteNodeReport(ctx, &apiV2.DeleteReportRequest{ + Id: reportID, + }) + s.Error(err) + s.Contains(err.Error(), "not a node vulnerability report") +} + +func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfiguration_NotFound() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + updateConfig := s.getValidNodeReportConfig() + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), updateConfig.GetId()). + Return(nil, false, nil).Times(1) + + _, err := s.service.UpdateNodeReportConfiguration(ctx, updateConfig) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestRunNodeReport_ConfigNotFound() { + creator := &storage.SlimUser{ + Id: "uid", + Name: "name", + } + ctx := s.getContextForUser(creator) + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "nonexistent"). + Return(nil, false, nil).Times(1) + + _, err := s.service.RunNodeReport(ctx, &apiV2.RunReportRequest{ + ReportConfigId: "nonexistent", + }) + s.Error(err) +} + +func (s *NodeReportServiceTestSuite) TestRunNodeReport_NoIdentity() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-config-id"). + Return(&storage.ReportConfiguration{ + Id: "test-config-id", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.RunNodeReport(s.ctx, &apiV2.RunReportRequest{ + ReportConfigId: "test-config-id", + }) + s.Error(err) + s.Contains(err.Error(), "user identity") +} + +func (s *NodeReportServiceTestSuite) TestGetNodeReportStatus_WrongType() { + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), "report-id"). + Return(&storage.ReportSnapshot{ + ReportId: "report-id", + Type: storage.ReportSnapshot_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.GetNodeReportStatus(s.ctx, &apiV2.ResourceByID{Id: "report-id"}) + s.Error(err) + s.Contains(err.Error(), "not a node vulnerability report") +} + +func (s *NodeReportServiceTestSuite) TestGetMyNodeReportHistory_UsesUserID() { + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "report-1", + ReportConfigurationId: "test-config-id", + Requester: creator, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, q *v1.Query) ([]*storage.ReportSnapshot, error) { + var foundUserID bool + search.ApplyFnToAllBaseQueries(q, func(bq *v1.BaseQuery) { + mfQ, ok := bq.GetQuery().(*v1.BaseQuery_MatchFieldQuery) + if !ok { + return + } + s.NotEqual("Requester User ID", mfQ.MatchFieldQuery.GetField()) + if mfQ.MatchFieldQuery.GetField() == search.UserID.String() { + foundUserID = true + s.Contains(mfQ.MatchFieldQuery.GetValue(), "uid") + } + }) + s.True(foundUserID) + return snapshots, nil + }).Times(1) + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetMyNodeReportHistory(ctx, &apiV2.GetReportHistoryRequest{ + Id: "test-config-id", + ReportParamQuery: &apiV2.RawQuery{ + Query: "", + }, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) +} + +func (s *NodeReportServiceTestSuite) TestGetViewBasedNodeReportHistory_ElevatesWorkflowAdministrationRead() { + limitedCtx := sac.WithGlobalAccessScopeChecker(context.Background(), + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), + sac.ResourceScopeKeys(resources.Node, resources.Cluster), + ), + ) + snapshots := []*storage.ReportSnapshot{ + { + ReportId: "view-report-1", + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + }, + } + + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, _ *v1.Query) ([]*storage.ReportSnapshot, error) { + allowed, err := sac.ForResource(resources.WorkflowAdministration).ReadAllowed(ctx) + s.NoError(err) + s.True(allowed) + return snapshots, nil + }).Times(1) + s.blobStore.EXPECT().Search(gomock.Any(), gomock.Any()).Return([]search.Result{}, nil).AnyTimes() + + result, err := s.service.GetViewBasedNodeReportHistory(limitedCtx, &apiV2.GetViewBasedReportHistoryRequest{ + ReportParamQuery: &apiV2.RawQuery{Query: ""}, + }) + s.NoError(err) + s.Len(result.GetReportSnapshots(), 1) +} + +func (s *NodeReportServiceTestSuite) TestAuthz() { + svc := serviceImpl{snapshotDatastore: s.reportSnapshotDataStore} + testutils.AssertAuthzWorks(s.T(), &svc) +} + +func (s *NodeReportServiceTestSuite) TestAuthzPermissions() { + svc := &serviceImpl{} + + ctxWithPerms := func(perms map[string]storage.Access) context.Context { + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + mockID.EXPECT().Permissions().Return(perms).AnyTimes() + return authn.ContextWithIdentity(context.Background(), mockID, s.T()) + } + + viewNodeCluster := map[string]storage.Access{ + "Node": storage.Access_READ_ACCESS, + "Cluster": storage.Access_READ_ACCESS, + } + viewWorkflow := map[string]storage.Access{ + "WorkflowAdministration": storage.Access_READ_ACCESS, + "Node": storage.Access_READ_ACCESS, + "Cluster": storage.Access_READ_ACCESS, + } + modifyWorkflow := map[string]storage.Access{ + "WorkflowAdministration": storage.Access_READ_WRITE_ACCESS, + "Node": storage.Access_READ_ACCESS, + "Cluster": storage.Access_READ_ACCESS, + } + nodeOnly := map[string]storage.Access{ + "Node": storage.Access_READ_ACCESS, + } + + testCases := map[string]struct { + method string + allowedPerms map[string]storage.Access + deniedPerms map[string]storage.Access + }{ + "GetNodeReportStatus allowed with View Node+Cluster, denied without Cluster": { + method: apiV2.NodeReportService_GetNodeReportStatus_FullMethodName, + allowedPerms: viewNodeCluster, + deniedPerms: nodeOnly, + }, + "CancelNodeReport allowed with View Node+Cluster, denied without Cluster": { + method: apiV2.NodeReportService_CancelNodeReport_FullMethodName, + allowedPerms: viewNodeCluster, + deniedPerms: nodeOnly, + }, + "DeleteNodeReport allowed with View Node+Cluster, denied without Cluster": { + method: apiV2.NodeReportService_DeleteNodeReport_FullMethodName, + allowedPerms: viewNodeCluster, + deniedPerms: nodeOnly, + }, + "PostViewBasedNodeReport allowed with View Node+Cluster, denied without Cluster": { + method: apiV2.NodeReportService_PostViewBasedNodeReport_FullMethodName, + allowedPerms: viewNodeCluster, + deniedPerms: nodeOnly, + }, + "ListNodeReportConfigurations requires View WorkflowAdministration": { + method: apiV2.NodeReportService_ListNodeReportConfigurations_FullMethodName, + allowedPerms: viewWorkflow, + deniedPerms: viewNodeCluster, + }, + "RunNodeReport requires Modify WorkflowAdministration": { + method: apiV2.NodeReportService_RunNodeReport_FullMethodName, + allowedPerms: modifyWorkflow, + deniedPerms: viewNodeCluster, + }, + } + + for name, tc := range testCases { + s.T().Run(name, func(t *testing.T) { + _, err := svc.AuthFuncOverride(ctxWithPerms(tc.allowedPerms), tc.method) + assert.NoError(t, err, "should be allowed with sufficient permissions") + + _, err = svc.AuthFuncOverride(ctxWithPerms(tc.deniedPerms), tc.method) + assert.Error(t, err, "should be denied with insufficient permissions") + }) + } +} + +func (s *NodeReportServiceTestSuite) expectNotify(ctx context.Context, channel, payload string) { + s.db.EXPECT().Exec(ctx, "SELECT pg_notify($1, $2)", channel, payload). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) +} + +func (s *NodeReportServiceTestSuite) TestPostNodeReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + accessScope := &storage.SimpleAccessScope{ + Rules: &storage.SimpleAccessScope_Rules{IncludedClusters: []string{"cluster-1"}}, + } + requestConfig := s.getValidNodeReportConfig() + mockID := mockIdentity.NewMockIdentity(s.mockCtrl) + ctx := authn.ContextWithIdentity(s.ctx, mockID, s.T()) + mockID.EXPECT().UID().Return(creator.GetId()).AnyTimes() + mockID.EXPECT().FullName().Return(creator.GetName()).AnyTimes() + mockID.EXPECT().FriendlyName().Return(creator.GetName()).AnyTimes() + mockRole := permissionsMocks.NewMockResolvedRole(s.mockCtrl) + mockRole.EXPECT().GetAccessScope().Return(accessScope).Times(1) + mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).Times(1) + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, cfg *storage.ReportConfiguration) (string, error) { + return cfg.GetId(), nil + }).Times(1) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), requestConfig.GetId()). + Return(&storage.ReportConfiguration{ + Id: requestConfig.GetId(), + Name: requestConfig.GetName(), + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + }, + }, true, nil).Times(1) + s.expectNotify(ctx, pgNotify.ReportConfigChanged, requestConfig.GetId()) + + result, err := s.service.PostNodeReportConfiguration(ctx, requestConfig) + s.NoError(err) + s.Equal(requestConfig.GetId(), result.GetId()) +} + +func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + updateConfig := s.getValidNodeReportConfig() + ctx := s.getContextForUser(creator) + + s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), updateConfig.GetId()). + Return(&storage.ReportConfiguration{ + Id: updateConfig.GetId(), + Name: "Old Name", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + }, + }, true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportConfigDataStore.EXPECT().UpdateReportConfiguration(gomock.Any(), gomock.Any()).Return(nil).Times(1) + s.expectNotify(ctx, pgNotify.ReportConfigChanged, updateConfig.GetId()) + + _, err := s.service.UpdateNodeReportConfiguration(ctx, updateConfig) + s.NoError(err) +} + +func (s *NodeReportServiceTestSuite) TestDeleteNodeReportConfigurationWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "test-id"). + Return(&storage.ReportConfiguration{ + Id: "test-id", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportConfigDataStore.EXPECT().RemoveReportConfiguration(gomock.Any(), "test-id"). + Return(nil).Times(1) + s.expectNotify(s.ctx, pgNotify.ReportConfigChanged, "test-id") + + _, err := s.service.DeleteNodeReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "test-id"}) + s.NoError(err) +} + +func (s *NodeReportServiceTestSuite) TestRunNodeReportWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + configID := "test-config-id" + protoReportConfig := &storage.ReportConfiguration{ + Id: configID, + Name: "test node report", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + Creator: creator, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{{Value: "cluster-1", MatchType: storage.MatchType_EXACT}}, + }, + }, + }, + }, + }, + Filter: &storage.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &storage.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + }, + } + + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), configID). + Return(protoReportConfig, true, nil).AnyTimes() + s.notifierDataStore.EXPECT().GetManyNotifiers(gomock.Any(), gomock.Any()). + Return(nil, nil).AnyTimes() + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + Return([]*storage.ReportSnapshot{}, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().AddReportSnapshot(gomock.Any(), gomock.Any()). + Return("on-demand-report-id", nil).Times(1) + s.db.EXPECT().Exec(gomock.Any(), "SELECT pg_notify($1, $2)", pgNotify.ReportRequestSubmitted, "on-demand-report-id"). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) + + result, err := s.service.RunNodeReport(ctx, &apiV2.RunReportRequest{ + ReportConfigId: configID, + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.NoError(err) + s.Equal("on-demand-report-id", result.GetReportId()) +} + +func (s *NodeReportServiceTestSuite) TestPostViewBasedNodeReportWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + req := &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:cluster-1", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + }, + } + + s.reportSnapshotDataStore.EXPECT().Count(gomock.Any(), gomock.Any()). + Return(0, nil).Times(1) + s.reportSnapshotDataStore.EXPECT().AddReportSnapshot(gomock.Any(), gomock.Any()). + Return("view-based-report-id", nil).Times(1) + s.db.EXPECT().Exec(gomock.Any(), "SELECT pg_notify($1, $2)", pgNotify.ReportRequestSubmitted, "view-based-report-id"). + Return(pgconn.NewCommandTag("SELECT 1"), nil).Times(1) + + result, err := s.service.PostViewBasedNodeReport(ctx, req) + s.NoError(err) + s.Equal("view-based-report-id", result.GetReportID()) +} + +func (s *NodeReportServiceTestSuite) TestCancelNodeReportWithCentralWorker() { + s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") + + reportID := "test-report-id" + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), reportID). + Return(&storage.ReportSnapshot{ + ReportId: reportID, + Requester: creator, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_PREPARING, + }, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + }, true, nil).Times(1) + s.expectNotify(ctx, pgNotify.ReportRequestCancelled, reportID) + + _, err := s.service.CancelNodeReport(ctx, &apiV2.ResourceByID{Id: reportID}) + s.NoError(err) +} diff --git a/central/reports/service/v2/node/singleton.go b/central/reports/service/v2/node/singleton.go new file mode 100644 index 0000000000000..a3d0abe2cbafd --- /dev/null +++ b/central/reports/service/v2/node/singleton.go @@ -0,0 +1,33 @@ +package node + +import ( + blobDS "github.com/stackrox/rox/central/blob/datastore" + "github.com/stackrox/rox/central/globaldb" + notifierDS "github.com/stackrox/rox/central/notifier/datastore" + reportConfigDS "github.com/stackrox/rox/central/reports/config/datastore" + schedulerV2 "github.com/stackrox/rox/central/reports/scheduler/v2" + snapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore" + "github.com/stackrox/rox/central/reports/validation" + "github.com/stackrox/rox/pkg/sync" +) + +var ( + once sync.Once + svc Service +) + +// Singleton returns the singleton instance of the node report service. +func Singleton() Service { + once.Do(func() { + svc = New( + reportConfigDS.Singleton(), + snapshotDS.Singleton(), + notifierDS.Singleton(), + schedulerV2.Singleton(), + blobDS.Singleton(), + validation.Singleton(), + globaldb.GetPostgres(), + ) + }) + return svc +} diff --git a/central/reports/service/v2/service_impl.go b/central/reports/service/v2/service_impl.go index be2bd0f43bb7d..bb1726d13400f 100644 --- a/central/reports/service/v2/service_impl.go +++ b/central/reports/service/v2/service_impl.go @@ -109,6 +109,10 @@ func (s *serviceImpl) PostReportConfiguration(ctx context.Context, request *apiV return nil, errors.New("Could not determine user identity from provided context") } + if request.GetType() == apiV2.ReportConfiguration_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.New("node vulnerability reports must be created via the node report service") + } + if err := s.validator.ValidateReportConfiguration(request); err != nil { return nil, errors.Wrap(err, "Validating report configuration") } @@ -158,6 +162,9 @@ func (s *serviceImpl) UpdateReportConfiguration(ctx context.Context, request *ap if !exists { return nil, errors.Wrapf(errox.NotFound, "report configuration with id '%s' does not exist", request.GetId()) } + if err := rejectNodeReportConfiguration(currentConfig); err != nil { + return nil, err + } query := search.NewQueryBuilder().AddExactMatches(search.ReportConfigID, request.GetId()).AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()).ProtoQuery() reportSnapshots, err := s.snapshotDatastore.SearchReportSnapshots(ctx, query) @@ -171,8 +178,11 @@ func (s *serviceImpl) UpdateReportConfiguration(ctx context.Context, request *ap } } - updatedConfig := s.convertV2ReportConfigurationToProto(request, currentConfig.GetCreator(), - currentConfig.GetVulnReportFilters().GetAccessScopeRules()) + var accessScopeRules []*storage.SimpleAccessScope_Rules + if filters := currentConfig.GetVulnReportFilters(); filters != nil { + accessScopeRules = filters.GetAccessScopeRules() + } + updatedConfig := s.convertV2ReportConfigurationToProto(request, currentConfig.GetCreator(), accessScopeRules) err = s.reportConfigStore.UpdateReportConfiguration(ctx, updatedConfig) if err != nil { @@ -193,6 +203,10 @@ func (s *serviceImpl) ListReportConfigurations(ctx context.Context, query *apiV2 if err != nil { return nil, errors.Wrap(errox.InvalidArgs, err.Error()) } + parsedQuery = search.ConjunctionQuery( + parsedQuery, + search.NewQueryBuilder().AddExactMatches(search.ReportType, storage.ReportConfiguration_VULNERABILITY.String()).ProtoQuery(), + ) // Fill in pagination. paginated.FillPaginationV2(parsedQuery, query.GetPagination(), maxPaginationLimit) @@ -223,6 +237,9 @@ func (s *serviceImpl) GetReportConfiguration(ctx context.Context, req *apiV2.Res if !exists { return nil, errors.Wrapf(errox.NotFound, "report configuration with id '%s' does not exist", req.GetId()) } + if err := rejectNodeReportConfiguration(config); err != nil { + return nil, err + } // Remove report configs with empty scope. This can happen after downgrade to a version that has less scoping methods and doesn't support the new scoping method. if !common.HasValidResourceScope(config.GetResourceScope()) { return nil, errors.Wrapf(errox.InvalidArgs, @@ -241,6 +258,10 @@ func (s *serviceImpl) CountReportConfigurations(ctx context.Context, request *ap if err != nil { return nil, errors.Wrap(errox.InvalidArgs, err.Error()) } + parsedQuery = search.ConjunctionQuery( + parsedQuery, + search.NewQueryBuilder().AddExactMatches(search.ReportType, storage.ReportConfiguration_VULNERABILITY.String()).ProtoQuery(), + ) numReportConfigs, err := s.reportConfigStore.Count(ctx, parsedQuery) if err != nil { return nil, err @@ -252,13 +273,16 @@ func (s *serviceImpl) DeleteReportConfiguration(ctx context.Context, id *apiV2.R if id.GetId() == "" { return nil, errors.Wrap(errox.InvalidArgs, "Report configuration id is required for deletion") } - _, found, err := s.reportConfigStore.GetReportConfiguration(ctx, id.GetId()) + config, found, err := s.reportConfigStore.GetReportConfiguration(ctx, id.GetId()) if err != nil { return nil, errors.Wrap(err, "Error finding report config") } if !found { return nil, errors.Wrapf(errox.NotFound, "Report config ID '%s' not found", id.GetId()) } + if err := rejectNodeReportConfiguration(config); err != nil { + return nil, err + } query := search.NewQueryBuilder().AddExactMatches(search.ReportConfigID, id.GetId()).AddExactMatches(search.ReportState, storage.ReportStatus_WAITING.String(), storage.ReportStatus_PREPARING.String()).ProtoQuery() reportSnapshots, _ := s.snapshotDatastore.SearchReportSnapshots(ctx, query) if len(reportSnapshots) > 0 { @@ -288,6 +312,9 @@ func (s *serviceImpl) GetReportStatus(ctx context.Context, req *apiV2.ResourceBy if !found { return nil, errors.Wrapf(errox.NotFound, "Report snapshot not found for job id %s", req.GetId()) } + if err := rejectNodeReportSnapshot(rep); err != nil { + return nil, err + } status := s.convertPrototoV2Reportstatus(rep.GetReportStatus()) return &apiV2.ReportStatusResponse{Status: status}, err } @@ -302,7 +329,10 @@ func (s *serviceImpl) GetReportHistory(ctx context.Context, req *apiV2.GetReport } conjunctionQuery := search.ConjunctionQuery( - search.NewQueryBuilder().AddExactMatches(search.ReportConfigID, req.GetId()).ProtoQuery(), + search.NewQueryBuilder(). + AddExactMatches(search.ReportConfigID, req.GetId()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()). + ProtoQuery(), parsedQuery, ) // Fill in pagination. @@ -344,7 +374,9 @@ func (s *serviceImpl) GetMyReportHistory(ctx context.Context, req *apiV2.GetRepo conjunctionQuery := search.ConjunctionQuery( search.NewQueryBuilder(). AddExactMatches(search.ReportConfigID, req.GetId()). - AddExactMatches(search.UserID, slimUser.GetId()).ProtoQuery(), + AddExactMatches(search.UserID, slimUser.GetId()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()). + ProtoQuery(), parsedQuery, ) @@ -389,6 +421,9 @@ func (s *serviceImpl) RunReport(ctx context.Context, req *apiV2.RunReportRequest if err != nil { return nil, err } + if reportReq.ReportSnapshot.GetType() == storage.ReportSnapshot_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.Newf("report configuration '%s' is a node vulnerability report; use the node report service", req.GetReportConfigId()) + } if env.CentralWorkerEnabled.BooleanSetting() { reportID, err := s.persistSnapshotAndNotify(ctx, reportReq, pgNotify.ReportRequestSubmitted) @@ -461,6 +496,9 @@ func (s *serviceImpl) DeleteReport(ctx context.Context, req *apiV2.DeleteReportR if !found { return nil, errors.Wrapf(errox.NotFound, "Error finding report snapshot with job ID '%q'.", req.GetId()) } + if err := rejectNodeReportSnapshot(rep); err != nil { + return nil, err + } if slimUser.GetId() != rep.GetRequester().GetId() { return nil, errors.Wrap(errox.NotAuthorized, "Report cannot be deleted by a user who did not request the report.") @@ -500,6 +538,9 @@ func (s *serviceImpl) PostViewBasedReport(ctx context.Context, req *apiV2.Report if req == nil { return nil, errors.Wrap(errox.InvalidArgs, "Empty Request Body") } + if req.GetType() == apiV2.ReportRequestViewBased_NODE_VULNERABILITY { + return nil, errox.InvalidArgs.New("node vulnerability reports must be created via the node report service") + } requesterID := authn.IdentityFromContextOrNil(ctx) if requesterID == nil { @@ -542,7 +583,9 @@ func (s *serviceImpl) GetViewBasedReportHistory(ctx context.Context, req *apiV2. conjunctionQuery := search.ConjunctionQuery( search.NewQueryBuilder().AddExactMatches( search.ReportRequestType, - storage.ReportStatus_VIEW_BASED.String()).ProtoQuery(), + storage.ReportStatus_VIEW_BASED.String()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()). + ProtoQuery(), parsedQuery, ) // Fill in pagination. @@ -551,13 +594,7 @@ func (s *serviceImpl) GetViewBasedReportHistory(ctx context.Context, req *apiV2. // View-based history endpoints are authorized with only Image+Deployment view. // The snapshot datastore requires WorkflowAdministration read, so elevate the // context to that single read scope instead of granting unrestricted access. - snapshotReadCtx := sac.WithGlobalAccessScopeChecker(ctx, - sac.AllowFixedScopes( - sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), - sac.ResourceScopeKeys(resources.WorkflowAdministration), - ), - ) - results, err := s.snapshotDatastore.SearchReportSnapshots(snapshotReadCtx, conjunctionQuery) + results, err := s.snapshotDatastore.SearchReportSnapshots(SnapshotReadContext(ctx), conjunctionQuery) if err != nil { return nil, err } @@ -596,6 +633,7 @@ func (s *serviceImpl) GetViewBasedMyReportHistory(ctx context.Context, req *apiV search.NewQueryBuilder(). AddExactMatches(search.UserID, slimUser.GetId()). AddExactMatches(search.ReportRequestType, storage.ReportStatus_VIEW_BASED.String()). + AddExactMatches(search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()). ProtoQuery(), parsedQuery, ) @@ -607,13 +645,7 @@ func (s *serviceImpl) GetViewBasedMyReportHistory(ctx context.Context, req *apiV // The snapshot datastore requires WorkflowAdministration read, so elevate the // context to that single read scope. Results are already scoped to the requesting // user via the UserID query filter above. - snapshotReadCtx := sac.WithGlobalAccessScopeChecker(ctx, - sac.AllowFixedScopes( - sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), - sac.ResourceScopeKeys(resources.WorkflowAdministration), - ), - ) - results, err := s.snapshotDatastore.SearchReportSnapshots(snapshotReadCtx, conjunctionQuery) + results, err := s.snapshotDatastore.SearchReportSnapshots(SnapshotReadContext(ctx), conjunctionQuery) if err != nil { return nil, err } @@ -628,6 +660,11 @@ func (s *serviceImpl) GetViewBasedMyReportHistory(ctx context.Context, req *apiV } func verifyNoUserSearchLabels(q *v1.Query) error { + return VerifyNoUserSearchLabels(q) +} + +// VerifyNoUserSearchLabels rejects queries that filter on user identity fields. +func VerifyNoUserSearchLabels(q *v1.Query) error { unexpectedLabels := set.NewStringSet(search.UserID.String(), search.UserName.String()) var err error search.ApplyFnToAllBaseQueries(q, func(bq *v1.BaseQuery) { @@ -641,6 +678,12 @@ func verifyNoUserSearchLabels(q *v1.Query) error { } func notifyWithRetry(ctx context.Context, db postgres.DB, channel, payload string) { + NotifyWithRetry(ctx, db, channel, payload) +} + +// NotifyWithRetry sends a pg_notify on the given channel, retrying a few times on failure. +// Failures are logged and not returned so that the originating RPC can still succeed. +func NotifyWithRetry(ctx context.Context, db postgres.DB, channel, payload string) { if db == nil { return } @@ -655,10 +698,40 @@ func notifyWithRetry(ctx context.Context, db postgres.DB, channel, payload strin } func (s *serviceImpl) persistSnapshotAndNotify(ctx context.Context, reportReq *reportGen.ReportRequest, channel string) (string, error) { - reportID, err := s.validator.PersistReportSnapshot(ctx, reportReq.ReportSnapshot) + return PersistSnapshotAndNotify(ctx, s.validator, s.db, reportReq, channel) +} + +func rejectNodeReportConfiguration(config *storage.ReportConfiguration) error { + if config.GetType() == storage.ReportConfiguration_NODE_VULNERABILITY { + return errox.InvalidArgs.Newf("report configuration '%s' is a node vulnerability report; use the node report service", config.GetId()) + } + return nil +} + +func rejectNodeReportSnapshot(snapshot *storage.ReportSnapshot) error { + if snapshot.GetType() == storage.ReportSnapshot_NODE_VULNERABILITY { + return errox.InvalidArgs.Newf("report job '%s' is a node vulnerability report; use the node report service", snapshot.GetReportId()) + } + return nil +} + +// SnapshotReadContext elevates the request context with WorkflowAdministration +// read so view-based history RPCs can query the snapshot datastore. +func SnapshotReadContext(ctx context.Context) context.Context { + return sac.WithGlobalAccessScopeChecker(ctx, + sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), + sac.ResourceScopeKeys(resources.WorkflowAdministration), + ), + ) +} + +// PersistSnapshotAndNotify persists a WAITING snapshot and notifies the worker. +func PersistSnapshotAndNotify(ctx context.Context, validator *validation.Validator, db postgres.DB, reportReq *reportGen.ReportRequest, channel string) (string, error) { + reportID, err := validator.PersistReportSnapshot(ctx, reportReq.ReportSnapshot) if err != nil { return "", err } - notifyWithRetry(ctx, s.db, channel, reportID) + NotifyWithRetry(ctx, db, channel, reportID) return reportID, nil } diff --git a/central/reports/service/v2/service_impl_test.go b/central/reports/service/v2/service_impl_test.go index 8b7de86da3b21..94f63715281e1 100644 --- a/central/reports/service/v2/service_impl_test.go +++ b/central/reports/service/v2/service_impl_test.go @@ -139,6 +139,109 @@ func (s *ReportServiceTestSuite) TestCreateReportConfiguration() { s.Error(err) } +func (s *ReportServiceTestSuite) TestPostReportConfiguration_RejectsNodeType() { + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + _, err := s.service.PostReportConfiguration(ctx, &apiV2.ReportConfiguration{ + Name: "node report", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + }) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestUpdateReportConfiguration_RejectsNodeType() { + protoReportConfig := &storage.ReportConfiguration{ + Id: "node-config", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + } + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "node-config"). + Return(protoReportConfig, true, nil).Times(1) + s.collectionDataStore.EXPECT().Exists(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() + s.notifierDataStore.EXPECT().Exists(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() + + requestConfig := fixtures.GetValidV2ReportConfigWithMultipleNotifiers() + requestConfig.Id = "node-config" + _, err := s.service.UpdateReportConfiguration(s.ctx, requestConfig) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestGetReportConfiguration_RejectsNodeType() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "node-config"). + Return(&storage.ReportConfiguration{ + Id: "node-config", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.GetReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "node-config"}) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestDeleteReportConfiguration_RejectsNodeType() { + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "node-config"). + Return(&storage.ReportConfiguration{ + Id: "node-config", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + }, true, nil).Times(1) + + _, err := s.service.DeleteReportConfiguration(s.ctx, &apiV2.ResourceByID{Id: "node-config"}) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestRunReport_RejectsNodeType() { + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + nodeConfig := &storage.ReportConfiguration{ + Id: "node-config", + Name: "node report", + Type: storage.ReportConfiguration_NODE_VULNERABILITY, + ResourceScope: &storage.ResourceScope{ + ScopeReference: &storage.ResourceScope_EntityScope{ + EntityScope: &storage.EntityScope{ + Rules: []*storage.EntityScopeRule{ + { + Entity: storage.EntityType_ENTITY_TYPE_CLUSTER, + Field: storage.EntityField_FIELD_ID, + Values: []*storage.RuleValue{{Value: "cluster-1", MatchType: storage.MatchType_EXACT}}, + }, + }, + }, + }, + }, + } + s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "node-config"). + Return(nodeConfig, true, nil).Times(1) + s.notifierDataStore.EXPECT().GetManyNotifiers(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) + + _, err := s.service.RunReport(ctx, &apiV2.RunReportRequest{ + ReportConfigId: "node-config", + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestPostViewBasedReport_RejectsNodeType() { + s.T().Setenv(features.VulnerabilityViewBasedReports.EnvVar(), "true") + creator := &storage.SlimUser{Id: "uid", Name: "name"} + ctx := s.getContextForUser(creator) + _, err := s.service.PostViewBasedReport(ctx, &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + }) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func imageReportTypeQuery(base *v1.Query) *v1.Query { + return search.ConjunctionQuery( + base, + search.NewQueryBuilder().AddExactMatches(search.ReportType, storage.ReportConfiguration_VULNERABILITY.String()).ProtoQuery(), + ) +} + func (s *ReportServiceTestSuite) TestCreateReportConfigurationWithCentralWorker() { s.T().Setenv(env.CentralWorkerEnabled.EnvVar(), "true") @@ -300,7 +403,7 @@ func (s *ReportServiceTestSuite) TestListReportConfigurations() { desc: "Empty query", query: &apiV2.RawQuery{Query: ""}, expectedQ: func() *v1.Query { - query := search.EmptyQuery() + query := imageReportTypeQuery(search.EmptyQuery()) query.Pagination = &v1.QueryPagination{Limit: maxPaginationLimit} return query }(), @@ -309,7 +412,7 @@ func (s *ReportServiceTestSuite) TestListReportConfigurations() { desc: "Query with search field", query: &apiV2.RawQuery{Query: "Report Name:name"}, expectedQ: func() *v1.Query { - query := search.NewQueryBuilder().AddStrings(search.ReportName, "name").ProtoQuery() + query := imageReportTypeQuery(search.NewQueryBuilder().AddStrings(search.ReportName, "name").ProtoQuery()) query.Pagination = &v1.QueryPagination{Limit: maxPaginationLimit} return query }(), @@ -321,7 +424,7 @@ func (s *ReportServiceTestSuite) TestListReportConfigurations() { Pagination: &apiV2.Pagination{Limit: 25}, }, expectedQ: func() *v1.Query { - query := search.EmptyQuery() + query := imageReportTypeQuery(search.EmptyQuery()) query.Pagination = &v1.QueryPagination{Limit: 25} return query }(), @@ -415,12 +518,12 @@ func (s *ReportServiceTestSuite) TestCountReportConfigurations() { { desc: "Empty query", query: &apiV2.RawQuery{Query: ""}, - expectedQ: search.NewQueryBuilder().ProtoQuery(), + expectedQ: imageReportTypeQuery(search.NewQueryBuilder().ProtoQuery()), }, { desc: "Query with search field", query: &apiV2.RawQuery{Query: "Report Name:name"}, - expectedQ: search.NewQueryBuilder().AddStrings(search.ReportName, "name").ProtoQuery(), + expectedQ: imageReportTypeQuery(search.NewQueryBuilder().AddStrings(search.ReportName, "name").ProtoQuery()), }, } @@ -809,6 +912,53 @@ func (s *ReportServiceTestSuite) TestGetReportStatus() { assert.Equal(s.T(), repStatusResponse.GetStatus().GetErrorMsg(), status.GetErrorMsg()) } +func (s *ReportServiceTestSuite) TestGetReportStatus_RejectsNodeType() { + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), "node-job").Return(&storage.ReportSnapshot{ + ReportId: "node-job", + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ReportStatus: &storage.ReportStatus{}, + }, true, nil) + + _, err := s.service.GetReportStatus(s.ctx, &apiV2.ResourceByID{Id: "node-job"}) + s.Error(err) + s.Contains(err.Error(), "node report service") +} + +func (s *ReportServiceTestSuite) TestGetReportHistory_FiltersImageReportType() { + var captured *v1.Query + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, q *v1.Query) ([]*storage.ReportSnapshot, error) { + captured = q + return nil, nil + }) + + _, err := s.service.GetReportHistory(s.ctx, &apiV2.GetReportHistoryRequest{ + Id: "test_report_config", + ReportParamQuery: &apiV2.RawQuery{Query: ""}, + }) + s.NoError(err) + s.True(queryHasFieldValue(captured, search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()), + "history query must constrain ReportType to VULNERABILITY, got %s", captured) +} + +func (s *ReportServiceTestSuite) TestGetMyReportHistory_FiltersImageReportType() { + user := &storage.SlimUser{Id: "user-a", Name: "user-a"} + var captured *v1.Query + s.reportSnapshotDataStore.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, q *v1.Query) ([]*storage.ReportSnapshot, error) { + captured = q + return nil, nil + }) + + _, err := s.service.GetMyReportHistory(s.getContextForUser(user), &apiV2.GetReportHistoryRequest{ + Id: "test_report_config", + ReportParamQuery: &apiV2.RawQuery{Query: ""}, + }) + s.NoError(err) + s.True(queryHasFieldValue(captured, search.ReportType, storage.ReportSnapshot_VULNERABILITY.String()), + "my-history query must constrain ReportType to VULNERABILITY, got %s", captured) +} + func (s *ReportServiceTestSuite) TestGetReportHistory() { reportSnapshot := &storage.ReportSnapshot{ ReportId: "test_report", @@ -1511,6 +1661,20 @@ func (s *ReportServiceTestSuite) TestDeleteReport() { }, isError: false, }, + { + desc: "Node vulnerability snapshot is rejected", + req: &apiV2.DeleteReportRequest{ + Id: reportSnapshot.GetReportId(), + }, + ctx: userContext, + mockGen: func() { + snap := reportSnapshot.CloneVT() + snap.Type = storage.ReportSnapshot_NODE_VULNERABILITY + s.reportSnapshotDataStore.EXPECT().Get(gomock.Any(), snap.GetReportId()). + Return(snap, true, nil).Times(1) + }, + isError: true, + }, } for _, tc := range testCases { s.T().Run(tc.desc, func(t *testing.T) { @@ -1881,3 +2045,17 @@ func (s *ReportServiceTestSuite) getContextForUser(user *storage.SlimUser) conte return authn.ContextWithIdentity(s.ctx, mockID, s.T()) } + +func queryHasFieldValue(q *v1.Query, field search.FieldLabel, want string) bool { + found := false + search.ApplyFnToAllBaseQueries(q, func(bq *v1.BaseQuery) { + mfQ, ok := bq.GetQuery().(*v1.BaseQuery_MatchFieldQuery) + if !ok { + return + } + if mfQ.MatchFieldQuery.GetField() == field.String() && strings.Contains(mfQ.MatchFieldQuery.GetValue(), want) { + found = true + } + }) + return found +} diff --git a/central/reports/validation/validate.go b/central/reports/validation/validate.go index 0823bfc4cf795..03418c09311e6 100644 --- a/central/reports/validation/validate.go +++ b/central/reports/validation/validate.go @@ -422,6 +422,9 @@ func (v *Validator) ValidateCancelReportRequest(reportID string, requester *stor if !found { return errors.Wrapf(errox.NotFound, "Report snapshot with job ID '%s' does not exist", reportID) } + if snapshot.GetType() == storage.ReportSnapshot_NODE_VULNERABILITY { + return errox.InvalidArgs.Newf("report job '%s' is a node vulnerability report; use the node report service", reportID) + } switch snapshot.GetReportStatus().GetRunState() { case storage.ReportStatus_WAITING, storage.ReportStatus_PREPARING: @@ -440,7 +443,9 @@ func (v *Validator) ValidateCancelReportRequest(reportID string, requester *stor // the database. Returns the report ID. func (v *Validator) PersistReportSnapshot(ctx context.Context, snapshot *storage.ReportSnapshot) (string, error) { reportType := snapshot.GetType() - if snapshot.GetVulnReportFilters() != nil && snapshot.GetReportStatus().GetReportRequestType() == storage.ReportStatus_ON_DEMAND { + requestType := snapshot.GetReportStatus().GetReportRequestType() + + if requestType == storage.ReportStatus_ON_DEMAND { hasPending, err := v.doesUserHavePendingReport(snapshot.GetReportConfigurationId(), snapshot.GetRequester().GetId(), reportType) if err != nil { return "", err @@ -451,7 +456,7 @@ func (v *Validator) PersistReportSnapshot(ctx context.Context, snapshot *storage } } - if snapshot.GetViewBasedVulnReportFilters() != nil { + if requestType == storage.ReportStatus_VIEW_BASED { hasPending, err := v.doesUserHaveViewBasedPendingReport(snapshot.GetRequester().GetId(), reportType) if err != nil { return "", err @@ -459,6 +464,9 @@ func (v *Validator) PersistReportSnapshot(ctx context.Context, snapshot *storage if hasPending { return "", errors.New("User already has a view based report queued") } + // View-based reports are authorized at the gRPC layer with only view permissions. + // The snapshot datastore requires WorkflowAdministration write. + ctx = sac.WithAllAccess(ctx) } snapshot.ReportStatus.RunState = storage.ReportStatus_WAITING diff --git a/central/reports/validation/validate_comprehensive_test.go b/central/reports/validation/validate_comprehensive_test.go index 4fe02a86e45c6..54a53138907c0 100644 --- a/central/reports/validation/validate_comprehensive_test.go +++ b/central/reports/validation/validate_comprehensive_test.go @@ -1,6 +1,7 @@ package validation import ( + "context" "testing" notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" @@ -10,6 +11,8 @@ import ( apiV2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -592,6 +595,21 @@ func TestValidateCancelReportRequest(t *testing.T) { expectError: true, errContains: "not found", }, + "node vulnerability snapshot is rejected": { + reportID: "node-job", + setupMocks: func() { + snapshotDS.EXPECT().Get(gomock.Any(), "node-job").Return(&storage.ReportSnapshot{ + ReportId: "node-job", + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + ReportStatus: &storage.ReportStatus{ + RunState: storage.ReportStatus_WAITING, + }, + Requester: &storage.SlimUser{Id: "user-1"}, + }, true, nil) + }, + expectError: true, + errContains: "node report service", + }, } for name, tc := range tests { @@ -611,3 +629,82 @@ func TestValidateCancelReportRequest(t *testing.T) { }) } } + +func TestPersistReportSnapshot_NodeOnDemandPending(t *testing.T) { + ctrl := gomock.NewController(t) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + validator := New(reportConfigDSMocks.NewMockDataStore(ctrl), snapshotDS, collectionDSMocks.NewMockDataStore(ctrl), notifierDSMocks.NewMockDataStore(ctrl)) + + pending := &storage.ReportSnapshot{ + ReportConfigurationId: "cfg", + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + Requester: &storage.SlimUser{Id: "u1"}, + } + snapshotDS.EXPECT().SearchReportSnapshots(gomock.Any(), gomock.Any()).Return([]*storage.ReportSnapshot{pending}, nil) + + _, err := validator.PersistReportSnapshot(t.Context(), nodeOnDemandSnapshot("cfg", "u1")) + require.Error(t, err) + assert.Contains(t, err.Error(), "already has a report running") +} + +func TestPersistReportSnapshot_NodeViewBasedPending(t *testing.T) { + ctrl := gomock.NewController(t) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + validator := New(reportConfigDSMocks.NewMockDataStore(ctrl), snapshotDS, collectionDSMocks.NewMockDataStore(ctrl), notifierDSMocks.NewMockDataStore(ctrl)) + + snapshotDS.EXPECT().Count(gomock.Any(), gomock.Any()).Return(1, nil) + + _, err := validator.PersistReportSnapshot(t.Context(), nodeViewBasedSnapshot("u1")) + require.Error(t, err) + assert.Contains(t, err.Error(), "view based report queued") +} + +func TestPersistReportSnapshot_NodeViewBasedElevatesSAC(t *testing.T) { + ctrl := gomock.NewController(t) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + validator := New(reportConfigDSMocks.NewMockDataStore(ctrl), snapshotDS, collectionDSMocks.NewMockDataStore(ctrl), notifierDSMocks.NewMockDataStore(ctrl)) + + snapshotDS.EXPECT().Count(gomock.Any(), gomock.Any()).Return(0, nil) + snapshotDS.EXPECT().AddReportSnapshot(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ *storage.ReportSnapshot) (string, error) { + ok, err := sac.ForResource(resources.WorkflowAdministration).WriteAllowed(ctx) + require.NoError(t, err) + assert.True(t, ok) + return "report-id", nil + }) + + id, err := validator.PersistReportSnapshot(sac.WithNoAccess(t.Context()), nodeViewBasedSnapshot("u1")) + require.NoError(t, err) + assert.Equal(t, "report-id", id) +} + +func nodeOnDemandSnapshot(configID, userID string) *storage.ReportSnapshot { + return &storage.ReportSnapshot{ + ReportConfigurationId: configID, + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + Requester: &storage.SlimUser{Id: userID}, + Filter: &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:c1", + }, + }, + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_ON_DEMAND, + }, + } +} + +func nodeViewBasedSnapshot(userID string) *storage.ReportSnapshot { + return &storage.ReportSnapshot{ + Type: storage.ReportSnapshot_NODE_VULNERABILITY, + Requester: &storage.SlimUser{Id: userID}, + Filter: &storage.ReportSnapshot_NodeVulnReportFilters{ + NodeVulnReportFilters: &storage.NodeVulnerabilityReportFilters{ + Query: "Cluster:c1", + }, + }, + ReportStatus: &storage.ReportStatus{ + ReportRequestType: storage.ReportStatus_VIEW_BASED, + }, + } +} From f8e0261dd10abd64199a9c243245a1ee2b0aff2e Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Mon, 7 Sep 2026 14:40:01 +0200 Subject: [PATCH 218/638] tests(upgrade): prune unused prow upgrade test code (#22548) --- .openshift-ci/ci_tests.py | 31 ---------------------------- scripts/ci/jobs/gke_upgrade_tests.py | 25 ---------------------- 2 files changed, 56 deletions(-) delete mode 100755 scripts/ci/jobs/gke_upgrade_tests.py diff --git a/.openshift-ci/ci_tests.py b/.openshift-ci/ci_tests.py index 8fe503c524f21..0acc24bc895ab 100755 --- a/.openshift-ci/ci_tests.py +++ b/.openshift-ci/ci_tests.py @@ -43,37 +43,6 @@ def run(self): pass -class UpgradeTest(BaseTest): - TEST_TIMEOUT = 60 * 60 * 2 - TEST_OUTPUT_DIR = "/tmp/postgres-upgrade-test-logs" - TEST_PG_UPGRADE_OUTPUT_DIR = "/tmp/postgres-version-upgrade-test-logs" - TEST_SENSOR_OUTPUT_DIR = "/tmp/postgres-sensor-upgrade-test-logs" - - def run(self): - print("Executing the Upgrade Test") - - self.run_with_graceful_kill( - [ - "tests/upgrade/postgres_sensor_run.sh", - self.TEST_SENSOR_OUTPUT_DIR, - ], - self.TEST_TIMEOUT, - output_dir=self.TEST_SENSOR_OUTPUT_DIR, - ) - - self.run_with_graceful_kill( - ["tests/upgrade/postgres_run.sh", self.TEST_OUTPUT_DIR], - self.TEST_TIMEOUT, - output_dir=self.TEST_OUTPUT_DIR, - ) - - self.run_with_graceful_kill( - ["tests/upgrade/postgres_upgrade_run.sh", self.TEST_PG_UPGRADE_OUTPUT_DIR], - self.TEST_TIMEOUT, - output_dir=self.TEST_PG_UPGRADE_OUTPUT_DIR, - ) - - class OperatorE2eTest(BaseTest): OLM_SETUP_TIMEOUT_SEC = 60 * 2 TEST_TIMEOUT_SEC = 60 * 60 * 2 diff --git a/scripts/ci/jobs/gke_upgrade_tests.py b/scripts/ci/jobs/gke_upgrade_tests.py deleted file mode 100755 index cca9c5be4b540..0000000000000 --- a/scripts/ci/jobs/gke_upgrade_tests.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env -S python3 -u - -""" -Run the upgrade test in a GKE cluster -""" -import os -from runners import ClusterTestRunner -from clusters import GKECluster -from pre_tests import PreSystemTests -from ci_tests import UpgradeTest -from post_tests import PostClusterTest, FinalPost - -os.environ["ORCHESTRATOR_FLAVOR"] = "k8s" -os.environ["KUBERNETES_PROVIDER"] = "gke" -os.environ["SCANNER_V4_DB_STORAGE_CLASS"] = "faster" - -ClusterTestRunner( - cluster=GKECluster("upgrade-test", machine_type="e2-standard-8"), - pre_test=PreSystemTests(), - test=UpgradeTest(), - post_test=PostClusterTest(), - final_post=FinalPost( - store_qa_tests_data=True, - ), -).run() From f0e76ae2e73e9e20e9665a96712c0d7576599b19 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Mon, 7 Sep 2026 16:39:23 +0200 Subject: [PATCH 219/638] chore: install roxie for the emailsender-compat test (#22673) --- .../workflows/emailsender-central-compatibility.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/emailsender-central-compatibility.yaml b/.github/workflows/emailsender-central-compatibility.yaml index 92e4b7be8941b..7dd300125b10e 100644 --- a/.github/workflows/emailsender-central-compatibility.yaml +++ b/.github/workflows/emailsender-central-compatibility.yaml @@ -62,6 +62,16 @@ jobs: - uses: ./stackrox/.github/actions/job-preamble with: free-disk-space: 40 + + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat stackrox/ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + - name: Create Kind cluster uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # ratchet:helm/kind-action@v1 with: From c3dd816fe121989139991be5d8f2c5d7720e8cfd Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Mon, 7 Sep 2026 18:31:18 +0200 Subject: [PATCH 220/638] ROX-36684: pin Rekor key in factory_test to avoid live TUF fetch (#22619) Co-authored-by: Claude Opus 4.8 --- pkg/signatures/factory_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/signatures/factory_test.go b/pkg/signatures/factory_test.go index eb512c5e3fc59..4d7b9a2a0451a 100644 --- a/pkg/signatures/factory_test.go +++ b/pkg/signatures/factory_test.go @@ -14,6 +14,16 @@ import ( // Signatures have been generated via cosign: // $ cosign generate-key-pair // $ cosign sign -y ttl.sh/88795dd4-270c-4eb7-b3d4-50241d5bc04c@sha256:f2e98ad37e4970f48e85946972ac4acb5574c39f27c624efbd9b17a3a402bfe4 --key=cosign.key +// +// The same `cosign sign` invocation also uploads the signature to the public-good +// Sigstore Rekor transparency log (this is cosign's default behaviour). The resulting +// Rekor bundle (SignedEntryTimestamp + Payload) is what is stored verbatim in +// testdata/bundle_bench_test.json. Because that SET was signed by the production Rekor +// instance, offline verification must be given the matching production Rekor public key +// (testdata/rekor_pub.pem); otherwise verification falls back to fetching trusted keys +// from Sigstore's TUF repository over the network, which made this test non-hermetic in CI +// (ROX-36684). If the bundle is ever regenerated against a different Rekor instance, +// testdata/rekor_pub.pem must be updated to match. const ( // pemMatchingPubKey matches the b64Signature. pemMatchingPubKey = `-----BEGIN PUBLIC KEY----- @@ -43,6 +53,10 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWi3tSxvBH7S/WUmv408nKPxNSJx6 func TestVerifyAgainstSignatureIntegration(t *testing.T) { bundle, err := os.ReadFile("testdata/bundle_bench_test.json") require.NoError(t, err) + // Pinned production Rekor key matching the SET in bundle_bench_test.json; see the + // package-level comment above. Shared with cosign_sig_verifier_test.go (ROX-36683). + rekorPubKey, err := os.ReadFile("testdata/rekor_pub.pem") + require.NoError(t, err) testImg, err := generateImageWithCosignSignature(imgString, b64Signature, b64SignaturePayload, nil, nil, bundle) require.NoError(t, err, "creating test image") @@ -74,6 +88,7 @@ func TestVerifyAgainstSignatureIntegration(t *testing.T) { TransparencyLog: &storage.TransparencyLogVerification{ Enabled: true, ValidateOffline: true, + PublicKeyPemEnc: string(rekorPubKey), }, }, result: &storage.ImageSignatureVerificationResult{ From 11f7f05875e3f7a715c0446306383c03c5432078 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Mon, 7 Sep 2026 20:52:19 +0200 Subject: [PATCH 221/638] ROX-36497: retry execInDeployment kubectl exec on transient EOF (#22617) Co-authored-by: Claude Sonnet 4.5 --- tests/common.go | 55 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/tests/common.go b/tests/common.go index fe774a13c956f..54565e09ea603 100644 --- a/tests/common.go +++ b/tests/common.go @@ -836,30 +836,44 @@ func deleteNamespace(t *testing.T, name string) { // execInDeployment executes a command in a pod from the given deployment. // Assumes deployment pods have label app= (set by privilegedDeploymentSpec). +// +// The pod is re-resolved and `kubectl exec` is retried within a bounded window +// to tolerate transient exec failures (e.g. "error: EOF" from a dropped exec +// stream) shortly after the deployment has been rolled/restarted (see +// ROX-36497). func execInDeployment(t *testing.T, client kubernetes.Interface, deploymentName, namespace string, command ...string) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() - podList, err := client.CoreV1().Pods(namespace).List(ctx, metaV1.ListOptions{ - LabelSelector: fmt.Sprintf("app=%s", deploymentName), - }) - require.NoError(t, err, "listing pods for deployment %q", deploymentName) - require.NotEmpty(t, podList.Items, "no pods found for deployment %q", deploymentName) + var lastPod string + mustEventually(t, ctx, func() error { + podList, err := client.CoreV1().Pods(namespace).List(ctx, metaV1.ListOptions{ + LabelSelector: fmt.Sprintf("app=%s", deploymentName), + }) + if err != nil { + return fmt.Errorf("listing pods for deployment %q: %w", deploymentName, err) + } + if len(podList.Items) == 0 { + return fmt.Errorf("no pods found for deployment %q", deploymentName) + } - podName := podList.Items[0].Name + podName := podList.Items[0].Name + lastPod = podName - args := make([]string, 0, 5+len(command)) - args = append(args, "exec", "-n", namespace, podName, "--") - args = append(args, command...) + args := make([]string, 0, 5+len(command)) + args = append(args, "exec", "-n", namespace, podName, "--") + args = append(args, command...) - cmd := exec.CommandContext(ctx, "kubectl", args...) - output, err := cmd.CombinedOutput() - if err != nil { - t.Logf("kubectl exec output: %s", string(output)) - } - require.NoError(t, err, "executing command %v in pod %q", command, podName) + execCtx, execCancel := context.WithTimeout(ctx, 30*time.Second) + defer execCancel() + output, err := exec.CommandContext(execCtx, "kubectl", args...).CombinedOutput() + if err != nil { + return fmt.Errorf("executing command %v in pod %q: %w: %s", command, podName, err, string(output)) + } + return nil + }, 10*time.Second, "kubectl exec failed, retrying") - t.Logf("Executed command %v in pod %q (deployment %q)", command, podName, deploymentName) + t.Logf("Executed command %v in pod %q (deployment %q)", command, lastPod, deploymentName) } func waitForCondition(t testutils.T, condition func() bool, desc string, timeout time.Duration, frequency time.Duration) { @@ -1096,7 +1110,12 @@ func mustEventually(t *testing.T, ctx context.Context, f func() error, pauseInte retry.Tries(math.MaxInt), retry.WithContext(ctx), retry.BetweenAttempts(func(_ int) { - time.Sleep(pauseInterval) + timer := time.NewTimer(pauseInterval) + defer timer.Stop() + select { + case <-timer.C: + case <-ctx.Done(): + } }), retry.OnFailedAttempts(func(err error) { logf(t, failureMsgPrefix+": %s", err) }))) } From dbeeef332eeb7a717687ffa56328e1d3c87289fc Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Mon, 7 Sep 2026 21:58:22 +0200 Subject: [PATCH 222/638] ROX-36250: add node vulnerability report service (#22388) Signed-off-by: Tomasz Janiszewski Co-authored-by: Cursor Co-authored-by: Claude Sonnet 4.5 --- central/main.go | 10 +- central/reports/service/v2/service_impl.go | 19 +- tests/node_report_test.go | 647 +++++++++++++++++++++ 3 files changed, 662 insertions(+), 14 deletions(-) create mode 100644 tests/node_report_test.go diff --git a/central/main.go b/central/main.go index 3fd9cc1d8ac6d..6b375b4956f24 100755 --- a/central/main.go +++ b/central/main.go @@ -144,7 +144,7 @@ import ( rbacService "github.com/stackrox/rox/central/rbac/service" vulnReportV2Scheduler "github.com/stackrox/rox/central/reports/scheduler/v2" reportServiceV2 "github.com/stackrox/rox/central/reports/service/v2" - v2Service "github.com/stackrox/rox/central/reports/service/v2" + nodeReportServiceV2 "github.com/stackrox/rox/central/reports/service/v2/node" "github.com/stackrox/rox/central/reprocessor" collectionService "github.com/stackrox/rox/central/resourcecollection/service" "github.com/stackrox/rox/central/risk/handlers/timeline" @@ -493,6 +493,10 @@ func servicesToRegister() []pkgGRPC.APIService { servicesToRegister = append(servicesToRegister, reportServiceV2.Singleton()) + if features.NodeVulnerabilityReports.Enabled() { + servicesToRegister = append(servicesToRegister, nodeReportServiceV2.Singleton()) + } + if features.ComplianceEnhancements.Enabled() { servicesToRegister = append(servicesToRegister, complianceOperatorIntegrationService.Singleton()) servicesToRegister = append(servicesToRegister, complianceScanSettings.Singleton()) @@ -996,7 +1000,7 @@ func customRoutes() (customRoutes []routes.CustomRoute) { customRoutes = append(customRoutes, routes.CustomRoute{ Route: "/api/reports/jobs/download", Authorizer: user.With(permissions.View(resources.Image)), - ServerHandler: v2Service.NewDownloadHandler(), + ServerHandler: reportServiceV2.NewDownloadHandler(), Compression: true, }) @@ -1004,7 +1008,7 @@ func customRoutes() (customRoutes []routes.CustomRoute) { customRoutes = append(customRoutes, routes.CustomRoute{ Route: "/api/reports/node/jobs/download", Authorizer: user.With(permissions.View(resources.Node), permissions.View(resources.Cluster)), - ServerHandler: v2Service.NewNodeDownloadHandler(), + ServerHandler: reportServiceV2.NewNodeDownloadHandler(), Compression: true, }) } diff --git a/central/reports/service/v2/service_impl.go b/central/reports/service/v2/service_impl.go index bb1726d13400f..d4e3def92eb8f 100644 --- a/central/reports/service/v2/service_impl.go +++ b/central/reports/service/v2/service_impl.go @@ -178,10 +178,7 @@ func (s *serviceImpl) UpdateReportConfiguration(ctx context.Context, request *ap } } - var accessScopeRules []*storage.SimpleAccessScope_Rules - if filters := currentConfig.GetVulnReportFilters(); filters != nil { - accessScopeRules = filters.GetAccessScopeRules() - } + accessScopeRules := currentConfig.GetVulnReportFilters().GetAccessScopeRules() updatedConfig := s.convertV2ReportConfigurationToProto(request, currentConfig.GetCreator(), accessScopeRules) err = s.reportConfigStore.UpdateReportConfiguration(ctx, updatedConfig) @@ -701,13 +698,6 @@ func (s *serviceImpl) persistSnapshotAndNotify(ctx context.Context, reportReq *r return PersistSnapshotAndNotify(ctx, s.validator, s.db, reportReq, channel) } -func rejectNodeReportConfiguration(config *storage.ReportConfiguration) error { - if config.GetType() == storage.ReportConfiguration_NODE_VULNERABILITY { - return errox.InvalidArgs.Newf("report configuration '%s' is a node vulnerability report; use the node report service", config.GetId()) - } - return nil -} - func rejectNodeReportSnapshot(snapshot *storage.ReportSnapshot) error { if snapshot.GetType() == storage.ReportSnapshot_NODE_VULNERABILITY { return errox.InvalidArgs.Newf("report job '%s' is a node vulnerability report; use the node report service", snapshot.GetReportId()) @@ -735,3 +725,10 @@ func PersistSnapshotAndNotify(ctx context.Context, validator *validation.Validat NotifyWithRetry(ctx, db, channel, reportID) return reportID, nil } + +func rejectNodeReportConfiguration(config *storage.ReportConfiguration) error { + if config.GetType() == storage.ReportConfiguration_NODE_VULNERABILITY { + return errox.InvalidArgs.Newf("report configuration '%s' is a node vulnerability report; use the node report service", config.GetId()) + } + return nil +} diff --git a/tests/node_report_test.go b/tests/node_report_test.go new file mode 100644 index 0000000000000..df5421a76a7ac --- /dev/null +++ b/tests/node_report_test.go @@ -0,0 +1,647 @@ +//go:build test_e2e + +package tests + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + "testing" + "time" + + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/testutils/centralgrpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/protobuf/proto" +) + +func TestNodeReport(t *testing.T) { + suite.Run(t, new(NodeReportSuite)) +} + +type NodeReportSuite struct { + suite.Suite + + ctx context.Context + cancel context.CancelFunc + service apiV2.NodeReportServiceClient + imageService apiV2.ReportServiceClient + configIDs []string +} + +func (s *NodeReportSuite) SetupSuite() { + s.ctx, s.cancel = context.WithTimeout(context.Background(), 30*time.Minute) + conn := centralgrpc.GRPCConnectionToCentral(s.T()) + s.service = apiV2.NewNodeReportServiceClient(conn) + s.imageService = apiV2.NewReportServiceClient(conn) + s.waitForCentralReady() +} + +func (s *NodeReportSuite) waitForCentralReady() { + s.T().Log("Waiting for Central to be ready...") + s.Require().Eventually(func() bool { + ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + defer cancel() + _, err := s.service.CountNodeReportConfigurations(ctx, &apiV2.RawQuery{}) + return err == nil + }, 2*time.Minute, 2*time.Second, "Central did not become ready") +} + +func (s *NodeReportSuite) TearDownSuite() { + defer s.cancel() + for _, id := range s.configIDs { + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + _, err := s.service.DeleteNodeReportConfiguration(ctx, &apiV2.ResourceByID{Id: id}) + cancel() + if err != nil { + s.T().Logf("Failed to delete report config %s: %v", id, err) + } + } +} + +func (s *NodeReportSuite) newNodeReportConfig(name, query string) *apiV2.ReportConfiguration { + return &apiV2.ReportConfiguration{ + Name: name, + Description: "E2E test node vulnerability report", + Type: apiV2.ReportConfiguration_NODE_VULNERABILITY, + Filter: &apiV2.ReportConfiguration_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + Query: query, + }, + }, + ResourceScope: &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "remote", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + }, + } +} + +func (s *NodeReportSuite) TestCreateNodeReportConfig() { + config := s.newNodeReportConfig("e2e-node-report-create", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err, "creating node report config") + s.Require().NotEmpty(created.GetId()) + s.configIDs = append(s.configIDs, created.GetId()) + + assert.Equal(s.T(), config.GetName(), created.GetName()) + assert.Equal(s.T(), apiV2.ReportConfiguration_NODE_VULNERABILITY, created.GetType()) + + scope := created.GetResourceScope().GetEntityScope() + s.Require().NotNil(scope, "entity scope should be set on the created config") + s.Require().Len(scope.GetRules(), 1) + + rule := scope.GetRules()[0] + assert.Equal(s.T(), apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, rule.GetEntity()) + assert.Equal(s.T(), apiV2.ScopeField_FIELD_NAME, rule.GetField()) + s.Require().Len(rule.GetValues(), 1) + assert.Equal(s.T(), "remote", rule.GetValues()[0].GetValue()) + assert.Equal(s.T(), apiV2.MatchType_EXACT, rule.GetValues()[0].GetMatchType()) + + filters := created.GetNodeVulnReportFilters() + s.Require().NotNil(filters) + assert.Equal(s.T(), "CVSS:>=7", filters.GetQuery()) + assert.True(s.T(), filters.GetAllVuln()) + + getCtx, getCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer getCancel() + + fetched, err := s.service.GetNodeReportConfiguration(getCtx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().NoError(err, "fetching created report config") + assert.Equal(s.T(), created.GetId(), fetched.GetId()) + assert.Equal(s.T(), "CVSS:>=7", fetched.GetNodeVulnReportFilters().GetQuery()) +} + +func (s *NodeReportSuite) TestUpdateNodeReportConfig() { + config := s.newNodeReportConfig("e2e-node-report-update", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + updated, ok := proto.Clone(created).(*apiV2.ReportConfiguration) + s.Require().True(ok) + updated.Id = created.GetId() + updated.Description = "Updated description" + updated.GetNodeVulnReportFilters().Query = "Severity:CRITICAL" + updated.ResourceScope = &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "prod-.*", MatchType: apiV2.MatchType_REGEX}, + }, + }, + }, + }, + }, + } + + updateCtx, updateCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer updateCancel() + + _, err = s.service.UpdateNodeReportConfiguration(updateCtx, updated) + s.Require().NoError(err, "updating node report config") + + getCtx, getCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer getCancel() + + fetched, err := s.service.GetNodeReportConfiguration(getCtx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().NoError(err) + + assert.Equal(s.T(), "Updated description", fetched.GetDescription()) + assert.Equal(s.T(), "Severity:CRITICAL", fetched.GetNodeVulnReportFilters().GetQuery()) + + fetchedScope := fetched.GetResourceScope().GetEntityScope() + s.Require().NotNil(fetchedScope) + s.Require().Len(fetchedScope.GetRules(), 1) + + fetchedRule := fetchedScope.GetRules()[0] + assert.Equal(s.T(), apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, fetchedRule.GetEntity()) + assert.Equal(s.T(), apiV2.ScopeField_FIELD_NAME, fetchedRule.GetField()) + s.Require().Len(fetchedRule.GetValues(), 1) + assert.Equal(s.T(), "prod-.*", fetchedRule.GetValues()[0].GetValue()) + assert.Equal(s.T(), apiV2.MatchType_REGEX, fetchedRule.GetValues()[0].GetMatchType()) +} + +func (s *NodeReportSuite) TestListAndCountNodeReportConfigs() { + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + config1, err := s.service.PostNodeReportConfiguration(ctx, s.newNodeReportConfig("e2e-node-list-1", "CVSS:>=7")) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, config1.GetId()) + + config2, err := s.service.PostNodeReportConfiguration(ctx, s.newNodeReportConfig("e2e-node-list-2", "CVSS:>=8")) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, config2.GetId()) + + config3, err := s.service.PostNodeReportConfiguration(ctx, s.newNodeReportConfig("e2e-node-list-3", "CVSS:>=9")) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, config3.GetId()) + + listCtx, listCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer listCancel() + + listResp, err := s.service.ListNodeReportConfigurations(listCtx, &apiV2.RawQuery{}) + s.Require().NoError(err) + s.Require().GreaterOrEqual(len(listResp.GetReportConfigs()), 3, "should have at least 3 configs") + + countCtx, countCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer countCancel() + + countResp, err := s.service.CountNodeReportConfigurations(countCtx, &apiV2.RawQuery{}) + s.Require().NoError(err) + s.Require().GreaterOrEqual(countResp.GetCount(), int32(3), "count should be at least 3") + + for _, config := range listResp.GetReportConfigs() { + assert.Equal(s.T(), apiV2.ReportConfiguration_NODE_VULNERABILITY, config.GetType(), + "list should only return NODE_VULNERABILITY type configs") + } +} + +func (s *NodeReportSuite) TestImageServiceDoesNotExposeNodeConfigs() { + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, s.newNodeReportConfig("e2e-node-not-in-image-list", "CVSS:>=7")) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + _, err = s.imageService.GetReportConfiguration(ctx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().Error(err, "image report service must not return node configurations") + + listResp, err := s.imageService.ListReportConfigurations(ctx, &apiV2.RawQuery{}) + s.Require().NoError(err) + for _, cfg := range listResp.GetReportConfigs() { + s.NotEqual(created.GetId(), cfg.GetId()) + s.NotEqual(apiV2.ReportConfiguration_NODE_VULNERABILITY, cfg.GetType()) + } +} + +func (s *NodeReportSuite) TestViewBasedNodeReport() { + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + runResp, err := s.service.PostViewBasedNodeReport(ctx, &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + AreaOfConcern: "e2e-node-view-based", + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "CVSS:>=7", + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + }, + }) + s.Require().NoError(err) + s.Require().NotEmpty(runResp.GetReportID()) + + histResp, err := s.service.GetViewBasedMyNodeReportHistory(ctx, &apiV2.GetViewBasedReportHistoryRequest{}) + s.Require().NoError(err) + found := false + for _, snap := range histResp.GetReportSnapshots() { + if snap.GetReportJobId() == runResp.GetReportID() { + found = true + s.Equal(apiV2.ReportSnapshot_NODE_VULNERABILITY, snap.GetType()) + break + } + } + s.True(found, "view-based history should include the submitted job") +} + +func (s *NodeReportSuite) TestRunAndDownloadNodeReport() { + config := s.newNodeReportConfig("e2e-node-report-run", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + runResp, err := s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err, "submitting node report run request") + s.Require().NotEmpty(runResp.GetReportId()) + + reportID := runResp.GetReportId() + s.T().Logf("Node report job submitted: config=%s report=%s", created.GetId(), reportID) + + s.waitForReportCompletion(reportID) + s.downloadReport(reportID) +} + +func (s *NodeReportSuite) TestNodeReportHistory() { + config := s.newNodeReportConfig("e2e-node-report-history", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + runResp, err := s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err) + + s.waitForReportCompletion(runResp.GetReportId()) + + histCtx, histCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer histCancel() + + histResp, err := s.service.GetNodeReportHistory(histCtx, &apiV2.GetReportHistoryRequest{ + Id: created.GetId(), + }) + s.Require().NoError(err) + s.Require().NotEmpty(histResp.GetReportSnapshots(), "should have at least one snapshot in history") + + snapshot := histResp.GetReportSnapshots()[0] + assert.Equal(s.T(), created.GetId(), snapshot.GetReportConfigId()) + + snapshotScope := snapshot.GetResourceScope() + s.Require().NotNil(snapshotScope, "snapshot should have resource scope") + entityScope := snapshotScope.GetEntityScope() + s.Require().NotNil(entityScope, "snapshot should have entity scope") + assert.NotEmpty(s.T(), entityScope.GetRules()) + + snapshotFilters := snapshot.GetNodeVulnReportFilters() + s.Require().NotNil(snapshotFilters, "snapshot should have node vuln report filters") + assert.Equal(s.T(), "CVSS:>=7", snapshotFilters.GetQuery()) +} + +func (s *NodeReportSuite) TestMyNodeReportHistory() { + config := s.newNodeReportConfig("e2e-node-report-my-history", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + runResp, err := s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err) + + s.waitForReportCompletion(runResp.GetReportId()) + + histCtx, histCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer histCancel() + + histResp, err := s.service.GetMyNodeReportHistory(histCtx, &apiV2.GetReportHistoryRequest{ + Id: created.GetId(), + }) + s.Require().NoError(err) + s.Require().NotEmpty(histResp.GetReportSnapshots(), "should have at least one snapshot in user history") + + snapshot := histResp.GetReportSnapshots()[0] + assert.Equal(s.T(), created.GetId(), snapshot.GetReportConfigId()) + s.Require().NotNil(snapshot.GetUser(), "snapshot should have user information") +} + +func (s *NodeReportSuite) TestInvalidEntityScope_NonClusterType() { + config := s.newNodeReportConfig("e2e-node-invalid-entity", "CVSS:>=7") + config.ResourceScope = &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_DEPLOYMENT, + Field: apiV2.ScopeField_FIELD_NAME, + Values: []*apiV2.RuleValue{ + {Value: "test", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + } + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + _, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().Error(err, "should reject config with deployment entity type") +} + +func (s *NodeReportSuite) TestInvalidEntityScope_UnsupportedField() { + config := s.newNodeReportConfig("e2e-node-invalid-field", "CVSS:>=7") + config.ResourceScope = &apiV2.ResourceScope{ + ScopeReference: &apiV2.ResourceScope_EntityScope{ + EntityScope: &apiV2.EntityScope{ + Rules: []*apiV2.EntityScopeRule{ + { + Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + Field: apiV2.ScopeField_FIELD_ANNOTATION, + Values: []*apiV2.RuleValue{ + {Value: "key=value", MatchType: apiV2.MatchType_EXACT}, + }, + }, + }, + }, + }, + } + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + _, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().Error(err, "should reject config with annotation field") +} + +func (s *NodeReportSuite) TestDeleteNodeReportConfig() { + config := s.newNodeReportConfig("e2e-node-delete", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + + deleteCtx, deleteCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer deleteCancel() + + _, err = s.service.DeleteNodeReportConfiguration(deleteCtx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().NoError(err, "deleting node report config") + + getCtx, getCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer getCancel() + + _, err = s.service.GetNodeReportConfiguration(getCtx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().Error(err, "should return error when getting deleted config") +} + +func (s *NodeReportSuite) TestDeleteNodeReportConfig_WithRunningJob() { + config := s.newNodeReportConfig("e2e-node-delete-running", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + _, err = s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err) + + deleteCtx, deleteCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer deleteCancel() + + _, err = s.service.DeleteNodeReportConfiguration(deleteCtx, &apiV2.ResourceByID{Id: created.GetId()}) + if err == nil { + s.T().Log("job finished before delete; config was removable") + return + } + s.Error(err, "delete must fail while a job is still running") +} + +func (s *NodeReportSuite) TestCancelNodeReport() { + config := s.newNodeReportConfig("e2e-node-cancel", "CVSS:>=7") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + runResp, err := s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err) + + cancelCtx, cancelCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancelCancel() + + _, err = s.service.CancelNodeReport(cancelCtx, &apiV2.ResourceByID{Id: runResp.GetReportId()}) + if err != nil { + s.T().Logf("Cancel returned error (job may have already finished): %v", err) + } + + statusCtx, statusCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer statusCancel() + + statusResp, err := s.service.GetNodeReportStatus(statusCtx, &apiV2.ResourceByID{Id: runResp.GetReportId()}) + if err != nil { + s.T().Logf("Report not found after cancel: %v", err) + return + } + + reportStatus := statusResp.GetStatus() + s.T().Logf("Report state after cancel: %s", reportStatus.GetRunState()) + switch reportStatus.GetRunState() { + case apiV2.ReportStatus_FAILURE: + s.Equal("report cancelled by user", reportStatus.GetErrorMsg(), "cancelled report should have cancellation message") + case apiV2.ReportStatus_GENERATED, apiV2.ReportStatus_DELIVERED: + s.T().Log("report completed before cancel took effect") + default: + s.Failf("unexpected report state after cancel", "got %s: %s", reportStatus.GetRunState(), reportStatus.GetErrorMsg()) + } +} + +func (s *NodeReportSuite) TestQueryFiltering() { + config := s.newNodeReportConfig("e2e-node-query-filter", "CVE:CVE-2024-*+CVSS:>=9") + + ctx, cancel := context.WithTimeout(s.ctx, 30*time.Second) + defer cancel() + + created, err := s.service.PostNodeReportConfiguration(ctx, config) + s.Require().NoError(err) + s.configIDs = append(s.configIDs, created.GetId()) + + runCtx, runCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer runCancel() + + runResp, err := s.service.RunNodeReport(runCtx, &apiV2.RunReportRequest{ + ReportConfigId: created.GetId(), + ReportNotificationMethod: apiV2.NotificationMethod_DOWNLOAD, + }) + s.Require().NoError(err) + + s.waitForReportCompletion(runResp.GetReportId()) + + getCtx, getCancel := context.WithTimeout(s.ctx, 30*time.Second) + defer getCancel() + + fetched, err := s.service.GetNodeReportConfiguration(getCtx, &apiV2.ResourceByID{Id: created.GetId()}) + s.Require().NoError(err) + assert.Equal(s.T(), "CVE:CVE-2024-*+CVSS:>=9", fetched.GetNodeVulnReportFilters().GetQuery(), + "query filter should be preserved") +} + +func (s *NodeReportSuite) waitForReportCompletion(reportID string) { + s.T().Logf("Waiting for node report %s to complete...", reportID) + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + timer := time.NewTimer(5 * time.Minute) + defer timer.Stop() + + for { + select { + case <-ticker.C: + ctx, cancel := context.WithTimeout(s.ctx, 10*time.Second) + statusResp, err := s.service.GetNodeReportStatus(ctx, &apiV2.ResourceByID{Id: reportID}) + cancel() + if err != nil { + s.T().Logf("Error checking report status: %v", err) + continue + } + + state := statusResp.GetStatus().GetRunState() + s.T().Logf("Node report %s state: %s", reportID, state) + + switch state { + case apiV2.ReportStatus_GENERATED, apiV2.ReportStatus_DELIVERED: + s.T().Logf("Node report %s completed successfully", reportID) + return + case apiV2.ReportStatus_FAILURE: + s.Require().Failf("Node report generation failed", + "Report %s failed: %s", reportID, statusResp.GetStatus().GetErrorMsg()) + return + } + case <-timer.C: + s.Require().Failf("Timed out", "Node report %s did not complete within 5 minutes", reportID) + return + } + } +} + +func (s *NodeReportSuite) downloadReport(reportID string) { + endpoint := centralgrpc.RoxAPIEndpoint(s.T()) + password := centralgrpc.RoxPassword(s.T()) + username := centralgrpc.RoxUsername(s.T()) + + downloadURL := fmt.Sprintf("https://%s/api/reports/node/jobs/download?id=%s", endpoint, reportID) + s.T().Logf("Downloading node report from %s", downloadURL) + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + Timeout: 60 * time.Second, + } + + var resp *http.Response + s.Require().Eventually(func() bool { + req, err := http.NewRequestWithContext(s.ctx, http.MethodGet, downloadURL, nil) + s.Require().NoError(err) + req.SetBasicAuth(username, password) + + newResp, err := client.Do(req) + if err != nil { + s.T().Logf("Download attempt failed: %v", err) + return false + } + if newResp.StatusCode != http.StatusOK { + s.T().Logf("Download attempt returned %d", newResp.StatusCode) + _ = newResp.Body.Close() + return false + } + resp = newResp + return true + }, 2*time.Minute, 5*time.Second, "failed to download node report after retries") + defer func() { _ = resp.Body.Close() }() + + body, err := io.ReadAll(resp.Body) + s.Require().NoError(err) + + require.Equal(s.T(), http.StatusOK, resp.StatusCode, + "expected 200 OK downloading node report, got %d: %s", resp.StatusCode, string(body)) + assert.Equal(s.T(), "application/zip", resp.Header.Get("Content-Type")) + assert.NotEmpty(s.T(), body, "downloaded node report should not be empty") + + s.T().Logf("Downloaded node report: %d bytes, Content-Type: %s", len(body), resp.Header.Get("Content-Type")) +} From 0b09aa918bfd82b7583db77ae032650e1ef6b8c4 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Tue, 8 Sep 2026 01:36:26 +0200 Subject: [PATCH 223/638] ROX-36605: make image signature verification timeout configurable (#22618) --- pkg/env/signature.go | 9 ++++ pkg/images/enricher/enricher_impl.go | 10 +++-- pkg/images/enricher/enricher_impl_test.go | 41 +++++++++++++++++ pkg/images/enricher/enricher_v2_impl.go | 10 +++-- pkg/images/enricher/enricher_v2_impl_test.go | 46 ++++++++++++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) diff --git a/pkg/env/signature.go b/pkg/env/signature.go index cee8747a1db0c..4f3d498686d8b 100644 --- a/pkg/env/signature.go +++ b/pkg/env/signature.go @@ -17,4 +17,13 @@ var ( // RedHatSigningKeyWatchInterval controls how often the watcher polls the key bundle file. // Set to 0 to disable the watcher entirely. RedHatSigningKeyWatchInterval = registerDurationSetting("ROX_REDHAT_SIGNING_KEY_WATCH_INTERVAL", 4*time.Hour, WithDurationZeroAllowed()) + + // ImageSignatureVerificationTimeout bounds how long the enricher waits for signature verification + // of a single image against all configured signature integrations. + // Keyless (cosign) verification performs remote RPCs (fetching the Sigstore TUF trust root and + // Fulcio/Rekor material, and querying the transparency log), which can take several seconds, + // especially on the first call before the TUF root is cached or when the Sigstore CDN is slow. + // The default is generous enough to accommodate keyless verification while still bounding the + // enrichment hot path. + ImageSignatureVerificationTimeout = registerDurationSetting("ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT", 30*time.Second) ) diff --git a/pkg/images/enricher/enricher_impl.go b/pkg/images/enricher/enricher_impl.go index 3356441902d7e..925955b8fc6ee 100644 --- a/pkg/images/enricher/enricher_impl.go +++ b/pkg/images/enricher/enricher_impl.go @@ -814,10 +814,12 @@ func (e *enricherImpl) enrichWithSignatureVerificationData(ctx context.Context, return false, nil } - // Timeout is based on benchmark test result for 200 integrations with 1 config each (roughly 0.1 sec) + a grace - // timeout on top. Currently, signature verification is done without remote RPCs, this will need to be - // changed accordingly when RPCs are required (i.e. cosign keyless). - verifySignatureCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + // Bound the time spent verifying signatures for a single image against all configured integrations. + // Public-key verification is local and fast, but keyless (cosign) verification performs remote RPCs + // (Sigstore TUF trust root, Fulcio/Rekor material, transparency log lookups) and can take several + // seconds. The timeout is configurable via ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT so it can be + // tuned for slow Sigstore infrastructure without silently marking valid signatures as unverified. + verifySignatureCtx, cancel := context.WithTimeout(ctx, env.ImageSignatureVerificationTimeout.DurationSetting()) defer cancel() res := e.signatureVerifier(verifySignatureCtx, sigIntegrations, img) diff --git a/pkg/images/enricher/enricher_impl_test.go b/pkg/images/enricher/enricher_impl_test.go index fafafab6b74a7..072e106e51fed 100644 --- a/pkg/images/enricher/enricher_impl_test.go +++ b/pkg/images/enricher/enricher_impl_test.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/generated/storage" delegatorMocks "github.com/stackrox/rox/pkg/delegatedregistry/mocks" + "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/images/integration" @@ -1048,6 +1049,46 @@ func TestEnrichWithSignatureVerificationData_Failure(t *testing.T) { assert.False(t, updated) } +// TestEnrichWithSignatureVerificationData_Timeout verifies that the signature verification +// context is bounded by ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT. This guards against +// regressing to a timeout too short for keyless (remote-RPC) verification, which caused valid +// keyless Sigstore signatures to be marked unverified (ROX-36605). +func TestEnrichWithSignatureVerificationData_Timeout(t *testing.T) { + // Use an arbitrary, non-default value so the assertion proves the deadline is driven by the + // env var rather than by the 30s default (or the old hardcoded 1s). + const timeout = 17 * time.Second + t.Setenv(env.ImageSignatureVerificationTimeout.EnvVar(), timeout.String()) + + var deadline time.Time + haveDeadline := false + e := enricherImpl{ + signatureIntegrationGetter: fakeSignatureIntegrationGetter("verifier1", false), + signatureVerifier: func(ctx context.Context, _ []*storage.SignatureIntegration, + _ *storage.Image) []*storage.ImageSignatureVerificationResult { + deadline, haveDeadline = ctx.Deadline() + return []*storage.ImageSignatureVerificationResult{ + createSignatureVerificationResult("verifier1", + storage.ImageSignatureVerificationResult_VERIFIED, "test:1.0"), + } + }, + } + img := &storage.Image{Id: "id", Name: &storage.ImageName{FullName: "test:1.0"}, + Signature: &storage.ImageSignature{Signatures: []*storage.Signature{createSignature("sig1", "payload1")}}} + + // The verifier is a stub that returns immediately, so this call does not wait for the timeout; + // it only checks the deadline the enricher put on the verification context. + start := time.Now() + updated, err := e.enrichWithSignatureVerificationData(emptyCtx, + EnrichmentContext{FetchOpt: ForceRefetch}, img) + require.NoError(t, err) + assert.True(t, updated) + require.True(t, haveDeadline, "verification context should carry a deadline") + // The deadline must be start+timeout. A 1s tolerance absorbs the negligible work between + // capturing start and creating the context; it is unrelated to the old hardcoded 1s timeout. + assert.WithinDuration(t, start.Add(timeout), deadline, time.Second, + "verification deadline should reflect ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT") +} + func TestDelegateEnrichImage(t *testing.T) { deleEnrichCtx := EnrichmentContext{Delegable: true} e := enricherImpl{ diff --git a/pkg/images/enricher/enricher_v2_impl.go b/pkg/images/enricher/enricher_v2_impl.go index 38c0d5a0b052f..68bf9ab291614 100644 --- a/pkg/images/enricher/enricher_v2_impl.go +++ b/pkg/images/enricher/enricher_v2_impl.go @@ -794,10 +794,12 @@ func (e *enricherV2Impl) enrichWithSignatureVerificationData(ctx context.Context return false, nil } - // Timeout is based on benchmark test result for 200 integrations with 1 config each (roughly 0.1 sec) + a grace - // timeout on top. Currently, signature verification is done without remote RPCs, this will need to be - // changed accordingly when RPCs are required (i.e. cosign keyless). - verifySignatureCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + // Bound the time spent verifying signatures for a single image against all configured integrations. + // Public-key verification is local and fast, but keyless (cosign) verification performs remote RPCs + // (Sigstore TUF trust root, Fulcio/Rekor material, transparency log lookups) and can take several + // seconds. The timeout is configurable via ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT so it can be + // tuned for slow Sigstore infrastructure without silently marking valid signatures as unverified. + verifySignatureCtx, cancel := context.WithTimeout(ctx, env.ImageSignatureVerificationTimeout.DurationSetting()) defer cancel() res := e.signatureVerifier(verifySignatureCtx, sigIntegrations, utils.ConvertToV1(imgV2)) diff --git a/pkg/images/enricher/enricher_v2_impl_test.go b/pkg/images/enricher/enricher_v2_impl_test.go index 99c7ff194864b..aaf767f3203cc 100644 --- a/pkg/images/enricher/enricher_v2_impl_test.go +++ b/pkg/images/enricher/enricher_v2_impl_test.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/generated/storage" delegatorMocks "github.com/stackrox/rox/pkg/delegatedregistry/mocks" + "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/images/integration" @@ -1071,6 +1072,51 @@ func TestEnrichWithSignatureVerificationDataV2_Failure(t *testing.T) { assert.False(t, updated) } +// TestEnrichWithSignatureVerificationDataV2_Timeout verifies that the signature verification +// context is bounded by ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT for the V2 (flattened image +// data) path. This guards against regressing to a timeout too short for keyless (remote-RPC) +// verification, which caused valid keyless Sigstore signatures to be marked unverified +// (ROX-36605). +func TestEnrichWithSignatureVerificationDataV2_Timeout(t *testing.T) { + testutils.MustUpdateFeature(t, features.FlattenImageData, true) + // Use an arbitrary, non-default value so the assertion proves the deadline is driven by the + // env var rather than by the 30s default (or the old hardcoded 1s). + const timeout = 17 * time.Second + t.Setenv(env.ImageSignatureVerificationTimeout.EnvVar(), timeout.String()) + + var deadline time.Time + haveDeadline := false + e := enricherV2Impl{ + signatureIntegrationGetter: fakeSignatureIntegrationGetter("verifier1", false), + signatureVerifier: func(ctx context.Context, _ []*storage.SignatureIntegration, + _ *storage.Image) []*storage.ImageSignatureVerificationResult { + deadline, haveDeadline = ctx.Deadline() + return []*storage.ImageSignatureVerificationResult{ + createSignatureVerificationResult("verifier1", + storage.ImageSignatureVerificationResult_VERIFIED, "test:1.0"), + } + }, + } + img := &storage.ImageV2{ + Id: utils.NewImageV2ID(&storage.ImageName{FullName: "test:1.0"}, "sha"), + Digest: "sha", + Name: &storage.ImageName{FullName: "test:1.0"}, + Signature: &storage.ImageSignature{Signatures: []*storage.Signature{createSignature("sig1", "payload1")}}} + + // The verifier is a stub that returns immediately, so this call does not wait for the timeout; + // it only checks the deadline the enricher put on the verification context. + start := time.Now() + updated, err := e.enrichWithSignatureVerificationData(emptyCtx, + EnrichmentContext{FetchOpt: ForceRefetch}, img) + require.NoError(t, err) + assert.True(t, updated) + require.True(t, haveDeadline, "verification context should carry a deadline") + // The deadline must be start+timeout. A 1s tolerance absorbs the negligible work between + // capturing start and creating the context; it is unrelated to the old hardcoded 1s timeout. + assert.WithinDuration(t, start.Add(timeout), deadline, time.Second, + "verification deadline should reflect ROX_IMAGE_SIGNATURE_VERIFICATION_TIMEOUT") +} + func TestDelegateEnrichImageV2(t *testing.T) { testutils.MustUpdateFeature(t, features.FlattenImageData, true) deleEnrichCtx := EnrichmentContext{Delegable: true} From bd4226928bf2b22f1cdba1c7bc951722f2f7e123 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 04:11:29 +0000 Subject: [PATCH 224/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to c93950f (#22687) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index ccfe1f830f048..6960cb915120c 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 2b65bbbe65c15..bd2719e0e832e 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index d3644d58d188c..5935ce132d0c1 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index eda4361a298bf..f46e834fb8197 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 520591a80ec06..0c40d2393fb07 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 42eb6058aa788..397ab1935b203 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 701183c317080..d08faf87dea1a 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 8975fa48da6a1..9668d02c5bf1e 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:f2820c37719f1ac6b7102b649a214671ae656d9c9203be963ebb0e6eb07fd1dd + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db - name: kind value: task resolver: bundles From d981f2a4c2f229ec09dc10affc860e436bc12d32 Mon Sep 17 00:00:00 2001 From: Charmik Sheth <101146970+charmik-redhat@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:33:28 -0700 Subject: [PATCH 225/638] ROX-32438: Prune V1 images with enriched V2 twins (#21602) Co-authored-by: Claude Opus 4.6 --- central/deployment/datastore/datastore.go | 2 + .../deployment/datastore/datastore_impl.go | 5 + .../datastore/internal/store/mocks/store.go | 15 ++ .../internal/store/postgres/full_store.go | 31 +++ .../store/postgres/full_store_test.go | 75 ++++++ .../datastore/internal/store/store.go | 2 + .../deployment/datastore/mocks/datastore.go | 15 ++ central/image/datastore/datastore.go | 1 + central/image/datastore/datastore_impl.go | 4 + central/image/datastore/mocks/datastore.go | 14 ++ .../mapper/datastore/datastore_impl.go | 4 + central/pruning/image_pruning_bench_test.go | 2 +- central/pruning/pruning.go | 217 ++++++++++++++++++ central/pruning/pruning_test.go | 214 ++++++++++++++++- central/pruning/singleton.go | 2 + pkg/env/vulnerability_management.go | 11 + tools/allowed-large-files | 1 + 17 files changed, 607 insertions(+), 8 deletions(-) diff --git a/central/deployment/datastore/datastore.go b/central/deployment/datastore/datastore.go index b8daec8bdf512..67210757b8e23 100644 --- a/central/deployment/datastore/datastore.go +++ b/central/deployment/datastore/datastore.go @@ -48,6 +48,8 @@ type DataStore interface { WalkByQuery(ctx context.Context, query *v1.Query, fn func(deployment *storage.Deployment) error) error GetContainerImageViews(ctx context.Context, q *v1.Query) ([]*views.ContainerImageView, error) + + GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) } func newDataStore( diff --git a/central/deployment/datastore/datastore_impl.go b/central/deployment/datastore/datastore_impl.go index 432fd2e55d4b3..196a6af2b98fa 100644 --- a/central/deployment/datastore/datastore_impl.go +++ b/central/deployment/datastore/datastore_impl.go @@ -488,6 +488,11 @@ func (ds *datastoreImpl) GetContainerImageViews(ctx context.Context, q *v1.Query return ds.deploymentStore.GetContainerImageViews(ctx, q) } +func (ds *datastoreImpl) GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) { + defer metrics.SetDatastoreFunctionDuration(time.Now(), "Deployment", "GetV2ImageIDsForDigests") + return ds.deploymentStore.GetV2ImageIDsForDigests(ctx, digests) +} + type DeploymentSearchResultConverter struct{} func (c *DeploymentSearchResultConverter) BuildName(result *pkgSearch.Result) string { diff --git a/central/deployment/datastore/internal/store/mocks/store.go b/central/deployment/datastore/internal/store/mocks/store.go index e5fd8e0f6b138..455b7c14e2d08 100644 --- a/central/deployment/datastore/internal/store/mocks/store.go +++ b/central/deployment/datastore/internal/store/mocks/store.go @@ -172,6 +172,21 @@ func (mr *MockStoreMockRecorder) GetManyListDeployments(ctx any, ids ...any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManyListDeployments", reflect.TypeOf((*MockStore)(nil).GetManyListDeployments), varargs...) } +// GetV2ImageIDsForDigests mocks base method. +func (m *MockStore) GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetV2ImageIDsForDigests", ctx, digests) + ret0, _ := ret[0].(map[string][]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetV2ImageIDsForDigests indicates an expected call of GetV2ImageIDsForDigests. +func (mr *MockStoreMockRecorder) GetV2ImageIDsForDigests(ctx, digests any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetV2ImageIDsForDigests", reflect.TypeOf((*MockStore)(nil).GetV2ImageIDsForDigests), ctx, digests) +} + // Search mocks base method. func (m *MockStore) Search(ctx context.Context, q *v1.Query) ([]search.Result, error) { m.ctrl.T.Helper() diff --git a/central/deployment/datastore/internal/store/postgres/full_store.go b/central/deployment/datastore/internal/store/postgres/full_store.go index b59cfa667bed6..a5cc1f314be26 100644 --- a/central/deployment/datastore/internal/store/postgres/full_store.go +++ b/central/deployment/datastore/internal/store/postgres/full_store.go @@ -162,6 +162,37 @@ func (f *fullStoreImpl) GetContainerImageViews(ctx context.Context, q *v1.Query) return results, nil } +type v2ImageIDsByDigestView struct { + ImageIDs []string `db:"image_id"` + ImageDigest string `db:"image_sha"` +} + +func (f *fullStoreImpl) GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) { + if len(digests) == 0 { + return nil, nil + } + q := pkgSearch.NewQueryBuilder(). + AddExactMatches(pkgSearch.ImageSHA, digests...). + ProtoQuery() + q.Selects = []*v1.QuerySelect{ + pkgSearch.NewQuerySelect(pkgSearch.ImageID).Distinct().Proto(), + pkgSearch.NewQuerySelect(pkgSearch.ImageSHA).Proto(), + } + q.GroupBy = &v1.QueryGroupBy{ + Fields: []string{pkgSearch.ImageSHA.String()}, + } + + queryCtx, cancel := contextutil.ContextWithTimeoutIfNotExists(ctx, queryTimeout) + defer cancel() + + result := make(map[string][]string, len(digests)) + err := pgSearch.RunSelectRequestForSchemaFn(queryCtx, f.db, pkgSchema.DeploymentsSchema, q, func(row *v2ImageIDsByDigestView) error { + result[row.ImageDigest] = row.ImageIDs + return nil + }) + return result, err +} + // NewFullTestStore is used for testing. func NewFullTestStore(ctx context.Context, _ testing.TB, store Store, db postgres.DB, gormDB *gorm.DB) store.Store { pkgSchema.ApplySchemaForTable(ctx, gormDB, baseTable) diff --git a/central/deployment/datastore/internal/store/postgres/full_store_test.go b/central/deployment/datastore/internal/store/postgres/full_store_test.go index d57a623f6e522..5f794b14ff714 100644 --- a/central/deployment/datastore/internal/store/postgres/full_store_test.go +++ b/central/deployment/datastore/internal/store/postgres/full_store_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/images/types" + "github.com/stackrox/rox/pkg/images/utils" "github.com/stackrox/rox/pkg/postgres/pgtest" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/testutils" @@ -173,3 +175,76 @@ func TestGetManyListDeployments(t *testing.T) { assert.Equal(t, dep1.GetNamespace(), result.GetNamespace()) }) } + +func TestGetV2ImageIDsForDigests(t *testing.T) { + ctx := sac.WithAllAccess(context.Background()) + testDB := pgtest.ForT(t) + fullStore := NewFullStore(testDB.DB) + + digest1 := types.NewDigest("digest-1").Digest() + digest2 := types.NewDigest("digest-2").Digest() + digest3 := types.NewDigest("digest-3").Digest() + + name1a := &storage.ImageName{FullName: "registry.io/repo/image1a:latest"} + name1b := &storage.ImageName{FullName: "registry.io/repo/image1b:latest"} + name2 := &storage.ImageName{FullName: "registry.io/repo/image2:latest"} + + v2ID1a := utils.NewImageV2ID(name1a, digest1) + v2ID1b := utils.NewImageV2ID(name1b, digest1) + v2ID2 := utils.NewImageV2ID(name2, digest2) + + // Two deployments reference digest1 with different V2 IDs (different image names). + dep1 := &storage.Deployment{ + Id: uuid.NewV4().String(), Name: "dep-1", Namespace: "ns", + ClusterId: "c1", ClusterName: "c1", NamespaceId: "c1ns", + Containers: []*storage.Container{{ + Image: &storage.ContainerImage{Id: digest1, IdV2: v2ID1a, Name: name1a}, + }}, + } + dep2 := &storage.Deployment{ + Id: uuid.NewV4().String(), Name: "dep-2", Namespace: "ns", + ClusterId: "c1", ClusterName: "c1", NamespaceId: "c1ns", + Containers: []*storage.Container{{ + Image: &storage.ContainerImage{Id: digest1, IdV2: v2ID1b, Name: name1b}, + }}, + } + // One deployment references digest2. + dep3 := &storage.Deployment{ + Id: uuid.NewV4().String(), Name: "dep-3", Namespace: "ns", + ClusterId: "c1", ClusterName: "c1", NamespaceId: "c1ns", + Containers: []*storage.Container{{ + Image: &storage.ContainerImage{Id: digest2, IdV2: v2ID2, Name: name2}, + }}, + } + + require.NoError(t, fullStore.Upsert(ctx, dep1)) + require.NoError(t, fullStore.Upsert(ctx, dep2)) + require.NoError(t, fullStore.Upsert(ctx, dep3)) + + t.Run("multiple digests with multiple V2 IDs", func(t *testing.T) { + result, err := fullStore.GetV2ImageIDsForDigests(ctx, []string{digest1, digest2}) + assert.NoError(t, err) + assert.Len(t, result, 2) + assert.ElementsMatch(t, []string{v2ID1a, v2ID1b}, result[digest1]) + assert.ElementsMatch(t, []string{v2ID2}, result[digest2]) + }) + + t.Run("digest not in any deployment", func(t *testing.T) { + result, err := fullStore.GetV2ImageIDsForDigests(ctx, []string{digest3}) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("empty input", func(t *testing.T) { + result, err := fullStore.GetV2ImageIDsForDigests(ctx, nil) + assert.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("mix of existing and non-existing digests", func(t *testing.T) { + result, err := fullStore.GetV2ImageIDsForDigests(ctx, []string{digest1, digest3}) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.ElementsMatch(t, []string{v2ID1a, v2ID1b}, result[digest1]) + }) +} diff --git a/central/deployment/datastore/internal/store/store.go b/central/deployment/datastore/internal/store/store.go index 0fbd7c80edfe4..19a7d3ea50ca5 100644 --- a/central/deployment/datastore/internal/store/store.go +++ b/central/deployment/datastore/internal/store/store.go @@ -30,4 +30,6 @@ type Store interface { GetIDs(ctx context.Context) ([]string, error) GetContainerImageViews(ctx context.Context, q *v1.Query) ([]*views.ContainerImageView, error) + + GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) } diff --git a/central/deployment/datastore/mocks/datastore.go b/central/deployment/datastore/mocks/datastore.go index bcf147d1cfa52..4f4ec023ee7c9 100644 --- a/central/deployment/datastore/mocks/datastore.go +++ b/central/deployment/datastore/mocks/datastore.go @@ -150,6 +150,21 @@ func (mr *MockDataStoreMockRecorder) GetImagesForDeployment(ctx, deployment any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImagesForDeployment", reflect.TypeOf((*MockDataStore)(nil).GetImagesForDeployment), ctx, deployment) } +// GetV2ImageIDsForDigests mocks base method. +func (m *MockDataStore) GetV2ImageIDsForDigests(ctx context.Context, digests []string) (map[string][]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetV2ImageIDsForDigests", ctx, digests) + ret0, _ := ret[0].(map[string][]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetV2ImageIDsForDigests indicates an expected call of GetV2ImageIDsForDigests. +func (mr *MockDataStoreMockRecorder) GetV2ImageIDsForDigests(ctx, digests any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetV2ImageIDsForDigests", reflect.TypeOf((*MockDataStore)(nil).GetV2ImageIDsForDigests), ctx, digests) +} + // ListDeployment mocks base method. func (m *MockDataStore) ListDeployment(ctx context.Context, id string) (*storage.ListDeployment, bool, error) { m.ctrl.T.Helper() diff --git a/central/image/datastore/datastore.go b/central/image/datastore/datastore.go index 5d4fee9f0987e..b3aac9d1f2fff 100644 --- a/central/image/datastore/datastore.go +++ b/central/image/datastore/datastore.go @@ -29,6 +29,7 @@ type DataStore interface { GetManyImageMetadata(ctx context.Context, ids []string) ([]*storage.Image, error) GetImagesBatch(ctx context.Context, shas []string) ([]*storage.Image, error) WalkByQuery(ctx context.Context, q *v1.Query, fn func(image *storage.Image) error) error + WalkMetadataByQuery(ctx context.Context, q *v1.Query, fn func(image *storage.Image) error) error UpsertImage(ctx context.Context, image *storage.Image) error UpdateVulnerabilityState(ctx context.Context, cve string, images []string, state storage.VulnerabilityState) error diff --git a/central/image/datastore/datastore_impl.go b/central/image/datastore/datastore_impl.go index cfd3c50017d5b..443f30ef90bdb 100644 --- a/central/image/datastore/datastore_impl.go +++ b/central/image/datastore/datastore_impl.go @@ -149,6 +149,10 @@ func (ds *datastoreImpl) WalkByQuery(ctx context.Context, q *v1.Query, fn func(i return ds.storage.WalkByQuery(ctx, q, wrappedFn) } +func (ds *datastoreImpl) WalkMetadataByQuery(ctx context.Context, q *v1.Query, fn func(image *storage.Image) error) error { + return ds.storage.WalkMetadataByQuery(ctx, q, fn) +} + // CountImages delegates to the underlying store. func (ds *datastoreImpl) CountImages(ctx context.Context) (int, error) { if _, err := imagesSAC.ReadAllowed(ctx); err != nil { diff --git a/central/image/datastore/mocks/datastore.go b/central/image/datastore/mocks/datastore.go index fe28488fb626e..90e0fe6f61efb 100644 --- a/central/image/datastore/mocks/datastore.go +++ b/central/image/datastore/mocks/datastore.go @@ -286,3 +286,17 @@ func (mr *MockDataStoreMockRecorder) WalkByQuery(ctx, q, fn any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalkByQuery", reflect.TypeOf((*MockDataStore)(nil).WalkByQuery), ctx, q, fn) } + +// WalkMetadataByQuery mocks base method. +func (m *MockDataStore) WalkMetadataByQuery(ctx context.Context, q *v1.Query, fn func(*storage.Image) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalkMetadataByQuery", ctx, q, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalkMetadataByQuery indicates an expected call of WalkMetadataByQuery. +func (mr *MockDataStoreMockRecorder) WalkMetadataByQuery(ctx, q, fn any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalkMetadataByQuery", reflect.TypeOf((*MockDataStore)(nil).WalkMetadataByQuery), ctx, q, fn) +} diff --git a/central/imagev2/datastore/mapper/datastore/datastore_impl.go b/central/imagev2/datastore/mapper/datastore/datastore_impl.go index cf3e15d8d149e..31b63d3812be7 100644 --- a/central/imagev2/datastore/mapper/datastore/datastore_impl.go +++ b/central/imagev2/datastore/mapper/datastore/datastore_impl.go @@ -186,6 +186,10 @@ func (ds *datastoreImpl) WalkByQuery(ctx context.Context, q *v1.Query, fn func(i }) } +func (ds *datastoreImpl) WalkMetadataByQuery(_ context.Context, _ *v1.Query, _ func(image *storage.Image) error) error { + return errors.New("WalkMetadataByQuery is not implemented for mapper datastore") +} + func (ds *datastoreImpl) UpsertImage(ctx context.Context, image *storage.Image) error { if !ds.flattenImageData { return ds.imageDataStore.UpsertImage(ctx, image) diff --git a/central/pruning/image_pruning_bench_test.go b/central/pruning/image_pruning_bench_test.go index 250053c98a4e2..7e18bdd665524 100644 --- a/central/pruning/image_pruning_bench_test.go +++ b/central/pruning/image_pruning_bench_test.go @@ -90,7 +90,7 @@ func setupBenchmarkTest(b *testing.B, pool postgres.DB) ( gc := newGarbageCollector(nil, nil, nil, imagesV2, nil, deployments, pods, nil, nil, nil, mockConfigDS, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil).(*garbageCollectorImpl) + nil, nil, nil, nil).(*garbageCollectorImpl) gc.postgres = pool return imagesV2, deployments, pods, gc diff --git a/central/pruning/pruning.go b/central/pruning/pruning.go index 61e6b8e34ebdb..5673b35d77fff 100644 --- a/central/pruning/pruning.go +++ b/central/pruning/pruning.go @@ -33,6 +33,7 @@ import ( roleDataStore "github.com/stackrox/rox/central/role/datastore" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" vulnReqDataStore "github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/datastore" + watchedImageDatastore "github.com/stackrox/rox/central/watchedimage/datastore" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/concurrency" @@ -40,6 +41,7 @@ import ( "github.com/stackrox/rox/pkg/dblock" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/features" + imageUtils "github.com/stackrox/rox/pkg/images/utils" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/maputil" pgPkg "github.com/stackrox/rox/pkg/postgres" @@ -76,6 +78,8 @@ var ( pruningCtx = sac.WithAllAccess(context.Background()) lastClusterPruneTime time.Time lastLogImbuePruneTime time.Time + lastV1ImagePruneTime time.Time + lastPrunedV1ImageID string pruningTimeout = env.PostgresDefaultPruningStatementTimeout.DurationSetting() prunedPLOPsWithoutPodUIDs = false @@ -127,6 +131,7 @@ func newGarbageCollector(alerts alertDatastore.DataStore, blobStore blobDatastore.Datastore, nodeCVEStore nodeCVEDS.DataStore, roleStore roleDataStore.DataStore, + watchedImages watchedImageDatastore.DataStore, ) GarbageCollector { return &garbageCollectorImpl{ alerts: alerts, @@ -153,6 +158,7 @@ func newGarbageCollector(alerts alertDatastore.DataStore, blobStore: blobStore, nodeCVEStore: nodeCVEStore, roleStore: roleStore, + watchedImages: watchedImages, } } @@ -182,6 +188,7 @@ type garbageCollectorImpl struct { blobStore blobDatastore.Datastore nodeCVEStore nodeCVEDS.DataStore roleStore roleDataStore.DataStore + watchedImages watchedImageDatastore.DataStore } func (g *garbageCollectorImpl) Start() { @@ -229,6 +236,9 @@ func (g *garbageCollectorImpl) pruneBasedOnConfig() { if env.OrphanedCVEsKeepAlive.BooleanSetting() { g.pruneOrphanedNodeCVEs() } + if features.FlattenImageData.Enabled() && env.PruneV1Images.BooleanSetting() { + g.pruneImageV1s() + } log.Info("[Pruning] Finished garbage collection cycle") } @@ -238,6 +248,7 @@ func (g *garbageCollectorImpl) runGC() { lastClusterPruneTime = time.Now().Add(-24 * time.Hour) lastLogImbuePruneTime = time.Now().Add(-24 * time.Hour) + lastV1ImagePruneTime = time.Now().Add(-env.V1ImagePruneInterval.DurationSetting()) g.pruneBasedOnConfig() t := time.NewTicker(pruneInterval) @@ -251,6 +262,212 @@ func (g *garbageCollectorImpl) runGC() { } } +func (g *garbageCollectorImpl) pruneImageV1s() { + if time.Since(lastV1ImagePruneTime) < env.V1ImagePruneInterval.DurationSetting() { + return + } + defer func() { lastV1ImagePruneTime = time.Now() }() + defer metrics.SetPruningDuration(time.Now(), "V1Images") + + log.Info("[Pruning] Pruning V1 images with V2 twins") + g.pruneImageV1Batch() +} + +// pruneImageV1Batch prunes V1 images from the old images table whose CVE data +// has been migrated to V2. +func (g *garbageCollectorImpl) pruneImageV1Batch() { + // Algorithm: + // 1. Fetch a batch of V1 images ordered by SHA, using cursor-based pagination. + // 2. Delete images with 0 CVEs (no scan data to preserve). + // 3. For images with CVEs, look up V2 twins from deployment containers; + // for watched (undeployed) images, generate V2 IDs from watched names. + // 4. Delete undeployed, unwatched images (no V2 twin to check). + // 5. Delete images whose V2 twins are all enriched (have >0 CVEs). + // + // We check V2 twin enrichment rather than per-CVE migration because reprocessing + // already replaces the full scan — a partial scan loses first-discovered timestamps + // regardless of image model, so this doesn't make things worse. + batchSize := env.V1ImagePruneBatchSize.IntegerSetting() + + sortOpt := search.NewSortOption(search.ImageSHA) + // Cursor-based pagination: fetch images with SHA > last processed SHA. + if lastPrunedV1ImageID != "" { + sortOpt = sortOpt.SearchAfter(lastPrunedV1ImageID) + } + q := search.NewQueryBuilder(). + WithPagination( + search.NewPagination(). + AddSortOption(sortOpt). + Limit(int32(batchSize)), + ).ProtoQuery() + + // Fetch a batch of V1 images + var v1Images []*storage.Image + err := g.images.WalkMetadataByQuery(pruningCtx, q, func(img *storage.Image) error { + v1Images = append(v1Images, img) + return nil + }) + if err != nil { + log.Errorf("[Pruning] Error fetching V1 images: %v", err) + return + } + if len(v1Images) == 0 { + lastPrunedV1ImageID = "" + return + } + + deleteIDs := g.filterV1ImageIDsToPrune(v1Images) + + if len(deleteIDs) > 0 { + if err := g.images.DeleteImages(pruningCtx, deleteIDs...); err != nil { + log.Errorf("[Pruning] Error deleting V1 images: %v", err) + return + } + log.Infof("[Pruning] Deleted %d V1 images", len(deleteIDs)) + } + + if len(v1Images) < batchSize { + lastPrunedV1ImageID = "" + } else { + lastPrunedV1ImageID = v1Images[len(v1Images)-1].GetId() + } +} + +func (g *garbageCollectorImpl) filterV1ImageIDsToPrune(v1Images []*storage.Image) []string { + var deleteIDs []string + var v1ImagesWithCVEs []*storage.Image + + for _, img := range v1Images { + // V1 images with 0 CVEs have no scan data worth preserving, safe to delete. + if img.GetCves() == 0 { + deleteIDs = append(deleteIDs, img.GetId()) + } else { + // V1 images with CVEs need to be checked against V2 twins. If all V2 twins have been enriched, we can delete the V1 image. + v1ImagesWithCVEs = append(v1ImagesWithCVEs, img) + } + } + + if len(v1ImagesWithCVEs) == 0 { + return deleteIDs + } + + var v1Digests []string + for _, img := range v1ImagesWithCVEs { + v1Digests = append(v1Digests, img.GetId()) + } + + // Image V2 twin IDs for the V1 image digests + digestToV2IDs, err := g.deployments.GetV2ImageIDsForDigests(pruningCtx, v1Digests) + if err != nil { + log.Errorf("[Pruning] Error fetching V2 image IDs for digests: %v", err) + return deleteIDs + } + + watchedNames, err := g.getWatchedNameSet() + if err != nil { + log.Errorf("[Pruning] Error fetching watched images: %v", err) + return deleteIDs + } + + for _, img := range v1ImagesWithCVEs { + digest := img.GetId() + v2IDs := digestToV2IDs[digest] + + if len(v2IDs) == 0 { + // V2 twins of a digest can also be generated from watched images. So check if the image name is watched. + watchedV2IDs := g.watchedV2IDsForImage(img, watchedNames) + if len(watchedV2IDs) == 0 { + deleteIDs = append(deleteIDs, digest) + continue + } + digestToV2IDs[digest] = watchedV2IDs + } + } + + // Get Image V2 twins from the IDs gathered above. + v2ImageMap := g.buildV2ImageMap(digestToV2IDs) + if v2ImageMap == nil { + return deleteIDs + } + + for _, img := range v1ImagesWithCVEs { + digest := img.GetId() + v2IDs := digestToV2IDs[digest] + if len(v2IDs) == 0 { + continue + } + // If all V2 twins of a v1 image are enriched, we can delete the v1 image. + if g.allV2TwinsEnriched(v2IDs, v2ImageMap) { + deleteIDs = append(deleteIDs, digest) + } + } + + return deleteIDs +} + +func (g *garbageCollectorImpl) getWatchedNameSet() (set.StringSet, error) { + if g.watchedImages == nil { + return set.NewStringSet(), nil + } + watched, err := g.watchedImages.GetAllWatchedImages(pruningCtx) + if err != nil { + return nil, err + } + names := set.NewStringSet() + for _, w := range watched { + names.Add(w.GetName()) + } + return names, nil +} + +func (g *garbageCollectorImpl) watchedV2IDsForImage(img *storage.Image, watchedNames set.StringSet) []string { + var v2IDs []string + for _, name := range img.GetNames() { + if watchedNames.Contains(name.GetFullName()) { + v2IDs = append(v2IDs, imageUtils.NewImageV2ID(name, img.GetId())) + } + } + return v2IDs +} + +func (g *garbageCollectorImpl) buildV2ImageMap(digestToV2IDs map[string][]string) map[string]*storage.ImageV2 { + var allV2IDs []string + for _, v2IDs := range digestToV2IDs { + allV2IDs = append(allV2IDs, v2IDs...) + } + if len(allV2IDs) == 0 { + return make(map[string]*storage.ImageV2) + } + + v2Images, err := g.imagesV2.GetManyImageMetadata(pruningCtx, allV2IDs) + if err != nil { + log.Errorf("[Pruning] Error fetching V2 image metadata: %v", err) + return nil + } + + v2Map := make(map[string]*storage.ImageV2, len(v2Images)) + for _, img := range v2Images { + v2Map[img.GetId()] = img + } + return v2Map +} + +// allV2TwinsEnriched returns true only if every V2 twin exists and has CVEs. +// A V2 twin with 0 CVEs (remember that corresponging V1 image has CVEs) indicates it may not have been enriched yet. +func (g *garbageCollectorImpl) allV2TwinsEnriched(v2IDs []string, v2ImageMap map[string]*storage.ImageV2) bool { + for _, v2ID := range v2IDs { + v2Img, exists := v2ImageMap[v2ID] + // It is enough to just check if the V2 twin has any CVEs. + // We don't check per-CVE migration because reprocessing already replaces the full + // scan result — a partial scan loses first-discovered timestamps regardless of + // image model, so this doesn't make things worse than they already are. + if !exists || v2Img.GetScanStats().GetCveCount() == 0 { + return false + } + } + return true +} + // Remove vulnerability requests that have expired and past the retention period. func (g *garbageCollectorImpl) removeExpiredVulnRequests() { defer metrics.SetPruningDuration(time.Now(), "VulnRequests") diff --git a/central/pruning/pruning_test.go b/central/pruning/pruning_test.go index 4d583e3d31e44..cb67672d1ec9d 100644 --- a/central/pruning/pruning_test.go +++ b/central/pruning/pruning_test.go @@ -56,6 +56,7 @@ import ( connectionMocks "github.com/stackrox/rox/central/sensor/service/connection/mocks" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" serviceAccountMocks "github.com/stackrox/rox/central/serviceaccount/datastore/mocks" + watchedImageDatastore "github.com/stackrox/rox/central/watchedimage/datastore" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/alert/convert" @@ -577,7 +578,7 @@ func (s *PruningTestSuite) TestImagePruning() { gc := newGarbageCollector(alerts, nodes, images, imagesV2, nil, deployments, pods, nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil).(*garbageCollectorImpl) + nil, nil, nil, nil).(*garbageCollectorImpl) gc.postgres = s.pool // Add images, deployments, and pods into the datastores @@ -603,10 +604,8 @@ func (s *PruningTestSuite) TestImagePruning() { privateConfig, err := config.GetPrivateConfig(ctx) require.NoError(t, err, "failed to get config") - // Garbage collect all of the images gc.collectImages(privateConfig) - // Grab the actual remaining images and make sure they match the images expected to be remaining if features.FlattenImageData.Enabled() { remainingImages, err := imagesV2.SearchRawImages(ctx, search.EmptyQuery()) require.NoError(t, err) @@ -804,7 +803,7 @@ func (s *PruningTestSuite) TestImagePruningSameDigestDifferentName() { gc := newGarbageCollector(nil, nil, nil, imagesV2, nil, deployments, pods, nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil).(*garbageCollectorImpl) + nil, nil, nil, nil).(*garbageCollectorImpl) gc.postgres = s.pool for _, dep := range c.deployments { @@ -1047,7 +1046,7 @@ func (s *PruningTestSuite) TestClusterPruning() { gc := newGarbageCollector(nil, nil, nil, nil, clusterDS, deploymentsDS, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil).(*garbageCollectorImpl) gc.collectClusters(c.config) @@ -1174,7 +1173,7 @@ func (s *PruningTestSuite) TestClusterPruningCentralCheck() { gc := newGarbageCollector(nil, nil, nil, nil, clusterDS, deploymentsDS, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil).(*garbageCollectorImpl) gc.collectClusters(getCluserRetentionConfig(60, 90, 72)) @@ -1352,7 +1351,7 @@ func (s *PruningTestSuite) TestAlertPruning() { gc := newGarbageCollector(alerts, nodes, images, imagesV2, nil, deployments, nil, nil, nil, nil, config, nil, - nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil).(*garbageCollectorImpl) // Add alerts into the datastores @@ -2918,3 +2917,204 @@ func resourceWithAccess(access storage.Access, resource permissions.ResourceMeta Resource: resource, } } + +func (s *PruningTestSuite) generateImageV1PruningDataStructures() (imageDatastore.DataStore, imageV2Datastore.DataStore, deploymentDatastore.DataStore, watchedImageDatastore.DataStore) { + ctrl := gomock.NewController(s.T()) + + mockRiskDatastore := riskDatastoreMocks.NewMockDataStore(ctrl) + mockRiskDatastore.EXPECT().RemoveRisk(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + mockFilter := filterMocks.NewMockFilter(ctrl) + mockFilter.EXPECT().UpdateByPod(gomock.Any()).AnyTimes() + mockFilter.EXPECT().DeleteByPod(gomock.Any()).AnyTimes() + mockFilter.EXPECT().Delete(gomock.Any()).AnyTimes() + + mockBaselineDataStore := processBaselineDatastoreMocks.NewMockDataStore(ctrl) + mockBaselineDataStore.EXPECT().RemoveProcessBaselinesByDeployment(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + + mockNetworkFlows := networkFlowDatastoreMocks.NewMockClusterDataStore(ctrl) + mockFlowStore := networkFlowDatastoreMocks.NewMockFlowDataStore(ctrl) + mockNetworkFlows.EXPECT().GetFlowStore(gomock.Any(), gomock.Any()).AnyTimes().Return(mockFlowStore, nil) + mockFlowStore.EXPECT().RemoveFlowsForDeployment(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + + images := imageDatastore.NewWithPostgres( + imagePostgresV2.New(s.pool, true, concurrency.NewKeyFence()), + mockRiskDatastore, + ranking.ImageRanker(), + ranking.ComponentRanker(), + ) + imagesV2 := imageV2Datastore.NewWithPostgres( + imageV2Postgres.New(s.pool, true, concurrency.NewKeyFence()), + mockRiskDatastore, + ranking.ImageRanker(), + ranking.ComponentRanker(), + ) + deployments, err := deploymentDatastore.New(s.pool, nil, nil, mockBaselineDataStore, mockNetworkFlows, mockRiskDatastore, nil, mockFilter, + ranking.NewRanker(), ranking.NewRanker(), ranking.NewRanker(), + platformmatcher.GetTestPlatformMatcherWithDefaultPlatformComponentConfig(ctrl)) + s.Require().NoError(err) + + watchedImages := watchedImageDatastore.GetTestPostgresDataStore(s.T(), s.pool) + + return images, imagesV2, deployments, watchedImages +} + +func newV1ImageWithCVEs(id string, cves int32) *storage.Image { + digest := types.NewDigest(id).Digest() + name := &storage.ImageName{FullName: "test/" + id + ":latest"} + return &storage.Image{ + Id: digest, + LastUpdated: protoconv.ConvertTimeToTimestamp(time.Now()), + Name: name, + Names: []*storage.ImageName{name}, + SetCves: &storage.Image_Cves{Cves: cves}, + SetComponents: &storage.Image_Components{Components: cves}, + } +} + +func (s *PruningTestSuite) TestPruneImageV1Batch() { + if !features.FlattenImageData.Enabled() { + s.T().Skip("V1 image pruning only runs when FlattenImageData is enabled") + } + if !env.PruneV1Images.BooleanSetting() { + s.T().Skip("V1 image pruning is disabled") + } + + ctx := sac.WithAllAccess(context.Background()) + images, imagesV2, depDS, watchedDS := s.generateImageV1PruningDataStructures() + + // Create V1 images covering all pruning scenarios. + image1 := newV1ImageWithCVEs("img-no-cves", 0) + image2 := newV1ImageWithCVEs("img-enriched", 5) + image3 := newV1ImageWithCVEs("img-not-enriched", 3) + image4 := newV1ImageWithCVEs("img-no-twin", 2) + image5 := newV1ImageWithCVEs("img-missing-v2", 4) + image6 := newV1ImageWithCVEs("img-watched-enriched", 6) + image7 := newV1ImageWithCVEs("img-watched-not-enriched", 7) + image8 := newV1ImageWithCVEs("img-watched-no-v2", 8) + + v1Images := []*storage.Image{image1, image2, image3, image4, image5, image6, image7, image8} + for _, img := range v1Images { + s.Require().NoError(images.UpsertImage(ctx, img)) + } + + // Create V2 images: one enriched, one not enriched. + v2IDImg2 := utils.NewImageV2ID(image2.GetName(), image2.GetId()) + v2IDImg3 := utils.NewImageV2ID(image3.GetName(), image3.GetId()) + v2IDImg5 := utils.NewImageV2ID(image5.GetName(), image5.GetId()) + v2IDImg6 := utils.NewImageV2ID(image6.GetName(), image6.GetId()) + v2IDImg7 := utils.NewImageV2ID(image7.GetName(), image7.GetId()) + + s.Require().NoError(imagesV2.UpsertImage(ctx, &storage.ImageV2{ + Id: v2IDImg2, Digest: image2.GetId(), + Name: image2.GetName(), + ScanStats: &storage.ImageV2_ScanStats{CveCount: 5}, + })) + s.Require().NoError(imagesV2.UpsertImage(ctx, &storage.ImageV2{ + Id: v2IDImg3, Digest: image3.GetId(), + Name: image3.GetName(), + ScanStats: &storage.ImageV2_ScanStats{CveCount: 0}, + })) + // Watched image6: V2 twin enriched. + s.Require().NoError(imagesV2.UpsertImage(ctx, &storage.ImageV2{ + Id: v2IDImg6, Digest: image6.GetId(), + Name: image6.GetName(), + ScanStats: &storage.ImageV2_ScanStats{CveCount: 6}, + })) + // Watched image7: V2 twin not enriched. + s.Require().NoError(imagesV2.UpsertImage(ctx, &storage.ImageV2{ + Id: v2IDImg7, Digest: image7.GetId(), + Name: image7.GetName(), + ScanStats: &storage.ImageV2_ScanStats{CveCount: 0}, + })) + + // Watch image6, image7, and image8 (none are deployed). + s.Require().NoError(watchedDS.UpsertWatchedImage(ctx, image6.GetName().GetFullName())) + s.Require().NoError(watchedDS.UpsertWatchedImage(ctx, image7.GetName().GetFullName())) + s.Require().NoError(watchedDS.UpsertWatchedImage(ctx, image8.GetName().GetFullName())) + + // Create deployment referencing image2 (enriched), image3 (not enriched), and image5 (missing from images_v2). + dep := &storage.Deployment{ + Id: fixtureconsts.Deployment1, + Containers: []*storage.Container{ + {Image: &storage.ContainerImage{Id: image2.GetId(), IdV2: v2IDImg2, Name: image2.GetName()}}, + {Image: &storage.ContainerImage{Id: image3.GetId(), IdV2: v2IDImg3, Name: image3.GetName()}}, + {Image: &storage.ContainerImage{Id: image5.GetId(), IdV2: v2IDImg5, Name: image5.GetName()}}, + }, + } + s.Require().NoError(depDS.UpsertDeployment(ctx, dep)) + + gc := &garbageCollectorImpl{ + images: images, + imagesV2: imagesV2, + deployments: depDS, + watchedImages: watchedDS, + postgres: s.pool, + } + + lastPrunedV1ImageID = "" + gc.pruneImageV1Batch() + + remaining, err := images.SearchListImages(ctx, search.EmptyQuery()) + s.Require().NoError(err) + + var remainingIDs []string + for _, img := range remaining { + remainingIDs = append(remainingIDs, img.GetId()) + } + + // image1 (0 CVEs): deleted + // image2 (deployed, V2 twin enriched): deleted + // image3 (deployed, V2 twin not enriched): kept + // image4 (not deployed, not watched): deleted + // image5 (deployed, V2 missing from images_v2): kept + // image6 (not deployed, watched, V2 twin enriched): deleted + // image7 (not deployed, watched, V2 twin not enriched): kept + // image8 (not deployed, watched, V2 missing): kept + s.Assert().ElementsMatch([]string{image3.GetId(), image5.GetId(), image7.GetId(), image8.GetId()}, remainingIDs) + + // Cleanup + _ = images.DeleteImages(ctx, image3.GetId(), image5.GetId(), image7.GetId(), image8.GetId()) + _ = imagesV2.DeleteImages(ctx, v2IDImg2, v2IDImg3, v2IDImg6, v2IDImg7) + _ = depDS.RemoveDeployment(ctx, "", dep.GetId()) +} + +func (s *PruningTestSuite) TestPruneImageV1BatchCursor() { + if !features.FlattenImageData.Enabled() { + s.T().Skip("V1 image pruning only runs when FlattenImageData is enabled") + } + if !env.PruneV1Images.BooleanSetting() { + s.T().Skip("V1 image pruning is disabled") + } + + ctx := sac.WithAllAccess(context.Background()) + images, imagesV2, depDS, watchedDS := s.generateImageV1PruningDataStructures() + + for range 3 { + img := newV1ImageWithCVEs(uuid.NewV4().String(), 0) + s.Require().NoError(images.UpsertImage(ctx, img)) + } + + gc := &garbageCollectorImpl{ + images: images, + imagesV2: imagesV2, + deployments: depDS, + watchedImages: watchedDS, + postgres: s.pool, + } + + // With batch size = 2, first batch processes 2, cursor advances. + s.T().Setenv("ROX_V1_IMAGE_PRUNE_BATCH_SIZE", "2") + + lastPrunedV1ImageID = "" + gc.pruneImageV1Batch() + s.Assert().NotEmpty(lastPrunedV1ImageID, "cursor should advance after full batch") + + // Second batch processes remaining 1, cursor resets. + gc.pruneImageV1Batch() + s.Assert().Empty(lastPrunedV1ImageID, "cursor should reset when batch < batchSize") + + remaining, err := images.SearchListImages(ctx, search.EmptyQuery()) + s.Require().NoError(err) + s.Assert().Empty(remaining, "all 0-CVE images should be pruned") +} diff --git a/central/pruning/singleton.go b/central/pruning/singleton.go index 8200e0c3ccb79..cf2cb4dd6f4f8 100644 --- a/central/pruning/singleton.go +++ b/central/pruning/singleton.go @@ -23,6 +23,7 @@ import ( roleDataStore "github.com/stackrox/rox/central/role/datastore" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" vulnReqDataStore "github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/datastore" + watchedImageDatastore "github.com/stackrox/rox/central/watchedimage/datastore" "github.com/stackrox/rox/pkg/sync" ) @@ -56,6 +57,7 @@ func Singleton() GarbageCollector { blobDS.Singleton(), nodeCVEDS.Singleton(), roleDataStore.Singleton(), + watchedImageDatastore.Singleton(), ) }) return gc diff --git a/pkg/env/vulnerability_management.go b/pkg/env/vulnerability_management.go index a8e7148254558..b8a8708401515 100644 --- a/pkg/env/vulnerability_management.go +++ b/pkg/env/vulnerability_management.go @@ -1,5 +1,7 @@ package env +import "time" + var ( // ReportExecutionMaxConcurrency sets the maximum number vulnerability reports that can run in parallel ReportExecutionMaxConcurrency = RegisterIntegerSetting("ROX_REPORT_EXECUTION_MAX_CONCURRENCY", 5) @@ -22,4 +24,13 @@ var ( // ReportMissedScheduleRecovery enables recovery of missed scheduled reports on startup ReportMissedScheduleRecovery = RegisterBooleanSetting("ROX_REPORT_MISSED_SCHEDULE_RECOVERY", true) + + // PruneV1Images controls whether V1 image pruning is active + PruneV1Images = RegisterBooleanSetting("ROX_PRUNE_V1_IMAGES", true) + + // V1ImagePruneInterval is how often the V1 image pruning cycle runs + V1ImagePruneInterval = registerDurationSetting("ROX_V1_IMAGE_PRUNE_INTERVAL", 6*time.Hour) + + // V1ImagePruneBatchSize is the number of V1 images to process per pruning cycle + V1ImagePruneBatchSize = RegisterIntegerSetting("ROX_V1_IMAGE_PRUNE_BATCH_SIZE", 500).WithMinimum(1).WithMaximum(10000) ) diff --git a/tools/allowed-large-files b/tools/allowed-large-files index ac11a963ed3f6..1f2268bb73de2 100644 --- a/tools/allowed-large-files +++ b/tools/allowed-large-files @@ -16,6 +16,7 @@ central/image/service/service_impl.go central/networkgraph/service/service_impl_test.go central/networkpolicies/graph/evaluator_test.go central/processlisteningonport/datastore/datastore_impl_test.go +central/pruning/pruning.go central/pruning/pruning_test.go central/reports/service/v2/service_impl_test.go central/sensor/service/connection/sensor-central-data-flow.excalidraw.svg From eee285aec76a74015b12a62dfd00be2d5727b916 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:57:59 +0200 Subject: [PATCH 226/638] ROX-35543: backport-pr only for release branches, add assignees (#281) (#22689) Co-authored-by: Tom Martensen --- .github/workflows/backport-pr.yaml | 43 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backport-pr.yaml b/.github/workflows/backport-pr.yaml index f30c5dd830a9a..817043cc41ea0 100644 --- a/.github/workflows/backport-pr.yaml +++ b/.github/workflows/backport-pr.yaml @@ -1,10 +1,18 @@ name: Backport PR to release branch on: - pull_request_target: + # From security considerations, it will not be possible for PRs from forks to be backported with this workflow. + pull_request: types: - closed - labeled +# Disables permissions of the default GITHUB_TOKEN, it's not used anyway. +permissions: {} + +defaults: + run: + shell: bash + jobs: backport: name: Backport @@ -17,11 +25,40 @@ jobs: github.event.action == 'closed' || ( github.event.action == 'labeled' - && contains(github.event.label.name, 'backport') + && startsWith(github.event.label.name, 'backport release-') ) ) steps: - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # ratchet:tibdex/backport@v2.0.4 + id: backport with: github_token: ${{ secrets.RHACS_BOT_GITHUB_TOKEN }} - title_template: "<%= title %>" + title_template: "<%= title %> (<%= base %>)" + label_pattern: '^backport (?(release-[0-9]+\.[0-9]+))$' + labels_template: '["backport"]' + + - name: Assign backport PRs to original author + if: steps.backport.outcome == 'success' && steps.backport.outputs.created_pull_requests != '{}' + env: + GH_TOKEN: ${{ secrets.RHACS_BOT_GITHUB_TOKEN }} + CREATED_PRS: ${{ steps.backport.outputs.created_pull_requests }} + ORIGINAL_AUTHOR: ${{ github.event.pull_request.user.login }} + run: | + set -u + echo "Created PRs: ${CREATED_PRS}" + echo "Original author: ${ORIGINAL_AUTHOR}" + + # Check if the original author can be assigned + if gh api --silent "repos/${{ github.repository }}/assignees/${ORIGINAL_AUTHOR}" 2>/dev/null; then + echo "Author ${ORIGINAL_AUTHOR} is eligible for assignment" + + # Parse the JSON object and extract PR numbers + for pr_number in $(echo "${CREATED_PRS}" | jq -r '.[]'); do + echo "Assigning PR #${pr_number} to ${ORIGINAL_AUTHOR}" + if ! gh pr edit "${pr_number}" --add-assignee "${ORIGINAL_AUTHOR}" --repo "${{ github.repository }}"; then + >&2 echo "Warning: Failed to assign PR #${pr_number} to ${ORIGINAL_AUTHOR}" + fi + done + else + >&2 echo "Warning: Author ${ORIGINAL_AUTHOR} cannot be assigned to PRs in this repository" + fi From f8955f7024405b05232dc0968d5b91866a5f7463 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 10:37:54 +0200 Subject: [PATCH 227/638] ROX-32849: Map discovered VM data in Sensor (#22483) --- .../virtual_machine_v2_test.go | 28 ++ pkg/virtualmachine/facts.go | 15 + sensor/common/virtualmachine/facts.go | 66 ++++ sensor/common/virtualmachine/facts_test.go | 122 ++++++- .../virtualmachine/virtualmachineinfo.go | 7 + .../virtualmachine/vmscraper/scraper.go | 126 ++++++- .../virtualmachine/vmscraper/scraper_test.go | 327 +++++++++++++++++- .../virtualmachine/dispatcher/utils.go | 13 + .../dispatcher/virtualmachineinstances.go | 2 + .../virtualmachineinstances_test.go | 43 +++ .../store/virtual_machine_store.go | 21 ++ .../store/virtual_machine_store_test.go | 51 +++ sensor/kubernetes/sensor/sensor.go | 2 +- tests/vm_scanning_test.go | 12 + 14 files changed, 803 insertions(+), 32 deletions(-) diff --git a/central/convert/internaltostorage/virtual_machine_v2_test.go b/central/convert/internaltostorage/virtual_machine_v2_test.go index 73bc39ab420a9..d2b0b46e2dd13 100644 --- a/central/convert/internaltostorage/virtual_machine_v2_test.go +++ b/central/convert/internaltostorage/virtual_machine_v2_test.go @@ -48,6 +48,34 @@ func TestVirtualMachineV2(t *testing.T) { State: storage.VirtualMachineV2_RUNNING, }, }, + { + name: "copies agent facts and keeps informer GuestOs column", + input: &virtualMachineV1.VirtualMachine{ + Id: "VM-ID-3", + Namespace: "ns", + Name: "vm-agent-facts", + ClusterId: uuid.NewTestUUID(3).String(), + Facts: map[string]string{ + pkgVM.GuestOSKey: "Red Hat Enterprise Linux", + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.AgentVersionKey: "4.10.0", + }, + State: virtualMachineV1.VirtualMachine_RUNNING, + }, + expected: &storage.VirtualMachineV2{ + Id: "VM-ID-3", + Namespace: "ns", + Name: "vm-agent-facts", + ClusterId: uuid.NewTestUUID(3).String(), + Facts: map[string]string{ + pkgVM.GuestOSKey: "Red Hat Enterprise Linux", + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.AgentVersionKey: "4.10.0", + }, + GuestOs: "Red Hat Enterprise Linux", + State: storage.VirtualMachineV2_RUNNING, + }, + }, { name: "virtual machine without guestOS fact", input: &virtualMachineV1.VirtualMachine{ diff --git a/pkg/virtualmachine/facts.go b/pkg/virtualmachine/facts.go index bb001e04cf29c..65c8b5fbd773c 100644 --- a/pkg/virtualmachine/facts.go +++ b/pkg/virtualmachine/facts.go @@ -10,7 +10,22 @@ const ( NodeNameKey = "nodeName" BootOrderKey = "bootOrder" CDRomDisksKey = "cdRomDisks" + // DetectedGuestOSKey is the guest OS reported by roxagent, kept separate + // from GuestOSKey so KubeVirt informer data is not overwritten. + DetectedGuestOSKey = "detectedGuestOS" + // AgentVersionKey is the roxagent version from ResponseMeta.AgentVersion. + AgentVersionKey = "agentVersion" + // ActivationStatusKey is whether the guest OS is activated. + ActivationStatusKey = "activationStatus" + // DNFMetadataStatusKey is whether DNF metadata is available on the guest. + DNFMetadataStatusKey = "dnfMetadataStatus" // UnknownGuestOS is the user-facing default value for GuestOSKey when the // guest OS has not been reported by the virtual machine instance. UnknownGuestOS = "unknown" + // User-facing values for roxagent-derived facts. Unspecified proto enum + // values are omitted rather than stored as raw proto names. + ActivationStatusActive = "active" + ActivationStatusInactive = "inactive" + DNFMetadataStatusAvailable = "available" + DNFMetadataStatusUnavailable = "unavailable" ) diff --git a/sensor/common/virtualmachine/facts.go b/sensor/common/virtualmachine/facts.go index 652a0e69708c8..d59fbde8e3801 100644 --- a/sensor/common/virtualmachine/facts.go +++ b/sensor/common/virtualmachine/facts.go @@ -8,6 +8,16 @@ import ( pkgVM "github.com/stackrox/rox/pkg/virtualmachine" ) +const rhelGuestOS = "Red Hat Enterprise Linux" + +// ResponseMeta.facts keys written by roxagent (proto enum String() values). +const ( + responseDetectedOSKey = "detected_os" + responseOSVersionKey = "os_version" + responseActivationStatusKey = "activation_status" + responseDNFMetadataStatusKey = "dnf_metadata_status" +) + // Facts builds the VM facts map sent to Central. func Facts(vm *Info) map[string]string { if vm == nil { @@ -38,9 +48,65 @@ func Facts(vm *Info) map[string]string { if len(vm.CDRomDisks) > 0 { facts[pkgVM.CDRomDisksKey] = strings.Join(vm.CDRomDisks, ", ") } + // GuestOSKey is informer-owned; roxagent reports DetectedGuestOSKey instead. + for k, v := range vm.AgentFacts { + if k == pkgVM.GuestOSKey { + continue + } + facts[k] = v + } return facts } +// AgentFactsFromResponseFacts maps roxagent ResponseMeta.facts to user-facing +// fact keys. Unrecognized or unspecified enum strings are omitted so Sensor +// does not surface raw proto names in the UI. +func AgentFactsFromResponseFacts(facts map[string]string) map[string]string { + if len(facts) == 0 { + return nil + } + + out := map[string]string{} + switch facts[responseDetectedOSKey] { + case v1.DetectedOS_RHEL.String(): + guestOS := rhelGuestOS + if osVersion := facts[responseOSVersionKey]; osVersion != "" { + guestOS += " " + osVersion + } + out[pkgVM.DetectedGuestOSKey] = guestOS + } + switch facts[responseActivationStatusKey] { + case v1.ActivationStatus_ACTIVE.String(): + out[pkgVM.ActivationStatusKey] = pkgVM.ActivationStatusActive + case v1.ActivationStatus_INACTIVE.String(): + out[pkgVM.ActivationStatusKey] = pkgVM.ActivationStatusInactive + } + switch facts[responseDNFMetadataStatusKey] { + case v1.DnfMetadataStatus_AVAILABLE.String(): + out[pkgVM.DNFMetadataStatusKey] = pkgVM.DNFMetadataStatusAvailable + case v1.DnfMetadataStatus_UNAVAILABLE.String(): + out[pkgVM.DNFMetadataStatusKey] = pkgVM.DNFMetadataStatusUnavailable + } + if len(out) == 0 { + return nil + } + return out +} + +// AgentFactsFromResponse merges ResponseMeta.facts with AgentVersion. Version +// is a sibling proto field, not a fact key, so it is attached here. +func AgentFactsFromResponse(facts map[string]string, agentVersion string) map[string]string { + out := AgentFactsFromResponseFacts(facts) + if agentVersion == "" { + return out + } + if out == nil { + out = make(map[string]string, 1) + } + out[pkgVM.AgentVersionKey] = agentVersion + return out +} + // State is RUNNING when the VM instance is up, otherwise STOPPED. func State(vm *Info) v1.VirtualMachine_State { if vm == nil { diff --git a/sensor/common/virtualmachine/facts_test.go b/sensor/common/virtualmachine/facts_test.go index 6eefca29daf7e..94d56062b9bd9 100644 --- a/sensor/common/virtualmachine/facts_test.go +++ b/sensor/common/virtualmachine/facts_test.go @@ -51,6 +51,33 @@ func TestFacts(t *testing.T) { pkgVM.CDRomDisksKey: "cdrom0", }, }, + "AgentFacts are merged into result": { + input: &Info{ + GuestOS: "RHEL 9", + AgentFacts: map[string]string{ + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + }, + }, + expected: map[string]string{ + pkgVM.GuestOSKey: "RHEL 9", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + }, + }, + "AgentFacts do not overwrite informer guestOS": { + input: &Info{ + GuestOS: "from-informer", + AgentFacts: map[string]string{ + pkgVM.GuestOSKey: "from-agent", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + }, + }, + expected: map[string]string{ + pkgVM.GuestOSKey: "from-informer", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + }, + }, } for name, tc := range cases { @@ -60,6 +87,85 @@ func TestFacts(t *testing.T) { } } +func TestAgentFactsFromResponseFacts(t *testing.T) { + cases := map[string]struct { + input map[string]string + expected map[string]string + }{ + "RHEL with version and statuses": { + input: map[string]string{ + "detected_os": v1.DetectedOS_RHEL.String(), + "os_version": "9.2", + "activation_status": v1.ActivationStatus_INACTIVE.String(), + "dnf_metadata_status": v1.DnfMetadataStatus_UNAVAILABLE.String(), + }, + expected: map[string]string{ + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusInactive, + pkgVM.DNFMetadataStatusKey: pkgVM.DNFMetadataStatusUnavailable, + }, + }, + "unspecified enums are omitted": { + input: map[string]string{ + "detected_os": v1.DetectedOS_UNKNOWN.String(), + "activation_status": v1.ActivationStatus_ACTIVATION_UNSPECIFIED.String(), + "dnf_metadata_status": v1.DnfMetadataStatus_DNF_METADATA_UNSPECIFIED.String(), + }, + expected: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, AgentFactsFromResponseFacts(tc.input)) + }) + } +} + +func TestAgentFactsFromResponse(t *testing.T) { + cases := map[string]struct { + facts map[string]string + agentVersion string + expected map[string]string + }{ + "attaches agent version to mapped facts": { + facts: map[string]string{ + "detected_os": v1.DetectedOS_RHEL.String(), + "os_version": "9.2", + "activation_status": v1.ActivationStatus_ACTIVE.String(), + }, + agentVersion: "4.10.0", + expected: map[string]string{ + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + pkgVM.AgentVersionKey: "4.10.0", + }, + }, + "version only when facts do not map": { + facts: map[string]string{"detected_os": v1.DetectedOS_UNKNOWN.String()}, + agentVersion: "4.10.0", + expected: map[string]string{pkgVM.AgentVersionKey: "4.10.0"}, + }, + "omits empty agent version": { + facts: map[string]string{ + "activation_status": v1.ActivationStatus_ACTIVE.String(), + }, + expected: map[string]string{ + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + }, + }, + "empty facts and empty version": { + expected: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, AgentFactsFromResponse(tc.facts, tc.agentVersion)) + }) + } +} + func TestState(t *testing.T) { cases := map[string]struct { input *Info @@ -132,12 +238,13 @@ func TestSensorEvent(t *testing.T) { t.Run("running VM with VSOCK CID", func(t *testing.T) { event := SensorEvent(central.ResourceAction_UPDATE_RESOURCE, "cluster-1", &Info{ - ID: "vm-1", - Name: "rhel9", - Namespace: "ns", - Running: true, - VSOCKCID: new(uint32(7)), - GuestOS: "Red Hat Enterprise Linux 9", + ID: "vm-1", + Name: "rhel9", + Namespace: "ns", + Running: true, + VSOCKCID: new(uint32(7)), + GuestOS: "Red Hat Enterprise Linux 9", + AgentFacts: map[string]string{pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive}, }) assert.Equal(t, "vm-1", event.GetId()) assert.Equal(t, central.ResourceAction_UPDATE_RESOURCE, event.GetAction()) @@ -147,7 +254,8 @@ func TestSensorEvent(t *testing.T) { assert.True(t, vm.GetVsockCidSet()) assert.Equal(t, v1.VirtualMachine_RUNNING, vm.GetState()) assert.Equal(t, map[string]string{ - pkgVM.GuestOSKey: "Red Hat Enterprise Linux 9", + pkgVM.GuestOSKey: "Red Hat Enterprise Linux 9", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, }, vm.GetFacts()) }) diff --git a/sensor/common/virtualmachine/virtualmachineinfo.go b/sensor/common/virtualmachine/virtualmachineinfo.go index eddc96716a01e..9dee29197cd11 100644 --- a/sensor/common/virtualmachine/virtualmachineinfo.go +++ b/sensor/common/virtualmachine/virtualmachineinfo.go @@ -1,5 +1,7 @@ package virtualmachine +import "maps" + type VMID string // Info information about a VirtualMachine @@ -22,6 +24,8 @@ type Info struct { BootOrder []string // CDRomDisks lists disk names with CD-ROM devices. CDRomDisks []string + // AgentFacts are roxagent-derived facts merged into the VM facts map. + AgentFacts map[string]string } // Key returns the identifier ("namespace/name") used to correlate this VM @@ -61,5 +65,8 @@ func (v *Info) Copy() *Info { if len(v.CDRomDisks) > 0 { ret.CDRomDisks = append([]string(nil), v.CDRomDisks...) } + if v.AgentFacts != nil { + ret.AgentFacts = maps.Clone(v.AgentFacts) + } return ret } diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index d514911f0ae8b..d164ee3935ac4 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "maps" "math/rand" "strconv" "strings" @@ -64,6 +65,8 @@ func clampPollInterval(interval time.Duration) time.Duration { type RunningVMStore interface { ListRunning() []*virtualmachine.Info Get(id virtualmachine.VMID) *virtualmachine.Info + AddOrUpdate(vm *virtualmachine.Info) *virtualmachine.Info + SetAgentFacts(id virtualmachine.VMID, facts map[string]string) } // VMDialer connects to a VM's VSOCK port. @@ -83,6 +86,10 @@ type Repo2CPEFetcher interface { FetchRepo2CPE(ctx context.Context) (mapping []byte, hash string, ok bool) } +type clusterIDGetter interface { + GetNoWait() string +} + // closeCoder is satisfied by transport errors carrying a structured close // code. Declared locally so VMScraper doesn't need to import a concrete // dialer's error type to recognize one. @@ -97,6 +104,7 @@ type VMScraper struct { dialer VMDialer client ProtocolClient repo2CPEFetcher Repo2CPEFetcher + clusterID clusterIDGetter toCentral chan *message.ExpiringMessage centralReady concurrency.Signal interval time.Duration @@ -138,13 +146,14 @@ var _ common.SensorComponent = (*VMScraper)(nil) // New creates a VMScraper with production defaults. A nil repo2CPEFetcher leaves // maybeSyncRepoCPEMapping a no-op, so pull still works if the repo-to-CPE cache was not built. -func New(store RunningVMStore, dialer VMDialer, client ProtocolClient, repo2CPEFetcher Repo2CPEFetcher) *VMScraper { +func New(store RunningVMStore, dialer VMDialer, client ProtocolClient, repo2CPEFetcher Repo2CPEFetcher, clusterID clusterIDGetter) *VMScraper { interval := clampPollInterval(env.VirtualMachinesScraperPollInterval.DurationSetting()) return &VMScraper{ store: store, dialer: dialer, client: client, repo2CPEFetcher: repo2CPEFetcher, + clusterID: clusterID, toCentral: make(chan *message.ExpiringMessage, env.VirtualMachinesIndexReportsBufferSize.IntegerSetting()), centralReady: concurrency.NewSignal(), interval: interval, @@ -493,6 +502,7 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } if result.Unchanged { + s.forwardAgentFactsIfChanged(ctx, vm, result.Meta) next := s.scheduleAfterAttempt(key, vm.ID, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=unchanged next=%s", key, next) log.Debugf("VMScraper: unchanged report from roxagent on %q (token=%s)", key, snap.lastToken) @@ -515,6 +525,9 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool metrics.PullReportBytes.Observe(float64(reportSize)) metrics.PullReportPackages.Observe(float64(len(result.IndexReport.GetContents().GetPackages()))) logAndRecordDiscoveredFacts(key, result.Meta.GetFacts()) + if mapped, ok := snapshotAgentFacts(result.Meta); ok { + vm.AgentFacts = mapped + } // Mapping sync has its own deadline, so forward uses the scrape parent. if err := s.forwardReport(ctx, vm, result.IndexReport); err != nil { @@ -537,6 +550,11 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool log.Infof("VMScraper: scrape %q failed %s next=%s", key, kind, next) return false } + // Persist facts only after the UPDATE is queued so a dropped send + // retries on the next unchanged poll instead of looking like a no-op. + if s.tryEnqueueVMUpdate(vm) { + s.persistAgentFacts(vm, result.Meta) + } newToken := result.Meta.GetReportToken() s.commitVMState(key, vm.ID, newToken, result.Meta.GetAgentVersion()) @@ -866,6 +884,14 @@ func (s *VMScraper) commitVMState(key string, vmID virtualmachine.VMID, newToken } func (s *VMScraper) forwardReport(ctx context.Context, vm *virtualmachine.Info, report *v4.IndexReport) error { + if err := s.enqueueToCentral(ctx, newIndexReportMessage(vm, report)); err != nil { + return err + } + metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() + return nil +} + +func (s *VMScraper) enqueueToCentral(ctx context.Context, msg *message.ExpiringMessage) error { if !centralcaps.Has(centralsensor.VirtualMachinesSupported) { return errox.NotImplemented.CausedBy(errCapabilityNotSupported) } @@ -874,11 +900,9 @@ func (s *VMScraper) forwardReport(ctx context.Context, vm *virtualmachine.Info, return errox.ResourceExhausted.CausedBy(errCentralNotReachable) } - msg := newIndexReportMessage(vm, report) select { case <-ctx.Done(): case s.toCentral <- msg: - metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() return nil default: metrics.IndexReportEnqueueBlockedTotal.Inc() @@ -886,9 +910,8 @@ func (s *VMScraper) forwardReport(ctx context.Context, vm *virtualmachine.Info, select { case <-ctx.Done(): - return fmt.Errorf("waiting to forward index report: %w", ctx.Err()) + return fmt.Errorf("waiting to forward: %w", ctx.Err()) case s.toCentral <- msg: - metrics.IndexReportsSent.With(metrics.StatusSuccessLabels).Inc() return nil } } @@ -918,3 +941,96 @@ func newIndexReportMessage(vm *virtualmachine.Info, report *v4.IndexReport) *mes }, }) } + +func (s *VMScraper) clusterIDString() string { + if s.clusterID == nil { + return "" + } + return s.clusterID.GetNoWait() +} + +func (s *VMScraper) vmUpdateMessage(vm *virtualmachine.Info) *message.ExpiringMessage { + event := virtualmachine.SensorEvent(central.ResourceAction_UPDATE_RESOURCE, s.clusterIDString(), vm) + if event == nil { + return nil + } + return message.New(¢ral.MsgFromSensor{ + Msg: ¢ral.MsgFromSensor_Event{Event: event}, + }) +} + +// tryEnqueueVMUpdate best-effort enqueues a facts UPDATE after a successful +// index forward. It must not block: a second wait on toCentral would stall +// the scrape if the buffer is full. +func (s *VMScraper) tryEnqueueVMUpdate(vm *virtualmachine.Info) bool { + if vm == nil || vm.AgentFacts == nil { + return false + } + if s.storedAgentFactsEqual(vm.ID, vm.AgentFacts) { + return false + } + msg := s.vmUpdateMessage(vm) + if msg == nil { + return false + } + select { + case s.toCentral <- msg: + return true + default: + log.Debugf("VMScraper: agent facts for %q not forwarded; will retry", vm.Key()) + return false + } +} + +func (s *VMScraper) persistAgentFacts(vm *virtualmachine.Info, meta *pb.ResponseMeta) { + mapped, ok := snapshotAgentFacts(meta) + if !ok { + return + } + vm.AgentFacts = mapped + s.store.SetAgentFacts(vm.ID, mapped) +} + +func (s *VMScraper) storedAgentFactsEqual(id virtualmachine.VMID, mapped map[string]string) bool { + var prev map[string]string + if stored := s.store.Get(id); stored != nil { + prev = stored.AgentFacts + } + return maps.Equal(prev, mapped) +} + +// snapshotAgentFacts maps ResponseMeta onto AgentFacts. A non-empty result +// replaces the last scrape as a whole; unspecified keys are omitted. ok is +// false when nothing maps, so last-known values stay. +func snapshotAgentFacts(meta *pb.ResponseMeta) (mapped map[string]string, ok bool) { + if meta == nil { + return nil, false + } + mapped = virtualmachine.AgentFactsFromResponse(meta.GetFacts(), meta.GetAgentVersion()) + return mapped, len(mapped) > 0 +} + +// forwardAgentFactsIfChanged emits a VM update when roxagent facts changed +// even if the index report is unchanged. The store is updated only after +// enqueue succeeds so a failed send is retried on the next unchanged scrape. +func (s *VMScraper) forwardAgentFactsIfChanged(ctx context.Context, vm *virtualmachine.Info, meta *pb.ResponseMeta) { + mapped, ok := snapshotAgentFacts(meta) + if !ok { + return + } + logAndRecordDiscoveredFacts(vm.Key(), meta.GetFacts()) + if s.storedAgentFactsEqual(vm.ID, mapped) { + return + } + toSend := vm.Copy() + toSend.AgentFacts = mapped + msg := s.vmUpdateMessage(toSend) + if msg == nil { + return + } + if err := s.enqueueToCentral(ctx, msg); err != nil { + log.Debugf("VMScraper: agent facts for %q not forwarded; will retry: %v", vm.Key(), err) + return + } + s.store.SetAgentFacts(vm.ID, mapped) +} diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 4945fa868c13e..75aecaf3a0769 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "maps" "sync/atomic" "testing" "testing/synctest" @@ -20,6 +21,7 @@ import ( "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" "github.com/stackrox/rox/sensor/common/centralcaps" "github.com/stackrox/rox/sensor/common/message" "github.com/stackrox/rox/sensor/common/virtualmachine" @@ -33,7 +35,10 @@ import ( // --- Mocks --- +// mockStore is used by concurrent scrape workers (persistAgentFacts calls +// SetAgentFacts), so mu guards vms and listRunningCalls. type mockStore struct { + mu sync.Mutex vms []*virtualmachine.Info listRunningCalls int } @@ -42,23 +47,59 @@ type mockStore struct { // with Running set, so tests exercise reconcile pruning rather than the // scrapeKey nil-VM guard when a VM stops running. func (m *mockStore) ListRunning() []*virtualmachine.Info { - m.listRunningCalls++ - var out []*virtualmachine.Info - for _, vm := range m.vms { - if vm.Running { - out = append(out, vm) + return concurrency.WithLock1(&m.mu, func() []*virtualmachine.Info { + m.listRunningCalls++ + var out []*virtualmachine.Info + for _, vm := range m.vms { + if vm.Running { + out = append(out, vm) + } } - } - return out + return out + }) } func (m *mockStore) Get(id virtualmachine.VMID) *virtualmachine.Info { - for _, vm := range m.vms { - if vm.ID == id { - return vm + return concurrency.WithLock1(&m.mu, func() *virtualmachine.Info { + for _, vm := range m.vms { + if vm.ID == id { + return vm.Copy() + } } + return nil + }) +} + +func (m *mockStore) AddOrUpdate(vm *virtualmachine.Info) *virtualmachine.Info { + if vm == nil { + return nil } - return nil + return concurrency.WithLock1(&m.mu, func() *virtualmachine.Info { + for i, existing := range m.vms { + if existing.ID != vm.ID { + continue + } + if vm.AgentFacts == nil { + vm.AgentFacts = existing.AgentFacts + } + m.vms[i] = vm + return vm + } + m.vms = append(m.vms, vm) + return vm + }) +} + +func (m *mockStore) SetAgentFacts(id virtualmachine.VMID, facts map[string]string) { + concurrency.WithLock(&m.mu, func() { + for _, vm := range m.vms { + if vm.ID != id { + continue + } + vm.AgentFacts = maps.Clone(facts) + return + } + }) } type mockDialer struct { @@ -250,7 +291,8 @@ func makeReport(token string) *vsockclient.GetReportResult { State: "IndexFinished", }, Meta: &pb.ResponseMeta{ - ReportToken: token, + ReportToken: token, + AgentVersion: "roxagent-test", Facts: map[string]string{ "detected_os": "RHEL", "activation_status": "ACTIVE", @@ -274,6 +316,16 @@ func metaWithMapping(hash string, path pb.RepoCPEMappingUpdatePath) *pb.Response } } +func unchangedResultWithFacts(facts map[string]string) *vsockclient.GetReportResult { + return &vsockclient.GetReportResult{ + Unchanged: true, + Meta: &pb.ResponseMeta{ + ReportToken: "1", + Facts: facts, + }, + } +} + // --- Tests --- func TestVMScraper_PollsRunningVMs(t *testing.T) { @@ -294,6 +346,68 @@ func TestVMScraper_PollsRunningVMs(t *testing.T) { assert.Equal(t, 2, forwardedCount(s)) assert.Len(t, client.calls, 2) assert.Equal(t, discoveredBefore+2, testutil.ToFloat64(metrics.VMDiscoveredData.WithLabelValues("RHEL", "ACTIVE", "AVAILABLE"))) + expectedFacts := virtualmachine.AgentFactsFromResponse(map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + }, "roxagent-test") + assert.Equal(t, expectedFacts, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) + assert.Equal(t, expectedFacts, store.Get(virtualmachine.VMID("ns2/vm-b")).AgentFacts) +} + +func TestPersistAgentFactsDoesNotAliasStorePointer(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + store := &mockStore{vms: []*virtualmachine.Info{vm.Copy()}} + s := &VMScraper{store: store} + + s.persistAgentFacts(vm, &pb.ResponseMeta{ + AgentVersion: "roxagent-test", + Facts: map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + }, + }) + vm.GuestOS = "mutated-after-persist" + + stored := store.Get(vm.ID) + require.NotNil(t, stored) + assert.NotEqual(t, "mutated-after-persist", stored.GuestOS) + assert.Equal(t, virtualmachine.AgentFactsFromResponse(map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + }, "roxagent-test"), stored.AgentFacts) +} + +func TestPersistAgentFactsDoesNotClobberInstanceFields(t *testing.T) { + stored := makeVM("ns1", "vm-a", 100) + stored.GuestOS = "from-informer" + stored.Description = "from-informer" + store := &mockStore{vms: []*virtualmachine.Info{stored}} + s := &VMScraper{store: store} + + stale := stored.Copy() + stale.GuestOS = "stale-scrape-copy" + stale.Description = "stale-scrape-copy" + s.persistAgentFacts(stale, &pb.ResponseMeta{ + AgentVersion: "roxagent-test", + Facts: map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + }, + }) + + got := store.Get(stored.ID) + require.NotNil(t, got) + assert.Equal(t, "from-informer", got.GuestOS) + assert.Equal(t, "from-informer", got.Description) + assert.Equal(t, virtualmachine.AgentFactsFromResponse(map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + }, "roxagent-test"), got.AgentFacts) } // TestVMScraper_SkipsWhenCentralLacksCapability covers a store already @@ -369,6 +483,153 @@ func TestVMScraper_SkipsUnchangedToken(t *testing.T) { assert.Zero(t, forwardedCount(s), "should not forward unchanged report") } +func TestVMScraper_ForwardsChangedAgentFactsOnUnchangedReport(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.pollOnce(context.Background()) + require.Equal(t, 1, forwardedCount(s)) + + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{unchangedResultWithFacts(map[string]string{ + "detected_os": "RHEL", + "activation_status": "INACTIVE", + "dnf_metadata_status": "UNAVAILABLE", + })} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + + expected := virtualmachine.AgentFactsFromResponseFacts(map[string]string{ + "detected_os": "RHEL", + "activation_status": "INACTIVE", + "dnf_metadata_status": "UNAVAILABLE", + }) + updates := drainVMUpdates(s) + require.Len(t, updates, 1, "unchanged report should not be forwarded") + assert.Equal(t, expected[pkgVM.ActivationStatusKey], updates[0].GetFacts()[pkgVM.ActivationStatusKey]) + assert.Equal(t, expected, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) + + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{unchangedResultWithFacts(map[string]string{ + "detected_os": "RHEL", + "activation_status": "INACTIVE", + "dnf_metadata_status": "UNAVAILABLE", + })} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + assert.Empty(t, drainToCentral(s), "should not emit a VM update when agent facts are unchanged") +} + +func TestVMScraper_ForwardsAgentVersionChangeOnUnchangedReport(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.pollOnce(context.Background()) + require.Equal(t, 1, forwardedCount(s)) + + sameFacts := map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + } + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{{ + Unchanged: true, + Meta: &pb.ResponseMeta{ + ReportToken: "1", + AgentVersion: "roxagent-upgraded", + Facts: sameFacts, + }, + }} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + + expected := virtualmachine.AgentFactsFromResponse(sameFacts, "roxagent-upgraded") + updates := drainVMUpdates(s) + require.Len(t, updates, 1) + assert.Equal(t, expected[pkgVM.AgentVersionKey], updates[0].GetFacts()[pkgVM.AgentVersionKey]) + assert.Equal(t, expected, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) +} + +func TestVMScraper_RetriesAgentFactsAfterIndexEnqueueFillsBuffer(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.toCentral = make(chan *message.ExpiringMessage, 1) + + s.pollOnce(context.Background()) + msgs := drainToCentral(s) + require.Len(t, msgs, 1) + assert.NotNil(t, msgs[0].GetEvent().GetVirtualMachineIndexReport()) + assert.Nil(t, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) + + sameFacts := map[string]string{ + "detected_os": "RHEL", + "activation_status": "ACTIVE", + "dnf_metadata_status": "AVAILABLE", + } + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{{ + Unchanged: true, + Meta: &pb.ResponseMeta{ + ReportToken: "1", + AgentVersion: "roxagent-test", + Facts: sameFacts, + }, + }} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + + expected := virtualmachine.AgentFactsFromResponse(sameFacts, "roxagent-test") + updates := drainVMUpdates(s) + require.Len(t, updates, 1) + assert.Equal(t, expected[pkgVM.ActivationStatusKey], updates[0].GetFacts()[pkgVM.ActivationStatusKey]) + assert.Equal(t, expected, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) +} + +func TestVMScraper_SkipsFactsUpdateWhenUnchangedOnNewIndex(t *testing.T) { + store := &mockStore{vms: []*virtualmachine.Info{ + makeVM("ns1", "vm-a", 100), + }} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.pollOnce(context.Background()) + require.Equal(t, 1, forwardedCount(s)) + require.NotNil(t, store.Get(virtualmachine.VMID("ns1/vm-a")).AgentFacts) + + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} + clock.Advance(s.interval) + s.pollOnce(context.Background()) + + msgs := drainToCentral(s) + require.Len(t, msgs, 1) + assert.NotNil(t, msgs[0].GetEvent().GetVirtualMachineIndexReport()) + assert.Nil(t, msgs[0].GetEvent().GetVirtualMachine()) +} + func TestVMScraper_RemainsScheduledAcrossUnchangedPolls(t *testing.T) { store := &mockStore{vms: []*virtualmachine.Info{ makeVM("ns1", "vm-a", 100), @@ -553,8 +814,11 @@ func TestVMScraper_NACK(t *testing.T) { require.Equal(t, 1, forwardedCount(s)) // Flip Running only after the first poll, so the VM is scraped normally - // once before the ACK/NACK under test is delivered. - vmA.Running = tc.vmRunning + // once before the ACK/NACK under test is delivered. The scraper + // stores a copy, so mutate the store's current object rather than vmA. + concurrency.WithLock(&store.mu, func() { + store.vms[0].Running = tc.vmRunning + }) acksBefore := testutil.ToFloat64(metrics.IndexReportAcksReceived.WithLabelValues(tc.ackAction.String())) err := s.ProcessMessage(context.Background(), ¢ral.MsgToSensor{ @@ -1085,18 +1349,42 @@ func TestIsAbnormalClose(t *testing.T) { } } -func forwardedCount(s *VMScraper) int { - n := 0 +func drainToCentral(s *VMScraper) []*message.ExpiringMessage { + var out []*message.ExpiringMessage for { select { - case <-s.toCentral: - n++ + case msg := <-s.toCentral: + out = append(out, msg) default: - return n + return out + } + } +} + +func forwardedCount(s *VMScraper) int { + n := 0 + for _, msg := range drainToCentral(s) { + if msg.GetEvent().GetVirtualMachineIndexReport() != nil { + n++ } } + return n } +func drainVMUpdates(s *VMScraper) []*pb.VirtualMachine { + var out []*pb.VirtualMachine + for _, msg := range drainToCentral(s) { + if vm := msg.GetEvent().GetVirtualMachine(); vm != nil { + out = append(out, vm) + } + } + return out +} + +type staticClusterID string + +func (c staticClusterID) GetNoWait() string { return string(c) } + func newTestScraper(t *testing.T, store RunningVMStore, dialer VMDialer, client ProtocolClient) (*VMScraper, *testClock) { t.Helper() centralcaps.Set([]centralsensor.CentralCapability{centralsensor.VirtualMachinesSupported}) @@ -1108,6 +1396,7 @@ func newTestScraper(t *testing.T, store RunningVMStore, dialer VMDialer, client store: store, dialer: dialer, client: client, + clusterID: staticClusterID("test-cluster"), toCentral: make(chan *message.ExpiringMessage, 256), centralReady: concurrency.NewSignal(), interval: interval, diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go index 28f85afcfbb1b..abf2dac6721bd 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/utils.go @@ -21,3 +21,16 @@ func getVirtualMachineOwnerReference(owners []metav1.OwnerReference) (*metav1.Ow func createEvent(action central.ResourceAction, clusterID string, vm *sensorVirtualMachine.Info) *central.SensorEvent { return sensorVirtualMachine.SensorEvent(action, clusterID, vm) } + +// attachStoredAgentFacts copies scrape-owned facts onto vm. Informer UPDATEs are +// built from the VMI and would otherwise replace Central's facts map without them. +func attachStoredAgentFacts(store virtualMachineStore, vm *sensorVirtualMachine.Info) { + if vm == nil { + return + } + stored := store.Get(vm.ID) + if stored == nil || len(stored.AgentFacts) == 0 { + return + } + vm.AgentFacts = stored.AgentFacts +} diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go index 7bcbbb0f1a699..4bdddbafc6ee0 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go @@ -110,6 +110,8 @@ func (d *VirtualMachineInstanceDispatcher) ProcessEvent( return nil } + attachStoredAgentFacts(d.store, vm) + // Send an Update event for the VirtualMachine that handles this instance return component.NewEvent(createEvent(central.ResourceAction_UPDATE_RESOURCE, d.clusterID, vm)) } diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go index d507df2c629b9..0903d10d571b4 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go @@ -104,6 +104,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { Running: false, }), ).Times(1), + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(nil), ) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ @@ -136,6 +137,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { Running: false, }), ).Times(1), + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(nil), ) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ @@ -160,6 +162,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { gomock.InOrder( s.store.EXPECT().Has(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(true), s.store.EXPECT().ClearState(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1), + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(nil), ) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ @@ -177,6 +180,45 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { }, }), }, + "remove event keeps stored agent facts": { + action: central.ResourceAction_REMOVE_RESOURCE, + obj: toUnstructured(newVirtualMachineInstance(vmiUID, vmiName, vmiNamespace, ownerUID, nil, v1.Scheduled)), + expectFn: func() { + gomock.InOrder( + s.store.EXPECT().Has(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(true), + s.store.EXPECT().ClearState(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1), + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(&vmInfo.Info{ + ID: ownerUID, + AgentFacts: map[string]string{ + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.8", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + pkgVM.DNFMetadataStatusKey: pkgVM.DNFMetadataStatusAvailable, + pkgVM.AgentVersionKey: "development", + }, + }), + ) + }, + expectedMsg: component.NewEvent(¢ral.SensorEvent{ + Id: ownerUID, + Action: central.ResourceAction_UPDATE_RESOURCE, + Resource: ¢ral.SensorEvent_VirtualMachine{ + VirtualMachine: &virtualMachineV1.VirtualMachine{ + Id: ownerUID, + Name: vmiName, + Namespace: vmiNamespace, + ClusterId: clusterID, + State: virtualMachineV1.VirtualMachine_STOPPED, + Facts: map[string]string{ + pkgVM.GuestOSKey: pkgVM.UnknownGuestOS, + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.8", + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + pkgVM.DNFMetadataStatusKey: pkgVM.DNFMetadataStatusAvailable, + pkgVM.AgentVersionKey: "development", + }, + }, + }, + }), + }, "no unstructured object": { action: central.ResourceAction_REMOVE_RESOURCE, obj: newVirtualMachineInstance(vmiUID, vmiName, vmiNamespace, ownerUID, nil, v1.Scheduled), @@ -352,6 +394,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { Running: true, }), ).Times(1), + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(ownerUID))).Times(1).Return(nil), ) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ diff --git a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go index 74a4fd58cc227..29a5384f12277 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store.go @@ -1,6 +1,8 @@ package store import ( + "maps" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" @@ -45,11 +47,26 @@ func (s *VirtualMachineStore) AddOrUpdate(vm *virtualmachine.Info) *virtualmachi vm.IPAddresses = copyStringSlice(oldVM.IPAddresses) vm.ActivePods = copyStringSlice(oldVM.ActivePods) vm.NodeName = oldVM.NodeName + if vm.AgentFacts == nil { + vm.AgentFacts = oldVM.AgentFacts + } } s.addOrUpdateNoLock(vm) return vm } +// SetAgentFacts replaces scrape-owned facts on an existing VM. A missing ID +// is ignored so persist cannot recreate a VM the informer already removed. +func (s *VirtualMachineStore) SetAgentFacts(id virtualmachine.VMID, facts map[string]string) { + s.lock.Lock() + defer s.lock.Unlock() + vm, ok := s.virtualMachines[id] + if !ok { + return + } + vm.AgentFacts = maps.Clone(facts) +} + // UpdateStateOrCreate updates the VirtualMachine state // If the VirtualMachine is not present we create a new VirtualMachine func (s *VirtualMachineStore) UpdateStateOrCreate(vm *virtualmachine.Info) { @@ -131,6 +148,7 @@ func (s *VirtualMachineStore) addOrUpdateNoLock(vm *virtualmachine.Info) { // Upsert the VirtualMachineInfo vmIDsByNamespace := s.getOrCreateNamespaceSet(vm.Namespace) vmIDsByNamespace.Add(vm.ID) + vm.AgentFacts = maps.Clone(vm.AgentFacts) s.virtualMachines[vm.ID] = vm } @@ -160,6 +178,9 @@ func (s *VirtualMachineStore) updateStatusOrCreateNoLock(updateInfo *virtualmach prev.Description = updateInfo.Description prev.BootOrder = copyStringSlice(updateInfo.BootOrder) prev.CDRomDisks = copyStringSlice(updateInfo.CDRomDisks) + if updateInfo.AgentFacts != nil { + prev.AgentFacts = maps.Clone(updateInfo.AgentFacts) + } } // copyVSOCKCID returns a new pointer so later changes to the caller's value diff --git a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go index 0c193d0c28e51..c984cb820dd47 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/store/virtual_machine_store_test.go @@ -3,6 +3,7 @@ package store import ( "testing" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" "github.com/stackrox/rox/sensor/common/virtualmachine" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -716,6 +717,56 @@ func (s *storeSuite) Test_UpdateStateOrCreateShouldRefreshRuntimeFields() { } } +func (s *storeSuite) Test_RuntimeUpdatesShouldPreserveAgentFacts() { + agentFacts := map[string]string{ + pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive, + } + + s.Run("AddOrUpdate preserves when nil", func() { + s.store.AddOrUpdate(&virtualmachine.Info{ + ID: vmID, Name: vmName, Namespace: vmNamespace, AgentFacts: agentFacts, + }) + s.store.AddOrUpdate(&virtualmachine.Info{ + ID: vmID, Name: vmName, Namespace: vmNamespace, Description: "updated", + }) + assert.Equal(s.T(), agentFacts, s.store.Get(vmID).AgentFacts) + }) + + s.Run("UpdateStateOrCreate preserves when nil", func() { + s.store = NewVirtualMachineStore() + s.store.AddOrUpdate(&virtualmachine.Info{ + ID: vmID, Name: vmName, Namespace: vmNamespace, AgentFacts: agentFacts, + }) + s.store.UpdateStateOrCreate(&virtualmachine.Info{ + ID: vmID, Name: vmName, Namespace: vmNamespace, Running: true, VSOCKCID: new(uint32(1)), + }) + assert.Equal(s.T(), agentFacts, s.store.Get(vmID).AgentFacts) + }) +} + +func (s *storeSuite) Test_SetAgentFacts() { + s.Run("replaces facts on an existing VM", func() { + s.store.AddOrUpdate(&virtualmachine.Info{ + ID: vmID, + Name: vmName, + Namespace: vmNamespace, + GuestOS: "from-informer", + Description: "kept", + }) + facts := map[string]string{pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive} + s.store.SetAgentFacts(vmID, facts) + got := s.store.Get(vmID) + assert.Equal(s.T(), facts, got.AgentFacts) + assert.Equal(s.T(), "from-informer", got.GuestOS) + assert.Equal(s.T(), "kept", got.Description) + }) + + s.Run("ignores a missing VM", func() { + s.store.SetAgentFacts(vmID, map[string]string{pkgVM.ActivationStatusKey: pkgVM.ActivationStatusActive}) + assert.Nil(s.T(), s.store.Get(vmID)) + }) +} + func (s *storeSuite) Test_RemoveVirtualMachine() { vsockCID1 := new(uint32(1)) cases := map[string]struct { diff --git a/sensor/kubernetes/sensor/sensor.go b/sensor/kubernetes/sensor/sensor.go index 06996112a0c7c..375e6da32f837 100644 --- a/sensor/kubernetes/sensor/sensor.go +++ b/sensor/kubernetes/sensor/sensor.go @@ -212,7 +212,7 @@ func CreateSensor(cfg *CreateOptions) (*sensor.Sensor, error) { if repo2CPE != nil { repo2CPEFetcher = repo2CPE } - vmScraper = vmscraper.New(storeProvider.VirtualMachines(), dialer, vmProtoClient, repo2CPEFetcher) + vmScraper = vmscraper.New(storeProvider.VirtualMachines(), dialer, vmProtoClient, repo2CPEFetcher, clusterID) vmStats = vmScraper } diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index 14e6252436093..032714ed2ce19 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -7,6 +7,7 @@ import ( "time" v2 "github.com/stackrox/rox/generated/api/v2" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" "github.com/stackrox/rox/tests/vmhelpers" "github.com/stretchr/testify/require" ) @@ -68,6 +69,7 @@ func (s *VMScanningSuite) TestScanPipeline() { require.Equal(t, v2.VirtualMachine_RUNNING, first.GetState()) require.NotNil(t, first.GetScan()) require.NotNil(t, first.GetScan().GetScanTime()) + requireForwardedAgentFacts(t, first.GetFacts()) }) t.Run("CentralScanComponents", func(t *testing.T) { @@ -105,6 +107,7 @@ func (s *VMScanningSuite) TestScanPipeline() { require.Equal(t, v2.VirtualMachineV2State_VM_STATE_RUNNING, detail.GetState()) require.NotNil(t, detail.GetLatestScan()) require.NotNil(t, detail.GetLatestScan().GetScanTime()) + requireForwardedAgentFacts(t, detail.GetFacts()) }) t.Run("VirtualMachineV2ListVMs", func(t *testing.T) { @@ -265,6 +268,15 @@ func componentRowNames(comps []*v2.VMComponentRow) []string { return names } +func requireForwardedAgentFacts(t *testing.T, facts map[string]string) { + t.Helper() + require.NotEmpty(t, facts) + require.Regexp(t, `^Red Hat Enterprise Linux \d`, facts[pkgVM.DetectedGuestOSKey], + "facts.detectedGuestOS should be the versioned guest OS from roxagent") + require.NotEmpty(t, facts[pkgVM.AgentVersionKey], + "facts.agentVersion should be the roxagent version from ResponseMeta") +} + func distinctCVEIDs(cves []*v2.VMCVERow) []string { seen := make(map[string]struct{}, len(cves)) ids := make([]string, 0, len(cves)) From 867fcfc51588a860ef1c205986feb5a77356b59c Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:09:10 +0200 Subject: [PATCH 228/638] ROX-36802: Populate GetVM agent_status from scrape freshness (#22650) --- .../convert/storagetov2/virtual_machine_v2.go | 14 +++ .../storagetov2/virtual_machine_v2_test.go | 39 ++++++++ .../virtualmachine/v2/datastore/datastore.go | 3 +- .../v2/datastore/store/postgres/store.go | 27 ++++-- .../v2/datastore/store/postgres/store_test.go | 54 +++++++++++ .../v2/datastore/store/store.go | 3 +- .../virtualmachine/v2/service/service_impl.go | 7 ++ .../v2/service/service_impl_test.go | 80 +++++++++++++++++ .../api/v2/virtual_machine_v2_service.pb.go | 19 ++-- .../virtual_machine_v2_service.swagger.json | 5 +- generated/storage/virtual_machine_v2.pb.go | 38 +++++--- .../storage/virtual_machine_v2_vtproto.pb.go | 90 +++++++++++++++++++ pkg/env/virtualmachine.go | 5 ++ pkg/postgres/schema/virtual_machine_v2.go | 19 ++-- pkg/search/options.go | 17 ++-- proto/api/v2/virtual_machine_v2_service.proto | 4 +- proto/storage/proto.lock | 5 ++ proto/storage/virtual_machine_v2.proto | 4 + .../VirtualMachine/virtualMachineUtils.ts | 1 + .../src/services/VirtualMachineService.ts | 2 +- 20 files changed, 385 insertions(+), 51 deletions(-) diff --git a/central/convert/storagetov2/virtual_machine_v2.go b/central/convert/storagetov2/virtual_machine_v2.go index b1d32d718b991..c93dbcf03efbd 100644 --- a/central/convert/storagetov2/virtual_machine_v2.go +++ b/central/convert/storagetov2/virtual_machine_v2.go @@ -2,10 +2,12 @@ package storagetov2 import ( "slices" + "time" "github.com/stackrox/rox/central/views/common" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" + "google.golang.org/protobuf/types/known/timestamppb" ) // VirtualMachineV2ToListItem converts a storage VM V2 to an API list item. @@ -49,6 +51,18 @@ func VirtualMachineV2ToDetail(vm *storage.VirtualMachineV2) *v2.VMDetail { } } +// AgentStatusFromLastContact maps scrape freshness onto the GetVM enum. +// A nil/invalid timestamp is UNKNOWN; age >= staleAfter is INACTIVE. +func AgentStatusFromLastContact(ts *timestamppb.Timestamp, now time.Time, staleAfter time.Duration) v2.AgentStatus { + if ts == nil || !ts.IsValid() { + return v2.AgentStatus_AGENT_STATUS_UNKNOWN + } + if now.Sub(ts.AsTime()) < staleAfter { + return v2.AgentStatus_AGENT_STATUS_ACTIVE + } + return v2.AgentStatus_AGENT_STATUS_INACTIVE +} + func convertVMNote(note storage.VirtualMachineV2_Note) v2.VMNote { switch note { case storage.VirtualMachineV2_MISSING_SCAN_DATA: diff --git a/central/convert/storagetov2/virtual_machine_v2_test.go b/central/convert/storagetov2/virtual_machine_v2_test.go index c62cdaf940aa5..fa1a5fe3e1f93 100644 --- a/central/convert/storagetov2/virtual_machine_v2_test.go +++ b/central/convert/storagetov2/virtual_machine_v2_test.go @@ -2,10 +2,12 @@ package storagetov2 import ( "testing" + "time" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" ) func TestVirtualMachineV2ToDetail_Notes(t *testing.T) { @@ -50,3 +52,40 @@ func TestVirtualMachineV2ToDetail_Notes(t *testing.T) { }) } } + +func TestAgentStatusFromLastContact(t *testing.T) { + now := time.Unix(1_700_000_000, 0).UTC() + staleAfter := 12 * time.Hour + + tests := map[string]struct { + ts *timestamppb.Timestamp + expected v2.AgentStatus + }{ + "should return unknown when never scraped": { + ts: nil, + expected: v2.AgentStatus_AGENT_STATUS_UNKNOWN, + }, + "should return unknown when timestamp is invalid": { + ts: ×tamppb.Timestamp{Nanos: 2_000_000_000}, + expected: v2.AgentStatus_AGENT_STATUS_UNKNOWN, + }, + "should return active when last scrape is still inside the window": { + ts: timestamppb.New(now.Add(-time.Hour)), + expected: v2.AgentStatus_AGENT_STATUS_ACTIVE, + }, + "should return inactive when last scrape is exactly the window": { + ts: timestamppb.New(now.Add(-staleAfter)), + expected: v2.AgentStatus_AGENT_STATUS_INACTIVE, + }, + "should return inactive when last scrape is older than the window": { + ts: timestamppb.New(now.Add(-13 * time.Hour)), + expected: v2.AgentStatus_AGENT_STATUS_INACTIVE, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expected, AgentStatusFromLastContact(tc.ts, now, staleAfter)) + }) + } +} diff --git a/central/virtualmachine/v2/datastore/datastore.go b/central/virtualmachine/v2/datastore/datastore.go index f3f57ce59c660..940a03a9dddbd 100644 --- a/central/virtualmachine/v2/datastore/datastore.go +++ b/central/virtualmachine/v2/datastore/datastore.go @@ -32,7 +32,8 @@ type DataStore interface { // UpsertScan upserts scan data (scan, components, CVEs) for a VM. // Hash-based change detection avoids unnecessary writes. CVE created_at - // timestamps are preserved across scan replacements. + // timestamps are preserved across scan replacements. Also stamps + // last_agent_contact: this is the scrape-derived path. UpsertScan(ctx context.Context, vmID string, parts common.VMScanParts) error // DeleteVirtualMachines removes VMs and all associated data (FK cascade). diff --git a/central/virtualmachine/v2/datastore/store/postgres/store.go b/central/virtualmachine/v2/datastore/store/postgres/store.go index 01661dac8d97d..f54c1f0990c29 100644 --- a/central/virtualmachine/v2/datastore/store/postgres/store.go +++ b/central/virtualmachine/v2/datastore/store/postgres/store.go @@ -69,8 +69,8 @@ func (s *storeImpl) EnsureExists(ctx context.Context, vmID, clusterID string) er } const stmt = "INSERT INTO " + vmTable + - " (Id, Name, Namespace, ClusterId, ClusterName, GuestOs, State, LastUpdated, serialized)" + - " VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)" + + " (Id, Name, Namespace, ClusterId, ClusterName, GuestOs, State, LastUpdated, LastAgentContact, serialized)" + + " VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)" + " ON CONFLICT(Id) DO NOTHING" _, err := s.db.Exec(ctx, stmt, @@ -82,6 +82,7 @@ func (s *storeImpl) EnsureExists(ctx context.Context, vmID, clusterID string) er vm.GetGuestOs(), vm.GetState(), protocompat.NilOrTime(vm.GetLastUpdated()), + protocompat.NilOrTime(vm.GetLastAgentContact()), serialized, ) return err @@ -126,6 +127,11 @@ func (s *storeImpl) upsertVM(ctx context.Context, vm *storage.VirtualMachineV2) return err } + // Sensor informer upserts never carry last_agent_contact; keep the scrape stamp. + if existingVM != nil && vm.GetLastAgentContact() == nil { + vm.LastAgentContact = existingVM.GetLastAgentContact() + } + if existingVM != nil && existingVM.GetHash() == hash { // Unchanged: timestamp-only update. Preserve the existing hash. vm.Hash = hash @@ -174,17 +180,19 @@ func (s *storeImpl) insertVM(ctx context.Context, tx *postgres.Tx, vm *storage.V vm.GetGuestOs(), vm.GetState(), protocompat.NilOrTime(vm.GetLastUpdated()), + protocompat.NilOrTime(vm.GetLastAgentContact()), serialized, } const stmt = "INSERT INTO " + vmTable + - " (Id, Name, Namespace, ClusterId, ClusterName, GuestOs, State, LastUpdated, serialized)" + - " VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)" + + " (Id, Name, Namespace, ClusterId, ClusterName, GuestOs, State, LastUpdated, LastAgentContact, serialized)" + + " VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)" + " ON CONFLICT(Id) DO UPDATE SET" + " Id = EXCLUDED.Id, Name = EXCLUDED.Name, Namespace = EXCLUDED.Namespace," + " ClusterId = EXCLUDED.ClusterId, ClusterName = EXCLUDED.ClusterName," + " GuestOs = EXCLUDED.GuestOs, State = EXCLUDED.State," + - " LastUpdated = EXCLUDED.LastUpdated, serialized = EXCLUDED.serialized" + " LastUpdated = EXCLUDED.LastUpdated, LastAgentContact = EXCLUDED.LastAgentContact," + + " serialized = EXCLUDED.serialized" _, err = tx.Exec(ctx, stmt, values...) return err } @@ -200,8 +208,8 @@ func (s *storeImpl) updateVMTimestamp(ctx context.Context, tx *postgres.Tx, vm * return err } - const stmt = "UPDATE " + vmTable + " SET LastUpdated = $2, serialized = $3 WHERE Id = $1" - _, err = tx.Exec(ctx, stmt, id, protocompat.NilOrTime(vm.GetLastUpdated()), serialized) + const stmt = "UPDATE " + vmTable + " SET LastUpdated = $2, LastAgentContact = $3, serialized = $4 WHERE Id = $1" + _, err = tx.Exec(ctx, stmt, id, protocompat.NilOrTime(vm.GetLastUpdated()), protocompat.NilOrTime(vm.GetLastAgentContact()), serialized) return err } @@ -313,7 +321,10 @@ func (s *storeImpl) touchVMLastUpdated(ctx context.Context, tx *postgres.Tx, vmI if existingVM == nil { return errox.NotFound.Newf("VM %s not found", vmID) } - existingVM.LastUpdated = protocompat.ConvertTimeToTimestampOrNil(&t) + ts := protocompat.ConvertTimeToTimestampOrNil(&t) + existingVM.LastUpdated = ts + // UpsertScan is the scrape-derived path (index-report SYNC), so last contact moves with last_updated. + existingVM.LastAgentContact = ts return s.updateVMTimestamp(ctx, tx, existingVM) } diff --git a/central/virtualmachine/v2/datastore/store/postgres/store_test.go b/central/virtualmachine/v2/datastore/store/postgres/store_test.go index 911d1d0238d68..8f9faf9624030 100644 --- a/central/virtualmachine/v2/datastore/store/postgres/store_test.go +++ b/central/virtualmachine/v2/datastore/store/postgres/store_test.go @@ -177,6 +177,60 @@ func (s *VMStoreTestSuite) TestUpsertVM_Changed() { s.NotEqual(firstGet.GetHash(), secondGet.GetHash(), "hash should change when data changes") } +func (s *VMStoreTestSuite) TestUpsertScan_StampsLastAgentContact() { + vm := s.newVM() + s.NoError(s.store.UpsertVM(s.ctx, vm)) + + beforeScan, _, err := s.store.Get(s.ctx, vm.GetId()) + s.NoError(err) + s.Nil(beforeScan.GetLastAgentContact()) + + parts := s.newScanParts(vm.GetId()) + before := time.Now() + s.NoError(s.store.UpsertScan(s.ctx, vm.GetId(), parts)) + after := time.Now() + + afterScan, _, err := s.store.Get(s.ctx, vm.GetId()) + s.NoError(err) + s.Require().NotNil(afterScan.GetLastAgentContact()) + contact := afterScan.GetLastAgentContact().AsTime() + s.False(contact.Before(before), "LastAgentContact %v is before UpsertScan started at %v", contact, before) + s.False(contact.After(after), "LastAgentContact %v is after UpsertScan finished at %v", contact, after) +} + +func (s *VMStoreTestSuite) TestUpsertVM_PreservesLastAgentContactWhenInformerOmitsIt() { + for name, mutate := range map[string]func(*storage.VirtualMachineV2){ + "unchanged upsert": func(*storage.VirtualMachineV2) {}, + "changed upsert": func(informerUpdate *storage.VirtualMachineV2) { + informerUpdate.State = storage.VirtualMachineV2_STOPPED + }, + } { + s.Run(name, func() { + vm := s.newVM() + s.NoError(s.store.UpsertVM(s.ctx, vm)) + + parts := s.newScanParts(vm.GetId()) + s.NoError(s.store.UpsertScan(s.ctx, vm.GetId(), parts)) + + afterScan, _, err := s.store.Get(s.ctx, vm.GetId()) + s.NoError(err) + s.Require().NotNil(afterScan.GetLastAgentContact()) + contact := afterScan.GetLastAgentContact().AsTime() + + informerUpdate := vm.CloneVT() + informerUpdate.LastUpdated = nil + informerUpdate.Hash = 0 + informerUpdate.LastAgentContact = nil + mutate(informerUpdate) + s.NoError(s.store.UpsertVM(s.ctx, informerUpdate)) + + got, _, err := s.store.Get(s.ctx, vm.GetId()) + s.NoError(err) + s.Equal(contact, got.GetLastAgentContact().AsTime()) + }) + } +} + // endregion UpsertVM tests // region UpsertScan tests diff --git a/central/virtualmachine/v2/datastore/store/store.go b/central/virtualmachine/v2/datastore/store/store.go index 6246f0affd48f..c16f45b452d39 100644 --- a/central/virtualmachine/v2/datastore/store/store.go +++ b/central/virtualmachine/v2/datastore/store/store.go @@ -27,7 +27,8 @@ type Store interface { // UpsertScan upserts scan data (scan, components, CVEs) for a VM. // Hash-comparison determines whether a full replace or scan_time-only // update is performed. CVE created_at timestamps are preserved across - // delete/re-insert cycles. + // delete/re-insert cycles. Also stamps last_agent_contact: this is the + // scrape-derived path. // NOTE: Mutates parts in-place (Scan.ScanTime, Scan.Hash, CVE CreatedAt). UpsertScan(ctx context.Context, vmID string, parts common.VMScanParts) error diff --git a/central/virtualmachine/v2/service/service_impl.go b/central/virtualmachine/v2/service/service_impl.go index e895b56e82859..47b4e58823d85 100644 --- a/central/virtualmachine/v2/service/service_impl.go +++ b/central/virtualmachine/v2/service/service_impl.go @@ -3,6 +3,7 @@ package service import ( "context" "slices" + "time" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/pkg/errors" @@ -17,6 +18,7 @@ import ( v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/grpc/authz" "github.com/stackrox/rox/pkg/grpc/authz/perrpc" "github.com/stackrox/rox/pkg/grpc/authz/user" @@ -654,6 +656,11 @@ func (s *serviceImpl) GetVM(ctx context.Context, request *v2.GetVMRequest) (*v2. } detail := storagetov2.VirtualMachineV2ToDetail(vm) + detail.AgentStatus = storagetov2.AgentStatusFromLastContact( + vm.GetLastAgentContact(), + time.Now(), + env.VirtualMachinesAgentStaleAfter.DurationSetting(), + ) // Get the latest scan for this VM. Scan IDs are UUIDv7 (time-sortable), // so sorting by the primary key is equivalent to sorting by time and avoids diff --git a/central/virtualmachine/v2/service/service_impl_test.go b/central/virtualmachine/v2/service/service_impl_test.go index 383443a1ce712..eb59402e13b6c 100644 --- a/central/virtualmachine/v2/service/service_impl_test.go +++ b/central/virtualmachine/v2/service/service_impl_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" ) type testCVEForVM struct { @@ -754,6 +755,85 @@ func TestGetVM(t *testing.T) { }, expectedResult: storagetov2.VirtualMachineV2ToDetail(vm1), }, + "never scraped stays unknown with vsock cid and agent version": { + request: &v2.GetVMRequest{ + Id: testVMID, + }, + setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { + neverScraped := &storage.VirtualMachineV2{ + Id: testVMID, + Name: "test-vm-1", + State: storage.VirtualMachineV2_RUNNING, + VsockCid: 42, + Facts: map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"}, + } + mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(neverScraped, true, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + }, + expectedResult: func() *v2.VMDetail { + detail := storagetov2.VirtualMachineV2ToDetail(&storage.VirtualMachineV2{ + Id: testVMID, + Name: "test-vm-1", + State: storage.VirtualMachineV2_RUNNING, + VsockCid: 42, + Facts: map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"}, + }) + detail.AgentStatus = v2.AgentStatus_AGENT_STATUS_UNKNOWN + return detail + }(), + }, + "fresh scrape is active": { + request: &v2.GetVMRequest{ + Id: testVMID, + }, + setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { + fresh := vm1.CloneVT() + fresh.LastAgentContact = timestamppb.New(time.Now().Add(-time.Hour)) + mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(fresh, true, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + }, + expectedResult: func() *v2.VMDetail { + detail := storagetov2.VirtualMachineV2ToDetail(vm1) + detail.AgentStatus = v2.AgentStatus_AGENT_STATUS_ACTIVE + return detail + }(), + }, + "activation status does not affect agent status": { + request: &v2.GetVMRequest{ + Id: testVMID, + }, + setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { + fresh := vm1.CloneVT() + fresh.Facts = map[string]string{"activationStatus": "inactive"} + fresh.LastAgentContact = timestamppb.New(time.Now().Add(-time.Hour)) + mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(fresh, true, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + }, + expectedResult: func() *v2.VMDetail { + detail := storagetov2.VirtualMachineV2ToDetail(vm1) + detail.Facts = map[string]string{"activationStatus": "inactive"} + detail.AgentStatus = v2.AgentStatus_AGENT_STATUS_ACTIVE + return detail + }(), + }, + "stale scrape is inactive even when agent version is still set": { + request: &v2.GetVMRequest{ + Id: testVMID, + }, + setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { + stale := vm1.CloneVT() + stale.Facts = map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"} + stale.LastAgentContact = timestamppb.New(time.Now().Add(-13 * time.Hour)) + mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(stale, true, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + }, + expectedResult: func() *v2.VMDetail { + detail := storagetov2.VirtualMachineV2ToDetail(vm1) + detail.Facts = map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"} + detail.AgentStatus = v2.AgentStatus_AGENT_STATUS_INACTIVE + return detail + }(), + }, "datastore error": { request: &v2.GetVMRequest{ Id: testVMID, diff --git a/generated/api/v2/virtual_machine_v2_service.pb.go b/generated/api/v2/virtual_machine_v2_service.pb.go index 55fb3c8513dc1..61bd7b26f1f40 100644 --- a/generated/api/v2/virtual_machine_v2_service.pb.go +++ b/generated/api/v2/virtual_machine_v2_service.pb.go @@ -179,12 +179,14 @@ func (VMNote) EnumDescriptor() ([]byte, []int) { return file_api_v2_virtual_machine_v2_service_proto_rawDescGZIP(), []int{2} } -// Agent status enriched from a separate data source (not on VirtualMachineV2 storage). +// AgentStatus is roxagent liveness from scrape freshness, computed on GetVM +// (not a stored enum). UNKNOWN means never scraped; INACTIVE means scraped, then quiet. type AgentStatus int32 const ( - AgentStatus_AGENT_STATUS_UNKNOWN AgentStatus = 0 - AgentStatus_AGENT_STATUS_ACTIVE AgentStatus = 1 + AgentStatus_AGENT_STATUS_UNKNOWN AgentStatus = 0 + AgentStatus_AGENT_STATUS_ACTIVE AgentStatus = 1 + AgentStatus_AGENT_STATUS_INACTIVE AgentStatus = 2 ) // Enum value maps for AgentStatus. @@ -192,10 +194,12 @@ var ( AgentStatus_name = map[int32]string{ 0: "AGENT_STATUS_UNKNOWN", 1: "AGENT_STATUS_ACTIVE", + 2: "AGENT_STATUS_INACTIVE", } AgentStatus_value = map[string]int32{ - "AGENT_STATUS_UNKNOWN": 0, - "AGENT_STATUS_ACTIVE": 1, + "AGENT_STATUS_UNKNOWN": 0, + "AGENT_STATUS_ACTIVE": 1, + "AGENT_STATUS_INACTIVE": 2, } ) @@ -2537,10 +2541,11 @@ const file_api_v2_virtual_machine_v2_service_proto_rawDesc = "" + "\x19VM_NOTE_MISSING_SIGNATURE\x10\x02\x12/\n" + "+VM_NOTE_MISSING_SIGNATURE_VERIFICATION_DATA\x10\x03\x12\x1b\n" + "\x17VM_NOTE_MISSING_SCANNER\x10\x04\x12\x17\n" + - "\x13VM_NOTE_SCAN_FAILED\x10\x05*@\n" + + "\x13VM_NOTE_SCAN_FAILED\x10\x05*[\n" + "\vAgentStatus\x12\x18\n" + "\x14AGENT_STATUS_UNKNOWN\x10\x00\x12\x17\n" + - "\x13AGENT_STATUS_ACTIVE\x10\x01*_\n" + + "\x13AGENT_STATUS_ACTIVE\x10\x01\x12\x19\n" + + "\x15AGENT_STATUS_INACTIVE\x10\x02*_\n" + "\n" + "ScanStatus\x12\x0f\n" + "\vNOT_SCANNED\x10\x00\x12\x10\n" + diff --git a/generated/api/v2/virtual_machine_v2_service.swagger.json b/generated/api/v2/virtual_machine_v2_service.swagger.json index 2cdd381899c79..39b003a344de8 100644 --- a/generated/api/v2/virtual_machine_v2_service.swagger.json +++ b/generated/api/v2/virtual_machine_v2_service.swagger.json @@ -767,10 +767,11 @@ "type": "string", "enum": [ "AGENT_STATUS_UNKNOWN", - "AGENT_STATUS_ACTIVE" + "AGENT_STATUS_ACTIVE", + "AGENT_STATUS_INACTIVE" ], "default": "AGENT_STATUS_UNKNOWN", - "description": "Agent status enriched from a separate data source (not on VirtualMachineV2 storage)." + "description": "AgentStatus is roxagent liveness from scrape freshness, computed on GetVM\n(not a stored enum). UNKNOWN means never scraped; INACTIVE means scraped, then quiet." }, "v2AggregateBy": { "type": "object", diff --git a/generated/storage/virtual_machine_v2.pb.go b/generated/storage/virtual_machine_v2.pb.go index eaa2db2556ba6..c9e7e603482a2 100644 --- a/generated/storage/virtual_machine_v2.pb.go +++ b/generated/storage/virtual_machine_v2.pb.go @@ -144,11 +144,14 @@ type VirtualMachineV2 struct { // KubeVirt facts about the VM. Facts map[string]string `protobuf:"bytes,6,rep,name=facts,proto3" json:"facts,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Guest OS reported by KubeVirt facts, extracted as a queryable column. - GuestOs string `protobuf:"bytes,7,opt,name=guest_os,json=guestOs,proto3" json:"guest_os,omitempty" search:"Guest OS" sql:"index=btree"` // @gotags: search:"Guest OS" sql:"index=btree" - State VirtualMachineV2_State `protobuf:"varint,8,opt,name=state,proto3,enum=storage.VirtualMachineV2_State" json:"state,omitempty" search:"Virtual Machine State"` // @gotags: search:"Virtual Machine State" - LastUpdated *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty" search:"Last Updated,hidden" sql:"type(timestamptz)" hash:"ignore"` // @gotags: search:"Last Updated,hidden" sql:"type(timestamptz)" hash:"ignore" - Notes []VirtualMachineV2_Note `protobuf:"varint,10,rep,packed,name=notes,proto3,enum=storage.VirtualMachineV2_Note" json:"notes,omitempty" hash:"set"` // @gotags: hash:"set" - VsockCid int32 `protobuf:"varint,11,opt,name=vsock_cid,json=vsockCid,proto3" json:"vsock_cid,omitempty"` + GuestOs string `protobuf:"bytes,7,opt,name=guest_os,json=guestOs,proto3" json:"guest_os,omitempty" search:"Guest OS" sql:"index=btree"` // @gotags: search:"Guest OS" sql:"index=btree" + State VirtualMachineV2_State `protobuf:"varint,8,opt,name=state,proto3,enum=storage.VirtualMachineV2_State" json:"state,omitempty" search:"Virtual Machine State"` // @gotags: search:"Virtual Machine State" + LastUpdated *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty" search:"Last Updated,hidden" sql:"type(timestamptz)" hash:"ignore"` // @gotags: search:"Last Updated,hidden" sql:"type(timestamptz)" hash:"ignore" + // last_agent_contact is the last successful pull-mode scrape that landed in Central. + // Unset means never scraped; GetVM maps age against ROX_VIRTUAL_MACHINES_AGENT_STALE_AFTER. + LastAgentContact *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=last_agent_contact,json=lastAgentContact,proto3" json:"last_agent_contact,omitempty" search:"Last Agent Contact,hidden" sql:"type(timestamptz)" hash:"ignore"` // @gotags: search:"Last Agent Contact,hidden" sql:"type(timestamptz)" hash:"ignore" + Notes []VirtualMachineV2_Note `protobuf:"varint,10,rep,packed,name=notes,proto3,enum=storage.VirtualMachineV2_Note" json:"notes,omitempty" hash:"set"` // @gotags: hash:"set" + VsockCid int32 `protobuf:"varint,11,opt,name=vsock_cid,json=vsockCid,proto3" json:"vsock_cid,omitempty"` // Hash of VM data fields for change detection (excludes last_updated). Hash uint64 `protobuf:"varint,12,opt,name=hash,proto3" json:"hash,omitempty" hash:"ignore"` // @gotags: hash:"ignore" unknownFields protoimpl.UnknownFields @@ -248,6 +251,13 @@ func (x *VirtualMachineV2) GetLastUpdated() *timestamppb.Timestamp { return nil } +func (x *VirtualMachineV2) GetLastAgentContact() *timestamppb.Timestamp { + if x != nil { + return x.LastAgentContact + } + return nil +} + func (x *VirtualMachineV2) GetNotes() []VirtualMachineV2_Note { if x != nil { return x.Notes @@ -273,7 +283,7 @@ var File_storage_virtual_machine_v2_proto protoreflect.FileDescriptor const file_storage_virtual_machine_v2_proto_rawDesc = "" + "\n" + - " storage/virtual_machine_v2.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd0\x05\n" + + " storage/virtual_machine_v2.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\"\x9a\x06\n" + "\x10VirtualMachineV2\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1c\n" + @@ -284,7 +294,8 @@ const file_storage_virtual_machine_v2_proto_rawDesc = "" + "\x05facts\x18\x06 \x03(\v2$.storage.VirtualMachineV2.FactsEntryR\x05facts\x12\x19\n" + "\bguest_os\x18\a \x01(\tR\aguestOs\x125\n" + "\x05state\x18\b \x01(\x0e2\x1f.storage.VirtualMachineV2.StateR\x05state\x12=\n" + - "\flast_updated\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\vlastUpdated\x124\n" + + "\flast_updated\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\vlastUpdated\x12H\n" + + "\x12last_agent_contact\x18\r \x01(\v2\x1a.google.protobuf.TimestampR\x10lastAgentContact\x124\n" + "\x05notes\x18\n" + " \x03(\x0e2\x1e.storage.VirtualMachineV2.NoteR\x05notes\x12\x1b\n" + "\tvsock_cid\x18\v \x01(\x05R\bvsockCid\x12\x12\n" + @@ -331,12 +342,13 @@ var file_storage_virtual_machine_v2_proto_depIdxs = []int32{ 3, // 0: storage.VirtualMachineV2.facts:type_name -> storage.VirtualMachineV2.FactsEntry 0, // 1: storage.VirtualMachineV2.state:type_name -> storage.VirtualMachineV2.State 4, // 2: storage.VirtualMachineV2.last_updated:type_name -> google.protobuf.Timestamp - 1, // 3: storage.VirtualMachineV2.notes:type_name -> storage.VirtualMachineV2.Note - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 4, // 3: storage.VirtualMachineV2.last_agent_contact:type_name -> google.protobuf.Timestamp + 1, // 4: storage.VirtualMachineV2.notes:type_name -> storage.VirtualMachineV2.Note + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_storage_virtual_machine_v2_proto_init() } diff --git a/generated/storage/virtual_machine_v2_vtproto.pb.go b/generated/storage/virtual_machine_v2_vtproto.pb.go index 326b889e0a363..4daef4e9f0c44 100644 --- a/generated/storage/virtual_machine_v2_vtproto.pb.go +++ b/generated/storage/virtual_machine_v2_vtproto.pb.go @@ -35,6 +35,7 @@ func (m *VirtualMachineV2) CloneVT() *VirtualMachineV2 { r.GuestOs = m.GuestOs r.State = m.State r.LastUpdated = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.LastUpdated).CloneVT()) + r.LastAgentContact = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.LastAgentContact).CloneVT()) r.VsockCid = m.VsockCid r.Hash = m.Hash if rhs := m.Facts; rhs != nil { @@ -117,6 +118,9 @@ func (this *VirtualMachineV2) EqualVT(that *VirtualMachineV2) bool { if this.Hash != that.Hash { return false } + if !(*timestamppb1.Timestamp)(this.LastAgentContact).EqualVT((*timestamppb1.Timestamp)(that.LastAgentContact)) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -157,6 +161,16 @@ func (m *VirtualMachineV2) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.LastAgentContact != nil { + size, err := (*timestamppb1.Timestamp)(m.LastAgentContact).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x6a + } if m.Hash != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Hash)) i-- @@ -325,6 +339,10 @@ func (m *VirtualMachineV2) SizeVT() (n int) { if m.Hash != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.Hash)) } + if m.LastAgentContact != nil { + l = (*timestamppb1.Timestamp)(m.LastAgentContact).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -839,6 +857,42 @@ func (m *VirtualMachineV2) UnmarshalVT(dAtA []byte) error { break } } + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastAgentContact", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LastAgentContact == nil { + m.LastAgentContact = ×tamppb.Timestamp{} + } + if err := (*timestamppb1.Timestamp)(m.LastAgentContact).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -1403,6 +1457,42 @@ func (m *VirtualMachineV2) UnmarshalVTUnsafe(dAtA []byte) error { break } } + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastAgentContact", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LastAgentContact == nil { + m.LastAgentContact = ×tamppb.Timestamp{} + } + if err := (*timestamppb1.Timestamp)(m.LastAgentContact).UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pkg/env/virtualmachine.go b/pkg/env/virtualmachine.go index d36db89a105cd..3c260afaa2dfb 100644 --- a/pkg/env/virtualmachine.go +++ b/pkg/env/virtualmachine.go @@ -87,4 +87,9 @@ var ( // Valid range is [0.01, 1] so W is not effectively zero; out-of-range values fall back to the default. VirtualMachinesScraperSteadySpreadFraction = RegisterFloatSetting("ROX_VIRTUAL_MACHINES_SCRAPER_STEADY_SPREAD_FRACTION", 2.0/3). WithMinimum(0.01).WithMaximum(1) + + // VirtualMachinesAgentStaleAfter is the GetVM Active window after the last + // successful scrape. Default 12h is larger than one healthy poll cycle + // (4h + up to 2/3 spread ≈ 6.7h), so one retryable VSOCK miss stays Active. + VirtualMachinesAgentStaleAfter = registerDurationSetting("ROX_VIRTUAL_MACHINES_AGENT_STALE_AFTER", 12*time.Hour) ) diff --git a/pkg/postgres/schema/virtual_machine_v2.go b/pkg/postgres/schema/virtual_machine_v2.go index 6890bb907818b..0c8a2f4df4253 100644 --- a/pkg/postgres/schema/virtual_machine_v2.go +++ b/pkg/postgres/schema/virtual_machine_v2.go @@ -56,13 +56,14 @@ const ( // VirtualMachineV2 holds the Gorm model for Postgres table `virtual_machine_v2`. type VirtualMachineV2 struct { - ID string `gorm:"column:id;type:uuid;primaryKey"` - Name string `gorm:"column:name;type:varchar"` - Namespace string `gorm:"column:namespace;type:varchar"` - ClusterID string `gorm:"column:clusterid;type:uuid"` - ClusterName string `gorm:"column:clustername;type:varchar"` - GuestOs string `gorm:"column:guestos;type:varchar"` - State storage.VirtualMachineV2_State `gorm:"column:state;type:integer"` - LastUpdated *time.Time `gorm:"column:lastupdated;type:timestamptz"` - Serialized []byte `gorm:"column:serialized;type:bytea"` + ID string `gorm:"column:id;type:uuid;primaryKey"` + Name string `gorm:"column:name;type:varchar"` + Namespace string `gorm:"column:namespace;type:varchar"` + ClusterID string `gorm:"column:clusterid;type:uuid"` + ClusterName string `gorm:"column:clustername;type:varchar"` + GuestOs string `gorm:"column:guestos;type:varchar"` + State storage.VirtualMachineV2_State `gorm:"column:state;type:integer"` + LastUpdated *time.Time `gorm:"column:lastupdated;type:timestamptz"` + LastAgentContact *time.Time `gorm:"column:lastagentcontact;type:timestamptz"` + Serialized []byte `gorm:"column:serialized;type:bytea"` } diff --git a/pkg/search/options.go b/pkg/search/options.go index 9c0683b72f631..18a556e8e5431 100644 --- a/pkg/search/options.go +++ b/pkg/search/options.go @@ -499,14 +499,15 @@ var ( AuthProviderName = newFieldLabel("AuthProvider Name") // Virtual Machine fields. - VirtualMachineID = newFieldLabel("Virtual Machine ID") - VirtualMachineName = newFieldLabel("Virtual Machine Name") - GuestOS = newFieldLabel("Guest OS") - VirtualMachineState = newFieldLabel("Virtual Machine State") - VirtualMachineScanID = newFieldLabel("Virtual Machine Scan ID") - VirtualMachineScanTime = newFieldLabel("Virtual Machine Scan Time") - VirtualMachineScanOS = newFieldLabel("Virtual Machine Scan OS") - VirtualMachineTopCVSS = newFieldLabel("Virtual Machine Top CVSS") + VirtualMachineID = newFieldLabel("Virtual Machine ID") + VirtualMachineName = newFieldLabel("Virtual Machine Name") + GuestOS = newFieldLabel("Guest OS") + VirtualMachineState = newFieldLabel("Virtual Machine State") + VirtualMachineScanID = newFieldLabel("Virtual Machine Scan ID") + VirtualMachineScanTime = newFieldLabel("Virtual Machine Scan Time") + VirtualMachineScanOS = newFieldLabel("Virtual Machine Scan OS") + VirtualMachineTopCVSS = newFieldLabel("Virtual Machine Top CVSS") + VirtualMachineLastAgentContact = newFieldLabel("Last Agent Contact") // Test Search Fields TestKey = newFieldLabel("Test Key") diff --git a/proto/api/v2/virtual_machine_v2_service.proto b/proto/api/v2/virtual_machine_v2_service.proto index 7aaffa7d7dc74..769269c56b643 100644 --- a/proto/api/v2/virtual_machine_v2_service.proto +++ b/proto/api/v2/virtual_machine_v2_service.proto @@ -125,10 +125,12 @@ enum VMNote { VM_NOTE_SCAN_FAILED = 5; } -// Agent status enriched from a separate data source (not on VirtualMachineV2 storage). +// AgentStatus is roxagent liveness from scrape freshness, computed on GetVM +// (not a stored enum). UNKNOWN means never scraped; INACTIVE means scraped, then quiet. enum AgentStatus { AGENT_STATUS_UNKNOWN = 0; AGENT_STATUS_ACTIVE = 1; + AGENT_STATUS_INACTIVE = 2; } message VMDetail { diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index 3837f78d1497e..979013d5d1c0b 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -20500,6 +20500,11 @@ "name": "last_updated", "type": "google.protobuf.Timestamp" }, + { + "id": 13, + "name": "last_agent_contact", + "type": "google.protobuf.Timestamp" + }, { "id": 10, "name": "notes", diff --git a/proto/storage/virtual_machine_v2.proto b/proto/storage/virtual_machine_v2.proto index 3dce14fbc1823..c041e9fb8efee 100644 --- a/proto/storage/virtual_machine_v2.proto +++ b/proto/storage/virtual_machine_v2.proto @@ -34,6 +34,10 @@ message VirtualMachineV2 { google.protobuf.Timestamp last_updated = 9; // @gotags: search:"Last Updated,hidden" sql:"type(timestamptz)" hash:"ignore" + // last_agent_contact is the last successful pull-mode scrape that landed in Central. + // Unset means never scraped; GetVM maps age against ROX_VIRTUAL_MACHINES_AGENT_STALE_AFTER. + google.protobuf.Timestamp last_agent_contact = 13; // @gotags: search:"Last Agent Contact,hidden" sql:"type(timestamptz)" hash:"ignore" + enum Note { MISSING_METADATA = 0; MISSING_SCAN_DATA = 1; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/virtualMachineUtils.ts b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/virtualMachineUtils.ts index fa7b438352d9d..16636ddcd8a74 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/virtualMachineUtils.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/virtualMachineUtils.ts @@ -9,4 +9,5 @@ export const stateDisplayMap: Record = { export const agentStatusDisplayMap: Record = { AGENT_STATUS_UNKNOWN: 'Unknown', AGENT_STATUS_ACTIVE: 'Active', + AGENT_STATUS_INACTIVE: 'Inactive', }; diff --git a/ui/apps/platform/src/services/VirtualMachineService.ts b/ui/apps/platform/src/services/VirtualMachineService.ts index 2d44604ba180e..44bcef607883f 100644 --- a/ui/apps/platform/src/services/VirtualMachineService.ts +++ b/ui/apps/platform/src/services/VirtualMachineService.ts @@ -217,7 +217,7 @@ export type VMNote = | 'VM_NOTE_MISSING_SCANNER' | 'VM_NOTE_SCAN_FAILED'; -export type AgentStatus = 'AGENT_STATUS_UNKNOWN' | 'AGENT_STATUS_ACTIVE'; +export type AgentStatus = 'AGENT_STATUS_UNKNOWN' | 'AGENT_STATUS_ACTIVE' | 'AGENT_STATUS_INACTIVE'; export type VMScanInfo = { scanId: string; From 315f7745efa2c1557bd7d6f7866b8ac9db4331ac Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Tue, 8 Sep 2026 13:49:09 +0200 Subject: [PATCH 229/638] ROX-36824: redact sensitive secret annotations in diagnostic bundle (#22681) Co-authored-by: Claude Opus 4.8 --- CHANGELOG.md | 1 + pkg/k8sintrospect/redact.go | 31 ++++++++++++++++++++++++- pkg/k8sintrospect/redact_test.go | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef0aa42afc471..22e89d1f12808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35079: installation of the `app.k8s.io/v1beta1/Application` resource when central is installed is deprecated. It will be removed in a future release. ### Technical Changes +- ROX-36824: Diagnostic bundles now redact the value of the `openshift.io/token-secret.value` annotation on secrets. Previously this OpenShift-managed annotation, which contains a plaintext service account token on generated dockercfg secrets, was included unredacted in the bundle. - ROX-36660: The **Fixable → CVE is not yet fixable** policy criterion now matches Scanner V4 CVEs that have no fix version. Scanner V4 leaves `Fixed By` unset instead of empty (Scanner V2 always set an empty string), so the matcher previously skipped those CVEs. - ROX-36490: The virtual machine enhanced data model (`ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL`) is now enabled by default. - ROX-32969: The `roxctl-linux` symlink has been removed from the `/assets/downloads/cli/` directory inside the main container image. Only the architecture-specific binaries (`roxctl-linux-amd64`, `roxctl-linux-arm64`, etc.) remain. This change does not affect CLI downloads from the Central UI or any other supported download path. diff --git a/pkg/k8sintrospect/redact.go b/pkg/k8sintrospect/redact.go index 13f70b0590d28..2e688e829cfed 100644 --- a/pkg/k8sintrospect/redact.go +++ b/pkg/k8sintrospect/redact.go @@ -4,6 +4,18 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +const redactedValue = "***REDACTED***" + +// sensitiveSecretAnnotations enumerates annotation keys whose values may contain +// sensitive data and therefore must be redacted before a secret is included in a +// diagnostic bundle. Unlike the secret `data` field, these values are not covered +// by the data redaction below. +var sensitiveSecretAnnotations = []string{ + // OpenShift stores a plaintext copy of the service account token in this + // annotation on the generated dockercfg secrets (ROX-36824). + "openshift.io/token-secret.value", +} + // RedactGeneric removes fields we don't care about for any object type. This includes: // - the `kubectl.kubernetes.io/last-applied-configuration` // - the selfLink field (this is not object-specific and fully captured by name, namespace, and kind) @@ -23,11 +35,28 @@ func RedactSecret(secret *unstructured.Unstructured) { if found && err == nil { redactedStringData := make(map[string]string, len(dataMap)) for key := range dataMap { - redactedStringData[key] = "***REDACTED***" + redactedStringData[key] = redactedValue } _ = unstructured.SetNestedStringMap(secret.UnstructuredContent(), redactedStringData, "stringData") } unstructured.RemoveNestedField(secret.UnstructuredContent(), "data") + + // Some secrets carry sensitive values in their annotations (e.g. the plaintext + // service account token OpenShift stores on dockercfg secrets). Redact those + // values while keeping the annotation key, so the bundle still shows the + // annotation was present. + if annotations := secret.GetAnnotations(); len(annotations) > 0 { + modified := false + for _, key := range sensitiveSecretAnnotations { + if _, ok := annotations[key]; ok { + annotations[key] = redactedValue + modified = true + } + } + if modified { + secret.SetAnnotations(annotations) + } + } } // FilterOutServiceAccountSecrets filters out secrets that are associated with a Kubernetes service account. diff --git a/pkg/k8sintrospect/redact_test.go b/pkg/k8sintrospect/redact_test.go index f0f1a6c43e82b..1a95f70322edf 100644 --- a/pkg/k8sintrospect/redact_test.go +++ b/pkg/k8sintrospect/redact_test.go @@ -86,3 +86,42 @@ func TestRedactSecret(t *testing.T) { assert.Equal(t, redactedSecret.StringData, expectedStringData) assert.Empty(t, redactedSecret.Data) } + +func TestRedactSecretSensitiveAnnotations(t *testing.T) { + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "stackrox", + Name: "builder-dockercfg-abcde", + Annotations: map[string]string{ + "openshift.io/token-secret.value": "test-only-placeholder-token-not-a-real-secret", + "openshift.io/token-secret.name": "builder-token-abcde", + "kubernetes.io/service-account.name": "builder", + }, + }, + Type: v1.SecretTypeDockercfg, + Data: map[string][]byte{ + ".dockercfg": []byte(`{"registry":{"auth":"c2VjcmV0"}}`), + }, + } + + var unstructuredSecret unstructured.Unstructured + require.NoError(t, scheme.Scheme.Convert(&secret, &unstructuredSecret, nil)) + + RedactSecret(&unstructuredSecret) + + var redactedSecret v1.Secret + require.NoError(t, scheme.Scheme.Convert(&unstructuredSecret, &redactedSecret, nil)) + + // The sensitive token annotation value must be redacted... + assert.Equal(t, "***REDACTED***", redactedSecret.Annotations["openshift.io/token-secret.value"]) + // ...while non-sensitive annotations are preserved verbatim. + assert.Equal(t, "builder-token-abcde", redactedSecret.Annotations["openshift.io/token-secret.name"]) + assert.Equal(t, "builder", redactedSecret.Annotations["kubernetes.io/service-account.name"]) + // The data field is still redacted as before. + assert.Empty(t, redactedSecret.Data) + assert.Equal(t, "***REDACTED***", redactedSecret.StringData[".dockercfg"]) +} From 99a1c13906811e5e817684b0fd88894d3cc1d2dc Mon Sep 17 00:00:00 2001 From: Aleksandr Kurlov Date: Tue, 8 Sep 2026 13:51:35 +0200 Subject: [PATCH 230/638] ROX-34722: Switch to periodic konflux retest (#22554) Co-authored-by: Tom Martensen --- .github/workflows/konflux-auto-retest.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/konflux-auto-retest.yml b/.github/workflows/konflux-auto-retest.yml index 78ef8543bb472..03dcac1d1f6a5 100644 --- a/.github/workflows/konflux-auto-retest.yml +++ b/.github/workflows/konflux-auto-retest.yml @@ -1,24 +1,18 @@ -name: Retest Konflux Builds +name: Periodic Retest Failed Konflux Builds on: - check_run: - types: [completed] - pull_request: - types: [synchronize] + schedule: + - cron: '5,15,25,35,45,55 * * * *' + workflow_dispatch: jobs: - retest-failed-builds: - # Guard against phantom fork PRs: when a fork has a sync PR whose head - # points at our master, GitHub populates check_run.pull_requests[0] with - # the fork's PR number. Verify the PR URL belongs to this repo. - if: >- - github.event_name == 'pull_request' || - contains(github.event.check_run.pull_requests[0].url, format('/repos/{0}/pulls/', github.repository)) - uses: stackrox/actions/.github/workflows/retest-konflux-builds.yml@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/.github/workflows/retest-konflux-builds.yml@v1 + retest: + uses: stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@f4660e8ce6bd7558e782e8e7cd26bad8cfbb6b9f # ratchet:stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@v1 permissions: pull-requests: write issues: write + checks: read + contents: read with: max_retries: 3 - check_name_suffix: '-on-push' retest_command: '/konflux-retest' From 45729ea10a54f53d873eeed26baa95af84751b50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:27:06 +0200 Subject: [PATCH 231/638] chore(deps-dev): bump js-yaml from 4.3.0 to 4.3.1 in /tests/performance/load (#22178) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/load/package-lock.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 4bad4b72afc51..39c545c9dd20c 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -1487,9 +1487,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -1501,7 +1501,6 @@ "url": "https://github.com/sponsors/nodeca" } ], - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3668,9 +3667,9 @@ "dev": true }, "js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "requires": { "argparse": "^2.0.1" From d9f8c0ac45f1ee466e902f3b0634b34f7deb184b Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:33:26 +0200 Subject: [PATCH 232/638] fix(api): Populate ListVMs scan_time from latest VM scan (#22693) --- .../virtualmachine/v2/service/service_impl.go | 33 +++++++++++++++++ .../v2/service/service_impl_test.go | 36 ++++++++++++++++++- tests/vm_scanning_test.go | 2 ++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/central/virtualmachine/v2/service/service_impl.go b/central/virtualmachine/v2/service/service_impl.go index 47b4e58823d85..69775fe103509 100644 --- a/central/virtualmachine/v2/service/service_impl.go +++ b/central/virtualmachine/v2/service/service_impl.go @@ -30,6 +30,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -106,6 +107,11 @@ func (s *serviceImpl) ListVMs(ctx context.Context, request *v2.ListVMsRequest) ( vmIDs = append(vmIDs, vm.GetId()) } + scanTimeByVM, err := s.latestScanTimeByVM(ctx, vmIDs) + if err != nil { + return nil, err + } + // Fetch per-VM CVE severity counts via SQL GROUP BY. vmFilter := search.NewQueryBuilder().AddExactMatches(search.VirtualMachineID, vmIDs...).ProtoQuery() severityRows, err := s.cveView.CountBySeverityPerVM(ctx, vmFilter) @@ -134,6 +140,7 @@ func (s *serviceImpl) ListVMs(ctx context.Context, request *v2.ListVMsRequest) ( item.CveSeverityCounts = &v2.VulnCountBySeverity{} } item.ComponentScanCount = componentCountsByVM[vm.GetId()] + item.ScanTime = scanTimeByVM[vm.GetId()] items = append(items, item) } @@ -212,6 +219,32 @@ func (s *serviceImpl) GetVMDashboardCounts(ctx context.Context, request *v2.VMDa }, nil } +// latestScanTimeByVM returns the latest scan timestamp per VM. Scan IDs are +// UUIDv7, so the greatest ID is the latest scan (same rule as GetVM). +func (s *serviceImpl) latestScanTimeByVM(ctx context.Context, vmIDs []string) (map[string]*timestamppb.Timestamp, error) { + result := make(map[string]*timestamppb.Timestamp, len(vmIDs)) + if len(vmIDs) == 0 { + return result, nil + } + + q := search.NewQueryBuilder().AddExactMatches(search.VirtualMachineID, vmIDs...).ProtoQuery() + scans, err := s.scanDS.SearchRawVMScans(ctx, q) + if err != nil { + return nil, err + } + + latestID := make(map[string]string, len(scans)) + for _, scan := range scans { + vmID := scan.GetVmV2Id() + if prevID, ok := latestID[vmID]; ok && scan.GetId() <= prevID { + continue + } + latestID[vmID] = scan.GetId() + result[vmID] = scan.GetScanTime() + } + return result, nil +} + // batchComponentScanCounts fetches all components for the given VM IDs in one query // and counts total vs scanned per VM in memory. The component Notes field is not // search-indexed, so SQL-level aggregation of scanned vs unscanned is not possible. diff --git a/central/virtualmachine/v2/service/service_impl_test.go b/central/virtualmachine/v2/service/service_impl_test.go index eb59402e13b6c..f94c9b130c7ee 100644 --- a/central/virtualmachine/v2/service/service_impl_test.go +++ b/central/virtualmachine/v2/service/service_impl_test.go @@ -907,8 +907,9 @@ func TestListVMs(t *testing.T) { Notes: []storage.VirtualMachineComponentV2_Note{storage.VirtualMachineComponentV2_UNSCANNED}, } + scanTime := timestamppb.New(time.Date(2025, time.April, 15, 10, 30, 0, 0, time.UTC)) scan1 := &storage.VirtualMachineScanV2{ - Id: "scan-1", VmV2Id: "vm-1", + Id: "scan-1", VmV2Id: "vm-1", ScanTime: scanTime, } tests := map[string]struct { @@ -928,6 +929,7 @@ func TestListVMs(t *testing.T) { }).AnyTimes() mockView.EXPECT().CountBySeverityPerVM(ctx, gomock.Any()).Return([]vmcve.VMSeverityCounts{sevCounts}, nil) mockComp.EXPECT().SearchRawVMComponents(ctx, gomock.Any()).Return([]*storage.VirtualMachineComponentV2{comp1, compUnscanned}, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return([]*storage.VirtualMachineScanV2{scan1}, nil) mockScan.EXPECT().GetBatch(ctx, gomock.Any()).Return([]*storage.VirtualMachineScanV2{scan1}, nil) }, expectedCount: 1, @@ -938,6 +940,8 @@ func TestListVMs(t *testing.T) { assert.Equal(t, int32(1), item.GetCveSeverityCounts().GetCritical().GetFixable()) assert.Equal(t, int32(2), item.GetComponentScanCount().GetTotal()) assert.Equal(t, int32(1), item.GetComponentScanCount().GetScanned()) + require.NotNil(t, item.GetScanTime()) + assert.Equal(t, scanTime.AsTime(), item.GetScanTime().AsTime()) }, }, "empty list": { @@ -947,6 +951,36 @@ func TestListVMs(t *testing.T) { }, expectedCount: 0, }, + "should use scan time from the greatest scan ID": { + setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { + // Greatest UUIDv7 ID sits in the middle with the earliest ScanTime + // so first-row, last-row, and max-timestamp picks fail. + lowID := &storage.VirtualMachineScanV2{ + Id: "01800000-0000-7000-8000-000000000001", VmV2Id: "vm-1", + ScanTime: timestamppb.New(time.Date(2025, time.April, 16, 12, 0, 0, 0, time.UTC)), + } + winID := &storage.VirtualMachineScanV2{ + Id: "01900000-0000-7000-8000-000000000003", VmV2Id: "vm-1", + ScanTime: timestamppb.New(time.Date(2025, time.April, 14, 8, 0, 0, 0, time.UTC)), + } + midID := &storage.VirtualMachineScanV2{ + Id: "01880000-0000-7000-8000-000000000002", VmV2Id: "vm-1", + ScanTime: timestamppb.New(time.Date(2025, time.April, 15, 10, 30, 0, 0, time.UTC)), + } + mockVM.EXPECT().CountVirtualMachines(ctx, gomock.Any()).Return(1, nil) + mockVM.EXPECT().SearchRawVirtualMachines(ctx, gomock.Any()).Return([]*storage.VirtualMachineV2{vm1}, nil) + mockView.EXPECT().CountBySeverityPerVM(ctx, gomock.Any()).Return(nil, nil) + mockComp.EXPECT().SearchRawVMComponents(ctx, gomock.Any()).Return(nil, nil) + mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return([]*storage.VirtualMachineScanV2{lowID, winID, midID}, nil) + mockScan.EXPECT().GetBatch(ctx, gomock.Any()).Return(nil, nil) + }, + expectedCount: 1, + checkResult: func(t *testing.T, resp *v2.ListVMsResponse) { + require.Len(t, resp.GetVms(), 1) + require.NotNil(t, resp.GetVms()[0].GetScanTime()) + assert.Equal(t, time.Date(2025, time.April, 14, 8, 0, 0, 0, time.UTC), resp.GetVms()[0].GetScanTime().AsTime()) + }, + }, "vm count error": { setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { mockVM.EXPECT().CountVirtualMachines(ctx, gomock.Any()).Return(0, errors.New("count error")) diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index 032714ed2ce19..aed56b77c7f7f 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -119,6 +119,8 @@ func (s *VMScanningSuite) TestScanPipeline() { require.NotEmpty(t, listed.GetClusterId()) require.NotEmpty(t, listed.GetClusterName()) require.Equal(t, v2.VirtualMachineV2State_VM_STATE_RUNNING, listed.GetState()) + require.NotNil(t, listed.GetScanTime(), + "ListVMs.scan_time should be populated from the latest scan") cves, total, err := vmhelpers.ListAllVMCVEsByVM(s.ctx, s.vmV2Client, snapshot.ID) require.NoError(t, err) From f37208779fe185d16700506041503cbca40b1c62 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:06:35 +0200 Subject: [PATCH 233/638] ROX-34863: Project detected guest OS onto API guest_os (#22658) --- .../convert/storagetov2/virtual_machine_v2.go | 5 ++- .../storagetov2/virtual_machine_v2_test.go | 37 ++++++++++++++++ pkg/virtualmachine/facts.go | 8 ++++ pkg/virtualmachine/facts_test.go | 44 +++++++++++++++++++ tests/vm_scanning_test.go | 6 +++ 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 pkg/virtualmachine/facts_test.go diff --git a/central/convert/storagetov2/virtual_machine_v2.go b/central/convert/storagetov2/virtual_machine_v2.go index c93dbcf03efbd..baccb91cecc71 100644 --- a/central/convert/storagetov2/virtual_machine_v2.go +++ b/central/convert/storagetov2/virtual_machine_v2.go @@ -7,6 +7,7 @@ import ( "github.com/stackrox/rox/central/views/common" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -21,7 +22,7 @@ func VirtualMachineV2ToListItem(vm *storage.VirtualMachineV2) *v2.VMListItem { Namespace: vm.GetNamespace(), ClusterId: vm.GetClusterId(), ClusterName: vm.GetClusterName(), - GuestOs: vm.GetGuestOs(), + GuestOs: pkgVM.DisplayGuestOS(vm.GetFacts(), vm.GetGuestOs()), State: convertVirtualMachineV2State(vm.GetState()), LastUpdated: vm.GetLastUpdated(), } @@ -42,7 +43,7 @@ func VirtualMachineV2ToDetail(vm *storage.VirtualMachineV2) *v2.VMDetail { Namespace: vm.GetNamespace(), ClusterId: vm.GetClusterId(), ClusterName: vm.GetClusterName(), - GuestOs: vm.GetGuestOs(), + GuestOs: pkgVM.DisplayGuestOS(vm.GetFacts(), vm.GetGuestOs()), State: convertVirtualMachineV2State(vm.GetState()), LastUpdated: vm.GetLastUpdated(), Facts: vm.GetFacts(), diff --git a/central/convert/storagetov2/virtual_machine_v2_test.go b/central/convert/storagetov2/virtual_machine_v2_test.go index fa1a5fe3e1f93..a50880281c2c0 100644 --- a/central/convert/storagetov2/virtual_machine_v2_test.go +++ b/central/convert/storagetov2/virtual_machine_v2_test.go @@ -6,6 +6,8 @@ import ( v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" + pkgVM "github.com/stackrox/rox/pkg/virtualmachine" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -89,3 +91,38 @@ func TestAgentStatusFromLastContact(t *testing.T) { }) } } + +func TestVirtualMachineV2GuestOsDisplay(t *testing.T) { + tests := map[string]struct { + facts map[string]string + storedOS string + want string + }{ + "prefers detected guest OS": { + facts: map[string]string{ + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.GuestOSKey: "Red Hat Enterprise Linux", + }, + storedOS: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux 9.2", + }, + "falls back to stored guest OS": { + facts: map[string]string{pkgVM.GuestOSKey: "Red Hat Enterprise Linux"}, + storedOS: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + vm := &storage.VirtualMachineV2{ + Id: "vm-1", + Facts: tt.facts, + GuestOs: tt.storedOS, + } + assert.Equal(t, tt.want, VirtualMachineV2ToDetail(vm).GetGuestOs()) + assert.Equal(t, tt.want, VirtualMachineV2ToListItem(vm).GetGuestOs()) + assert.Equal(t, tt.storedOS, vm.GetGuestOs()) + }) + } +} diff --git a/pkg/virtualmachine/facts.go b/pkg/virtualmachine/facts.go index 65c8b5fbd773c..c036d16701ed5 100644 --- a/pkg/virtualmachine/facts.go +++ b/pkg/virtualmachine/facts.go @@ -1,5 +1,7 @@ package virtualmachine +import "cmp" + // Facts keys used in VirtualMachine.Facts maps. // Keep the keys camelCase to match the style used elsewhere in the UI. const ( @@ -29,3 +31,9 @@ const ( DNFMetadataStatusAvailable = "available" DNFMetadataStatusUnavailable = "unavailable" ) + +// DisplayGuestOS prefers the agent-detected OS so callers can show a versioned +// string while storage.GuestOs remains the informer value used for scan stamping. +func DisplayGuestOS(facts map[string]string, fallback string) string { + return cmp.Or(facts[DetectedGuestOSKey], fallback) +} diff --git a/pkg/virtualmachine/facts_test.go b/pkg/virtualmachine/facts_test.go new file mode 100644 index 0000000000000..759e34bacd10d --- /dev/null +++ b/pkg/virtualmachine/facts_test.go @@ -0,0 +1,44 @@ +package virtualmachine + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDisplayGuestOS(t *testing.T) { + tests := map[string]struct { + facts map[string]string + fallback string + want string + }{ + "prefers detected guest OS": { + facts: map[string]string{ + DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + GuestOSKey: "Red Hat Enterprise Linux", + }, + fallback: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux 9.2", + }, + "falls back when detected is absent": { + facts: map[string]string{GuestOSKey: "Red Hat Enterprise Linux"}, + fallback: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux", + }, + "falls back when detected is empty": { + facts: map[string]string{DetectedGuestOSKey: ""}, + fallback: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux", + }, + "nil facts use fallback": { + fallback: "unknown", + want: "unknown", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tt.want, DisplayGuestOS(tt.facts, tt.fallback)) + }) + } +} diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index aed56b77c7f7f..161584f0c4959 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -108,6 +108,8 @@ func (s *VMScanningSuite) TestScanPipeline() { require.NotNil(t, detail.GetLatestScan()) require.NotNil(t, detail.GetLatestScan().GetScanTime()) requireForwardedAgentFacts(t, detail.GetFacts()) + require.Equal(t, detail.GetFacts()[pkgVM.DetectedGuestOSKey], detail.GetGuestOs(), + "GetVM.guest_os should prefer facts.detectedGuestOS") }) t.Run("VirtualMachineV2ListVMs", func(t *testing.T) { @@ -122,6 +124,10 @@ func (s *VMScanningSuite) TestScanPipeline() { require.NotNil(t, listed.GetScanTime(), "ListVMs.scan_time should be populated from the latest scan") + detail := s.mustGetVMV2(snapshot.ID) + require.Equal(t, detail.GetGuestOs(), listed.GetGuestOs(), + "ListVMs.guest_os should match GetVM.guest_os") + cves, total, err := vmhelpers.ListAllVMCVEsByVM(s.ctx, s.vmV2Client, snapshot.ID) require.NoError(t, err) require.Greater(t, total, int32(0), "scanned RHEL guest should report CVEs via ListVMCVEsByVM") From 07f05df0e9c10ed390982c85486b3f170f66c1c0 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:11:15 -0500 Subject: [PATCH 234/638] chore(ui): Wire node reporting create button (#22688) --- .../NodeVulnerabilityReportsLayout.tsx | 7 ++-- .../NodeVulnerabilityReportsPage.tsx | 39 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx index 22d162bcb6d20..51eb164dd6eaf 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsLayout.tsx @@ -7,7 +7,8 @@ import { Tabs, Title, } from '@patternfly/react-core'; -import { Outlet, useLocation, useNavigate } from 'react-router-dom-v5-compat'; +import type { ReactNode } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import PageTitle from 'Components/PageTitle'; @@ -18,7 +19,7 @@ import { vulnerabilityReportsPath, } from 'routePaths'; -function NodeVulnerabilityReportsLayout() { +function NodeVulnerabilityReportsLayout({ children }: { children: ReactNode }) { const location = useLocation(); const navigate = useNavigate(); @@ -76,7 +77,7 @@ function NodeVulnerabilityReportsLayout() { : 'view-based-jobs-tab-content' } > - + {children} ); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx index 9b240c372b5e3..4e39e62175636 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/NodeVulnerabilityReportsPage.tsx @@ -21,21 +21,38 @@ function NodeVulnerabilityReportsPage() { return ( - }> + + } + /> + {hasReadAccessForReport && ( + (pageAction === 'create' || pageAction === 'createFromFilters') && + hasWriteAccessForReport ? ( + + ) : ( + + + + ) } /> - {hasReadAccessForReport && ( - } /> - )} - } /> - + )} + + + + } + /> {hasReadAccessForReport && ( Date: Tue, 8 Sep 2026 19:13:34 +0200 Subject: [PATCH 235/638] ROX-36858: Add more image and node vulnerability labels (#22626) --- CHANGELOG.md | 1 + central/metrics/custom/image_vulnerabilities/labels.go | 10 +++++++++- central/metrics/custom/node_vulnerabilities/labels.go | 7 ++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e89d1f12808..b7460b048583a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35508: Scanner V4 now suppresses duplicate OSV.dev vulnerability records when Red Hat VEX data covers the same CVE for a Red Hat product image, showing Red Hat's own severity/CVSS/remediation data instead of a conflicting OSV.dev one. Enabled by default; disable via `ROX_SCANNER_V4_SUPPRESS_OSV_WITH_RED_HAT_VEX=false` if needed. - ROX-34488: Added support for cosign signature discovery via OCI 1.1 referrers, including DSSE envelope verification for sigstore bundle-format signatures. +- ROX-36858: Added more supported labels to the image and node vulnerability central custom metrics. ### Removed Features diff --git a/central/metrics/custom/image_vulnerabilities/labels.go b/central/metrics/custom/image_vulnerabilities/labels.go index b0e7a39f19153..5745307489f81 100644 --- a/central/metrics/custom/image_vulnerabilities/labels.go +++ b/central/metrics/custom/image_vulnerabilities/labels.go @@ -14,6 +14,7 @@ var lazyLabels = tracker.LazyLabelGetters[*finding]{ "Deployment": func(f *finding) string { return f.deployment.GetName() }, "Type": func(f *finding) string { return f.deployment.GetType() }, "IsActive": func(f *finding) string { return strconv.FormatBool(!f.deployment.GetInactive()) }, + "IsSuppressed": func(f *finding) string { return strconv.FormatBool(f.vuln.GetSuppressed()) }, "IsPlatformWorkload": isPlatformWorkload, "ImageID": func(f *finding) string { return f.image.GetId() }, @@ -33,7 +34,14 @@ var lazyLabels = tracker.LazyLabelGetters[*finding]{ "EPSSPercentile": func(f *finding) string { return strconv.FormatFloat(float64(f.vuln.GetEpss().GetEpssPercentile()), 'f', 1, 32) }, - "IsFixable": func(f *finding) string { return strconv.FormatBool(f.vuln.GetFixedBy() != "") }, + "Origin": func(f *finding) string { return f.vuln.GetOrigin().String() }, + + "IsFixable": func(f *finding) string { return strconv.FormatBool(f.vuln.GetFixedBy() != "") }, + "FixAvailable": func(f *finding) string { return strconv.FormatInt(f.vuln.GetFixAvailableTimestamp().GetSeconds(), 10) }, + "HasCisaKev": func(f *finding) string { return strconv.FormatBool(f.vuln.GetCisaKev()) }, + "Published": func(f *finding) string { return strconv.FormatInt(f.vuln.GetPublishedOn().GetSeconds(), 10) }, + "FirstImageOccurrence": func(f *finding) string { return strconv.FormatInt(f.vuln.GetFirstImageOccurrence().GetSeconds(), 10) }, + "FirstSystemOccurrence": func(f *finding) string { return strconv.FormatInt(f.vuln.GetFirstSystemOccurrence().GetSeconds(), 10) }, } // finding holds all information for computing any label in this category. diff --git a/central/metrics/custom/node_vulnerabilities/labels.go b/central/metrics/custom/node_vulnerabilities/labels.go index f2e7afd9d5fc2..d25fb35066427 100644 --- a/central/metrics/custom/node_vulnerabilities/labels.go +++ b/central/metrics/custom/node_vulnerabilities/labels.go @@ -19,7 +19,6 @@ var lazyLabels = tracker.LazyLabelGetters[*finding]{ "CVE": func(f *finding) string { return f.vulnerability.GetCveBaseInfo().GetCve() }, "CVSS": func(f *finding) string { return strconv.FormatFloat(float64(f.vulnerability.GetCvss()), 'f', 1, 32) }, "Severity": func(f *finding) string { return f.vulnerability.GetSeverity().String() }, - "IsFixable": func(f *finding) string { return strconv.FormatBool(f.vulnerability.GetFixedBy() != "") }, "IsSnoozed": func(f *finding) string { return strconv.FormatBool(f.vulnerability.GetSnoozed()) }, "EPSSPercentile": func(f *finding) string { return strconv.FormatFloat(float64(f.vulnerability.GetCveBaseInfo().GetEpss().GetEpssPercentile()), 'f', 1, 32) @@ -27,6 +26,12 @@ var lazyLabels = tracker.LazyLabelGetters[*finding]{ "EPSSProbability": func(f *finding) string { return strconv.FormatFloat(float64(f.vulnerability.GetCveBaseInfo().GetEpss().GetEpssProbability()), 'f', 1, 32) }, + + "IsFixable": func(f *finding) string { return strconv.FormatBool(f.vulnerability.GetFixedBy() != "") }, + "HasCisaKev": func(f *finding) string { return strconv.FormatBool(f.vulnerability.GetCveBaseInfo().GetCisaKev()) }, + "Published": func(f *finding) string { + return strconv.FormatInt(f.vulnerability.GetCveBaseInfo().GetPublishedOn().GetSeconds(), 10) + }, } type finding struct { From 0307625892e44c9ef82a609c27ed2d35191e1989 Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:08:14 -0400 Subject: [PATCH 236/638] ROX-36265: Add E2E tests for central-worker (#21597) Co-authored-by: Claude Opus 4.6 (1M context) --- .../groovy/services/ReportV2Service.groovy | 39 ++++ .../src/test/groovy/CentralWorkerTest.groovy | 177 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 qa-tests-backend/src/main/groovy/services/ReportV2Service.groovy create mode 100644 qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy diff --git a/qa-tests-backend/src/main/groovy/services/ReportV2Service.groovy b/qa-tests-backend/src/main/groovy/services/ReportV2Service.groovy new file mode 100644 index 0000000000000..c911ecc2d54ef --- /dev/null +++ b/qa-tests-backend/src/main/groovy/services/ReportV2Service.groovy @@ -0,0 +1,39 @@ +package services + +import groovy.util.logging.Slf4j + +import io.stackrox.proto.api.v2.Common +import io.stackrox.proto.api.v2.ReportServiceGrpc +import io.stackrox.proto.api.v2.ReportServiceOuterClass + +@Slf4j +class ReportV2Service extends BaseService { + + static ReportServiceGrpc.ReportServiceBlockingStub getClient() { + return ReportServiceGrpc.newBlockingStub(getChannel()) + } + + static ReportServiceOuterClass.ReportConfiguration createReportConfig( + ReportServiceOuterClass.ReportConfiguration config) { + return getClient().postReportConfiguration(config) + } + + static deleteReportConfig(String configId) { + getClient().deleteReportConfiguration( + Common.ResourceByID.newBuilder().setId(configId).build()) + } + + static ReportServiceOuterClass.RunReportResponse runReport(String configId, + ReportServiceOuterClass.NotificationMethod method) { + return getClient().runReport( + ReportServiceOuterClass.RunReportRequest.newBuilder() + .setReportConfigId(configId) + .setReportNotificationMethod(method) + .build()) + } + + static ReportServiceOuterClass.ReportStatusResponse getReportStatus(String reportId) { + return getClient().getReportStatus( + Common.ResourceByID.newBuilder().setId(reportId).build()) + } +} diff --git a/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy b/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy new file mode 100644 index 0000000000000..6937a0289a6ff --- /dev/null +++ b/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy @@ -0,0 +1,177 @@ +import static util.Helpers.withRetry + +import io.stackrox.proto.api.v2.ReportServiceOuterClass + +import services.CollectionsService +import services.ReportV2Service + +import spock.lang.Tag +import spock.lang.Shared + +@Tag("CentralWorker") +class CentralWorkerTest extends BaseSpecification { + + @Shared + private String collectionId + + def setupSpec() { + // Create a collection scoped to the stackrox namespace for report tests + def collection = CollectionsService.createCollection([], ["stackrox"]) + collectionId = collection.getId() + assert collectionId + LOG.info("Created test collection: ${collectionId}") + } + + def cleanupSpec() { + if (collectionId) { + CollectionsService.deleteCollection(collectionId) + } + } + + def "central-worker pod should be running"() { + expect: + "exactly one worker pod should be running" + withRetry(10, 3) { + def pods = orchestrator.getPods("stackrox", "central-worker") + assert pods.size() == 1 + } + } + + def "Central should skip pruning when worker is enabled"() { + expect: + "Central should log that pruning is managed by central-worker" + withRetry(10, 3) { + def centralLog = orchestrator.getPodLog("stackrox", + orchestrator.getPods("stackrox", "central").first().metadata.name) + assert centralLog.contains("Pruning is managed by central-worker") + } + } + + def "Central should skip report scheduling when worker is enabled"() { + expect: + "Central should log that report scheduling is managed by central-worker" + withRetry(10, 3) { + def centralLog = orchestrator.getPodLog("stackrox", + orchestrator.getPods("stackrox", "central").first().metadata.name) + assert centralLog.contains("Report scheduling is managed by central-worker") + } + } + + def "Worker should run pruning cycle"() { + expect: + "worker should have completed a pruning cycle" + withRetry(30, 5) { + def workerPodName = orchestrator.getPods("stackrox", "central-worker") + .first().metadata.name + def workerLog = orchestrator.getPodLog("stackrox", workerPodName) + assert workerLog.contains("Finished garbage collection cycle") + } + } + + def "Worker should acquire advisory lock for pruning"() { + expect: + "worker should acquire and release pruning advisory lock" + withRetry(30, 5) { + def workerPodName = orchestrator.getPods("stackrox", "central-worker") + .first().metadata.name + def workerLog = orchestrator.getPodLog("stackrox", workerPodName) + assert workerLog.contains("Advisory lock 7293581046138294017 acquired") + assert workerLog.contains("Advisory lock 7293581046138294017 released") + } + } + + def "Worker should start LISTEN/NOTIFY listener"() { + expect: + "worker should be listening on report channels" + withRetry(10, 3) { + def workerPodName = orchestrator.getPods("stackrox", "central-worker") + .first().metadata.name + def workerLog = orchestrator.getPodLog("stackrox", workerPodName) + assert workerLog.contains("Notification listener started on channels: " + + "[notifier_changed report_config_changed report_request_submitted report_request_cancelled]") + } + } + + def "On-demand report should be generated by worker"() { + given: + "a report configuration" + def config = ReportV2Service.createReportConfig( + ReportServiceOuterClass.ReportConfiguration.newBuilder() + .setName("e2e-worker-test-${UUID.randomUUID()}") + .setDescription("E2E test for central-worker report generation") + .setType(ReportServiceOuterClass.ReportConfiguration.ReportType.VULNERABILITY) + .setVulnReportFilters( + ReportServiceOuterClass.VulnerabilityReportFilters.newBuilder() + .setFixability( + ReportServiceOuterClass.VulnerabilityReportFilters + .Fixability.BOTH) + .addSeverities( + ReportServiceOuterClass.VulnerabilityReportFilters + .VulnerabilitySeverity.CRITICAL_VULNERABILITY_SEVERITY) + .addImageTypes( + ReportServiceOuterClass.VulnerabilityReportFilters + .ImageType.DEPLOYED) + .setAllVuln(true) + .build()) + .setResourceScope( + ReportServiceOuterClass.ResourceScope.newBuilder() + .setCollectionScope( + ReportServiceOuterClass.CollectionReference + .newBuilder() + .setCollectionId(collectionId) + .build()) + .build()) + .build()) + + when: + "submitting an on-demand report" + def runResponse = ReportV2Service.runReport(config.getId(), + ReportServiceOuterClass.NotificationMethod.DOWNLOAD) + def reportId = runResponse.getReportId() + + then: + "report should be generated successfully" + withRetry(10, 3) { + def status = ReportV2Service.getReportStatus(reportId) + assert status.getStatus().getRunState() == + ReportServiceOuterClass.ReportStatus.RunState.GENERATED || + status.getStatus().getRunState() == + ReportServiceOuterClass.ReportStatus.RunState.DELIVERED + } + + and: + "worker logs should show report execution" + withRetry(10, 3) { + def workerPodName = orchestrator.getPods("stackrox", "central-worker") + .first().metadata.name + def workerLog = orchestrator.getPodLog("stackrox", workerPodName) + assert workerLog.contains("Executing report") + } + + cleanup: + if (config?.getId()) { + ReportV2Service.deleteReportConfig(config.getId()) + } + } + + def "Worker should recover after pod restart"() { + when: + "killing the worker pod" + def workerPods = orchestrator.getPods("stackrox", "central-worker") + assert workerPods.size() == 1 + def oldPodName = workerPods.first().metadata.name + orchestrator.deletePod("stackrox", oldPodName, 0L) + + then: + "a new worker pod should start and complete initialization" + withRetry(20, 5) { + def pods = orchestrator.getPods("stackrox", "central-worker") + assert pods.size() == 1 + def newPodName = pods.first().metadata.name + assert newPodName != oldPodName + def log = orchestrator.getPodLog("stackrox", newPodName) + assert log.contains("central-worker is ready") + assert log.contains("Finished garbage collection cycle") + } + } +} From 10cd7f081c34b626264dbcdbec5c43c04fffd0b6 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 20:20:40 +0200 Subject: [PATCH 237/638] test(ui): accept empty Node CVEs table in Cypress (#22701) --- .../nodeCves/NodeCve.helpers.ts | 40 ++-- .../nodeCves/nodeCvesOverviewPage.test.ts | 22 +- .../nodeCves/nodeDetailPage.test.ts | 219 +++++++----------- 3 files changed, 119 insertions(+), 162 deletions(-) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/NodeCve.helpers.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/NodeCve.helpers.ts index 504e89dcb5d15..341dcc9fa1840 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/NodeCve.helpers.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/NodeCve.helpers.ts @@ -1,14 +1,10 @@ import type { RouteHandler, RouteMatcherOptions } from 'cypress/types/net-stubbing'; import { graphql } from '../../../constants/apiEndpoints'; -import { - getRouteMatcherMapForGraphQL, - interactAndWaitForResponses, -} from '../../../helpers/request'; import { visit, visitWithStaticResponseForPermissions } from '../../../helpers/visit'; -import { selectors as vulnSelectors } from '../vulnerabilities.selectors'; export const nodeCveBaseUrl = '/main/vulnerabilities/node-cves/cves'; +export const nodePageBaseUrl = '/main/vulnerabilities/node-cves/nodes'; // Source of truth for keys in routeMatcherMap and staticResponseMap objects. // Overview page @@ -100,6 +96,16 @@ export function visitNodeCvePage( return visit(mockNodeCvePageUrl, routeMatcherMap, staticResponseMap); } +export function visitNodePage( + nodeId: string, + routeMatcherMap?: Record, + staticResponseMap?: Record, + params?: Record +) { + const paramString = params ? `?${new URLSearchParams(params).toString()}` : ''; + return visit(`${nodePageBaseUrl}/${nodeId}${paramString}`, routeMatcherMap, staticResponseMap); +} + export function visitNodeCvePageWithStaticPermissions( mockCveName: string, resourceToAccess: Record, @@ -130,22 +136,8 @@ export const staticResponseMapForNodePage = { }, }; -export function visitFirstNodeLinkFromTable(): Cypress.Chainable { - // Get the name of the first node in the table and pass it to the caller - return cy - .get('tbody tr td[data-label="Node"] a') - .first() - .then(($link) => { - interactAndWaitForResponses( - () => cy.wrap($link).click(), - getRouteMatcherMapForGraphQL(['getNodeMetadata']) - ); - return cy.wrap($link.text()); - }); -} - -export function visitFirstNodeFromOverviewPage() { - visitNodeCveOverviewPage(); - cy.get(vulnSelectors.entityTypeToggleItem('Node')).click(); - visitFirstNodeLinkFromTable(); -} +export const staticResponseMapForNodes = { + [getNodesOpname]: { + fixture: `vulnerabilities/nodeCves/${getNodesOpname}`, + }, +}; diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts index 7b66a3da45aad..ab89add17082e 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeCvesOverviewPage.test.ts @@ -12,7 +12,7 @@ import { routeMatcherMapForEntityCounts, routeMatcherMapForNodeCves, routeMatcherMapForNodes, - visitFirstNodeLinkFromTable, + staticResponseMapForNodes, visitNodeCveOverviewPage, } from './NodeCve.helpers'; import { @@ -111,12 +111,19 @@ describe('Node CVEs - Overview Page', () => { }); it('should link a Node table row to the correct Node detail page', () => { - visitNodeCveOverviewPage(); - visitEntityTab('Node'); - - visitFirstNodeLinkFromTable().then((name) => { - cy.get('h1').contains(name); + // This table lists nodes that have unsnoozed CVEs. A scan with none leaves + // it empty, so CI cannot rely on live rows. Mock the list and assert hrefs. + visitNodeCveOverviewPage(routeMatcherMapForNodes, staticResponseMapForNodes, { + entityTab: 'Node', }); + + cy.get('tbody tr td[data-label="Node"] a') + .first() + .then(($link) => { + const linkHref = $link.attr('href'); + expect(linkHref).to.match(/\/node-cves\/nodes\/1$/); + expect($link.text()).to.include('cypress-node-1'); + }); }); it('should sort CVE table columns', () => { @@ -187,6 +194,8 @@ describe('Node CVEs - Overview Page', () => { }); it('should sort Node table columns', () => { + // Sorting asserts request payloads. The table may have zero rows when + // no unsnoozed Node CVEs exist; headers still trigger those requests. interceptAndWatchRequests(routeMatcherMapForNodes).then( ({ waitForRequests, @@ -314,6 +323,7 @@ describe('Node CVEs - Overview Page', () => { // expectRequestedQueryToIncludeSubstrings asserts items to be independent // of order in concatenated query string // of CVE Snoozed that is outside scope of test + // Row-content checks no-op when the table is empty (no unsnoozed Node CVEs). interceptAndWatchRequests(routeMatcherMapForNodes).then( ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { // Visit Node tab and wait for initial load diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts index 687e12b993fce..d2a20f7019c73 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/nodeCves/nodeDetailPage.test.ts @@ -8,7 +8,6 @@ import { interceptAndWatchRequests, } from '../../../helpers/request'; import { - assertOnEachRowForColumn, paginateNext, paginatePrevious, queryTableHeader, @@ -26,19 +25,19 @@ import { } from '../workloadCves/WorkloadCves.helpers'; import { getNodeVulnerabilitiesOpname, + nodePageBaseUrl, routeMatcherMapForNodePage, routeMatcherMapForNodes, staticResponseMapForNodePage, - visitFirstNodeFromOverviewPage, + visitNodePage, } from './NodeCve.helpers'; const { assertAvailableFilters } = filterHelpers; -const nodeBaseUrl = '/main/vulnerabilities/node-cves/nodes'; const mockNodeId = '1'; const mockNodeName = 'cypress-node-1'; -const mockNodePageUrl = `${nodeBaseUrl}/${mockNodeId}`; +const mockNodePageUrl = `${nodePageBaseUrl}/${mockNodeId}`; describe('Node CVEs - Node Detail Page', () => { withAuth(); @@ -70,7 +69,7 @@ describe('Node CVEs - Node Detail Page', () => { }); it('should only show relevant filters for the Node Detail page', () => { - visitFirstNodeFromOverviewPage(); + visitNodePage(mockNodeId, routeMatcherMapForNodePage, staticResponseMapForNodePage); assertAvailableFilters({ CVE: ['Name', 'CVSS', 'Discovered Time'], @@ -79,7 +78,7 @@ describe('Node CVEs - Node Detail Page', () => { }); it('should follow the breadcrumb link to the Node list tab', () => { - visitFirstNodeFromOverviewPage(); + visitNodePage(mockNodeId, routeMatcherMapForNodePage, staticResponseMapForNodePage); // clicking the Nodes breadcrumb should navigate to the overview page with the Node tab selected interactAndWaitForResponses(() => { @@ -90,13 +89,7 @@ describe('Node CVEs - Node Detail Page', () => { }); it('should link from a CVE in the table to the CVE detail page', () => { - interactAndWaitForResponses( - () => { - visitFirstNodeFromOverviewPage(); - }, - routeMatcherMapForNodePage, - staticResponseMapForNodePage - ); + visitNodePage(mockNodeId, routeMatcherMapForNodePage, staticResponseMapForNodePage); // clicking a CVE name in the list should navigate to a Node CVE details page cy.get(`table td[data-label="CVE"]`).first().click(); @@ -104,132 +97,94 @@ describe('Node CVEs - Node Detail Page', () => { }); it('should sort CVE table columns', () => { - interceptAndWatchRequests({ - [getNodeVulnerabilitiesOpname]: - routeMatcherMapForNodePage[getNodeVulnerabilitiesOpname], - }).then(({ waitForRequests, waitAndYieldRequestBodyVariables }) => { - visitFirstNodeFromOverviewPage(); - waitForRequests(); - - // check sorting of CVE column - sortByTableHeader('CVE'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'CVE', reversed: true }) - ); - sortByTableHeader('CVE'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'CVE', reversed: false }) - ); - - // check sorting of Top Severity column - sortByTableHeader('Top CVE severity'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'Severity', reversed: true }) - ); - sortByTableHeader('Top CVE severity'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'Severity', reversed: false }) - ); - - // check sorting of CVE status column - sortByTableHeader('CVE status'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'Fixable', reversed: true }) - ); - sortByTableHeader('CVE status'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'Fixable', reversed: false }) - ); - - // check sorting of CVSS column - sortByTableHeader('Top CVSS'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'CVSS', reversed: true }) - ); - sortByTableHeader('Top CVSS'); - waitAndYieldRequestBodyVariables().then( - expectRequestedSort({ field: 'CVSS', reversed: false }) - ); - - // check that the Affected components column is not sortable - queryTableHeader('Affected components'); - queryTableSortHeader('Affected components').should('not.exist'); - }); + interceptAndWatchRequests(routeMatcherMapForNodePage, staticResponseMapForNodePage).then( + ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { + visitNodePage(mockNodeId); + waitForRequests(); + + const waitForVulnQuery = () => + waitAndYieldRequestBodyVariables([getNodeVulnerabilitiesOpname]).then((vars) => + cy.wrap(Array.isArray(vars) ? vars[0] : vars) + ); + + // check sorting of CVE column + sortByTableHeader('CVE'); + waitForVulnQuery().then(expectRequestedSort({ field: 'CVE', reversed: true })); + sortByTableHeader('CVE'); + waitForVulnQuery().then(expectRequestedSort({ field: 'CVE', reversed: false })); + + // check sorting of Top Severity column + sortByTableHeader('Top CVE severity'); + waitForVulnQuery().then(expectRequestedSort({ field: 'Severity', reversed: true })); + sortByTableHeader('Top CVE severity'); + waitForVulnQuery().then( + expectRequestedSort({ field: 'Severity', reversed: false }) + ); + + // check sorting of CVE status column + sortByTableHeader('CVE status'); + waitForVulnQuery().then(expectRequestedSort({ field: 'Fixable', reversed: true })); + sortByTableHeader('CVE status'); + waitForVulnQuery().then(expectRequestedSort({ field: 'Fixable', reversed: false })); + + // check sorting of CVSS column + sortByTableHeader('Top CVSS'); + waitForVulnQuery().then(expectRequestedSort({ field: 'CVSS', reversed: true })); + sortByTableHeader('Top CVSS'); + waitForVulnQuery().then(expectRequestedSort({ field: 'CVSS', reversed: false })); + + // check that the Affected components column is not sortable + queryTableHeader('Affected components'); + queryTableSortHeader('Affected components').should('not.exist'); + } + ); }); it('should filter the CVE table', () => { - interceptAndWatchRequests({ - [getNodeVulnerabilitiesOpname]: - routeMatcherMapForNodePage[getNodeVulnerabilitiesOpname], - }).then(({ waitForRequests, waitAndYieldRequestBodyVariables }) => { - visitFirstNodeFromOverviewPage(); - waitForRequests(); - - // filtering by CVE name should only display rows with a matching name - filterHelpers.addAutocompleteFilter('CVE', 'Name', 'CVE-2021-1234'); - waitAndYieldRequestBodyVariables().then(expectRequestedQuery('CVE:r/CVE-2021-1234')); - // Do not assert on cell contents as the filter value is mocked - filterHelpers.clearFilters(); - waitForRequests(); - - // filtering by Severity should only display rows with a matching top severity - applyLocalSeverityFilters('Low'); - waitAndYieldRequestBodyVariables().then( - expectRequestedQuery('Severity:LOW_VULNERABILITY_SEVERITY') - ); - assertOnEachRowForColumn('Top CVE severity', (_, cell) => { - expect(cell.innerText).to.contain('Low'); - }); - filterHelpers.clearFilters(); - waitForRequests(); - - // filtering by CVE Status should only display rows with a matching status - applyLocalStatusFilters('Fixable'); - waitAndYieldRequestBodyVariables().then(expectRequestedQuery('Fixable:true')); - assertOnEachRowForColumn('CVE status', (_, cell) => { - expect(cell.innerText).to.contain('Fixable'); - }); - filterHelpers.clearFilters(); - waitForRequests(); - - // filtering by CVSS should only display rows with a CVSS in range - filterHelpers.addNumericFilter('CVE', 'CVSS', 'Is less than', 8); - waitAndYieldRequestBodyVariables().then(expectRequestedQuery('CVSS:<8')); - assertOnEachRowForColumn('Top CVSS', (_, cell) => { - const cvss = parseFloat(cell.innerText.replace(/[^0-9.]/g, '')); - expect(cvss).to.be.lessThan(8); - }); - filterHelpers.clearFilters(); - waitForRequests(); - - // filtering by Component should only display rows with a nested table containing a matching component - // - expand each row - // - check that the component name exists in the table - const componentFilter = 'a'; - filterHelpers.addAutocompleteFilter('Node component', 'Name', componentFilter); - waitAndYieldRequestBodyVariables().then( - expectRequestedQuery(`Component:r/${componentFilter}`) - ); - // scope these assertions to the first parent table so that assertions run in the child tables - const columnDataLabel = 'Component'; - cy.get(`table`) - .then(($el) => - $el.find( - `table:has(th:contains("${columnDataLabel}")) td[data-label="${columnDataLabel}"]` - ) - ) - .then(($cells) => { - $cells.each((_, cell) => { - expect(cell.innerText).to.contain(componentFilter); - }); - }); - }); + interceptAndWatchRequests(routeMatcherMapForNodePage, staticResponseMapForNodePage).then( + ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { + visitNodePage(mockNodeId); + waitForRequests(); + + const waitForVulnQuery = () => + waitAndYieldRequestBodyVariables([getNodeVulnerabilitiesOpname]).then((vars) => + cy.wrap(Array.isArray(vars) ? vars[0] : vars) + ); + + // Assert GraphQL query strings. Stubbed rows are not a live filter result. + filterHelpers.addAutocompleteFilter('CVE', 'Name', 'CVE-2021-1234'); + waitForVulnQuery().then(expectRequestedQuery('CVE:r/CVE-2021-1234')); + filterHelpers.clearFilters(); + waitForRequests([getNodeVulnerabilitiesOpname]); + + applyLocalSeverityFilters('Low'); + waitForVulnQuery().then( + expectRequestedQuery('Severity:LOW_VULNERABILITY_SEVERITY') + ); + filterHelpers.clearFilters(); + waitForRequests([getNodeVulnerabilitiesOpname]); + + applyLocalStatusFilters('Fixable'); + waitForVulnQuery().then(expectRequestedQuery('Fixable:true')); + filterHelpers.clearFilters(); + waitForRequests([getNodeVulnerabilitiesOpname]); + + filterHelpers.addNumericFilter('CVE', 'CVSS', 'Is less than', 8); + waitForVulnQuery().then(expectRequestedQuery('CVSS:<8')); + filterHelpers.clearFilters(); + waitForRequests([getNodeVulnerabilitiesOpname]); + + const componentFilter = 'a'; + filterHelpers.addAutocompleteFilter('Node component', 'Name', componentFilter); + waitForVulnQuery().then(expectRequestedQuery(`Component:r/${componentFilter}`)); + } + ); }); it('should correctly paginate the CVE table', () => { interceptAndWatchRequests(routeMatcherMapForNodePage, staticResponseMapForNodePage).then( ({ waitForRequests, waitAndYieldRequestBodyVariables }) => { - visitFirstNodeFromOverviewPage(); + visitNodePage(mockNodeId); waitForRequests(); paginateNext(); @@ -274,7 +229,7 @@ describe('Node CVEs - Node Detail Page', () => { it('should update summary cards when a filter is applied', () => { interceptAndWatchRequests(routeMatcherMapForNodePage, staticResponseMapForNodePage).then( ({ waitForRequests }) => { - visitFirstNodeFromOverviewPage(); + visitNodePage(mockNodeId); waitForRequests(); applyLocalSeverityFilters('Low'); From 9a6f0e3efdbbf2c8436e2149124afeb4379babf9 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:20:19 +0200 Subject: [PATCH 238/638] fix(tests): make e2e shell scripts portable on macOS (#22651) --- scripts/ci/logcheck/check.sh | 14 ++++++++++++-- scripts/retry-kubectl.sh | 8 ++++---- tests/e2e/lib.sh | 6 +++++- tests/upgrade/lib.sh | 7 +++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/scripts/ci/logcheck/check.sh b/scripts/ci/logcheck/check.sh index 3b820b3a5f4c9..8b4d1529c11a4 100755 --- a/scripts/ci/logcheck/check.sh +++ b/scripts/ci/logcheck/check.sh @@ -11,6 +11,16 @@ DIR="$(cd "$(dirname "$0")" && pwd)" +# Block/allow lists use PCRE. BSD grep has no -P; Homebrew GNU grep is `ggrep`. +if grep -P 'a' <<<'a' >/dev/null 2>&1; then + GREP_CMD="grep" +elif command -v ggrep >/dev/null 2>&1; then + GREP_CMD="ggrep" +else + echo "logcheck requires GNU grep (-P). On macOS: brew install grep" >&2 + exit 2 +fi + LINES_OF_CONTEXT=5 BLOCKLIST_FILE="${DIR}/blocklist-patterns" allow_file="${ALLOWLIST_FILE:-${DIR}/allowlist-patterns}" @@ -25,8 +35,8 @@ IFS=$'\n' read -d '' -r -a allowlist_subpatterns < <(egrep -v '^(#.*|\s*)$' "${a allowlist_pattern="$(join_by '|' "${allowlist_subpatterns[@]}")" -if check_out="$(grep -vHP "$allowlist_pattern" "$@" | grep -"${LINES_OF_CONTEXT}" -Pi "$blocklist_pattern")"; then - first_occurence="$(grep -vhP "$allowlist_pattern" "$@" | grep -Pi "$blocklist_pattern" | head -1)" +if check_out="$("${GREP_CMD}" -vHP "$allowlist_pattern" "$@" | "${GREP_CMD}" -"${LINES_OF_CONTEXT}" -Pi "$blocklist_pattern")"; then + first_occurence="$("${GREP_CMD}" -vhP "$allowlist_pattern" "$@" | "${GREP_CMD}" -Pi "$blocklist_pattern" | head -1)" echo "${first_occurence}" echo "${check_out}" exit 1 diff --git a/scripts/retry-kubectl.sh b/scripts/retry-kubectl.sh index 9b6a9169bd49f..aeb9057a2899c 100755 --- a/scripts/retry-kubectl.sh +++ b/scripts/retry-kubectl.sh @@ -20,10 +20,10 @@ The connection to the server [^ ]+ was refused EOT ) -tmp_in="$(mktemp)" -tmp_out="$(mktemp --suffix=-stdout.txt)" -tmp_err="$(mktemp --suffix=-stderr.txt)" -grep_out="$(mktemp)" +tmp_in="$(mktemp "${TMPDIR:-/tmp}/rox-kubectl-stdin.XXXXXX")" +tmp_out="$(mktemp "${TMPDIR:-/tmp}/rox-kubectl-stdout.XXXXXX")" +tmp_err="$(mktemp "${TMPDIR:-/tmp}/rox-kubectl-stderr.XXXXXX")" +grep_out="$(mktemp "${TMPDIR:-/tmp}/rox-kubectl-grep.XXXXXX")" trap 'cat ${tmp_out}; cat ${tmp_err} >&2; rm -f ${tmp_in} ${tmp_out} ${tmp_err} ${grep_out}' EXIT cat > "${tmp_in}" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 89c37497d9e7d..c2fb2ae67f61c 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -1808,7 +1808,11 @@ restore_4_6_postgres_backup() { require_environment "API_ENDPOINT" require_environment "ROX_ADMIN_PASSWORD" - setup_gcp + # CI activates the stackrox SA via setup_gcp. Local runs keep the caller's + # gcloud credentials; this bucket is readable with typical ACS engineer ADC. + if is_CI; then + setup_gcp + fi gsutil cp gs://stackrox-ci-upgrade-test-fixtures/upgrade-test-dbs/postgres_db_4_6.sql.zip . roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index 5c354af6fe8fc..a7a07dd6089aa 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -115,7 +115,7 @@ wait_for_background_migrations() { } get_target_bg_migration_seqnum() { - grep -oP 'CurrentBgMigrationSeqNum\s*=\s*\K[0-9]+' \ + sed -n 's/^const CurrentBgMigrationSeqNum = \([0-9][0-9]*\)$/\1/p' \ "$TEST_ROOT/central/backgroundmigrations/seq_num.go" } @@ -128,7 +128,10 @@ deploy_earlier_postgres_central() { PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl version # Let's try helm - ROX_ADMIN_PASSWORD="$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c12 || true)" + # BSD tr cannot filter /dev/urandom (NUL bytes). gen_admin_password + # produces a value shared with Helm --set and later restore/auth. + ROX_ADMIN_PASSWORD="$(gen_admin_password)" + export ROX_ADMIN_PASSWORD PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output central-services --image-defaults opensource --output-dir /tmp/early-stackrox-central-services-chart --remove helm install -n stackrox --create-namespace stackrox-central-services /tmp/early-stackrox-central-services-chart \ From d71ab1de1b7281511afeef723c0f95c3edfde862 Mon Sep 17 00:00:00 2001 From: Lukas Javorsky Date: Tue, 8 Sep 2026 21:41:07 +0200 Subject: [PATCH 239/638] Add link to the reporting guidelines of CRA reportable events (#22446) --- SECURITY.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5e0a211857a53..37a22a5f32203 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -71,10 +71,9 @@ We follow established industry best practices for secure development incl. but n # EU Cyber Resilience Act — Open Source Steward Statement -This project is stewarded by **Red Hat, Inc.**, an open source software steward -as defined in Article 3(14) of the -[EU Cyber Resilience Act (Regulation 2024/2847)](https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng). -Contact: cra-steward@redhat.com +This project is stewarded by **Red Hat, Inc.**, an open source software steward as defined in Article 3(14) of the [EU Cyber Resilience Act (Regulation 2024/2847)](https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng). +Contact: [cra-steward@redhat.com](mailto:cra-steward@redhat.com) -Refer to [Red Hat's security practices and vulnerability management policy](https://access.redhat.com/security/) -for detailed information. +For guidance on reporting actively exploited vulnerabilities and severe incidents under the CRA, see [Reporting Actively Exploited Vulnerabilities and Severe Incidents](https://github.com/RedHatProductSecurity/CRA/blob/main/Guidelines/eu-cra-incident-and-vulnerability-reporting-guidelines.md). + +Refer to [Red Hat's security practices and vulnerability management policy](https://access.redhat.com/security/) for detailed information. From 5bd4e2ded54e44da1422770ff88a475ff3740260 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:44:53 +0200 Subject: [PATCH 240/638] fix(ui): Remove VM tech preview label (#22698) --- .../virtualMachineCvesOverviewPage.test.ts | 1 + .../Overview/VirtualMachineCvesOverviewPage.tsx | 6 +----- .../VirtualMachine/VirtualMachinePageHeaderLegacy.tsx | 6 +----- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts index 68086e2308f10..5aa785365c842 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/virtualMachineCves/virtualMachineCvesOverviewPage.test.ts @@ -54,6 +54,7 @@ describe('Virtual Machine CVEs - Overview Page', () => { ); cy.get('h1').contains('Virtual machine vulnerabilities'); + cy.contains('Technology preview').should('not.exist'); cy.get('body').contains('Prioritize and remediate observed CVEs across virtual machines'); }); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx index 1007dbbe943f9..ffe629f7ca169 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/Overview/VirtualMachineCvesOverviewPage.tsx @@ -1,7 +1,6 @@ import { Content, Flex, PageSection, Title } from '@patternfly/react-core'; import PageTitle from 'Components/PageTitle'; -import TechnologyPreviewLabel from 'Components/PatternFly/PreviewLabel/TechnologyPreviewLabel'; import { useManagedColumns } from 'hooks/useManagedColumns'; import useFeatureFlags from 'hooks/useFeatureFlags'; import useURLPagination from 'hooks/useURLPagination'; @@ -37,10 +36,7 @@ function VirtualMachineCvesOverviewPage() { - - Virtual machine vulnerabilities - - + Virtual machine vulnerabilities Prioritize and remediate observed CVEs across virtual machines diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageHeaderLegacy.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageHeaderLegacy.tsx index 025c165cbdeeb..a6124b41323bf 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageHeaderLegacy.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VirtualMachineCves/VirtualMachine/VirtualMachinePageHeaderLegacy.tsx @@ -1,6 +1,5 @@ import { Alert, Flex, Label, LabelGroup, Title } from '@patternfly/react-core'; -import TechnologyPreviewLabel from 'Components/PatternFly/PreviewLabel/TechnologyPreviewLabel'; import type { VirtualMachine } from 'services/VirtualMachineService'; import { getDateTime } from 'utils/dateUtils'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; @@ -46,10 +45,7 @@ function VirtualMachinePageHeaderLegacy({ return ( - - {virtualMachine.name} - - + {virtualMachine.name} - {isRiskSummaryEnabled && aiRiskSummary.isPresent && data && ( + {isAiSummaryAvailable && aiRiskSummary.isPresent && data && ( - - Red Hat Lightspeed icon - IBM watsonx code assistant, AI, ML, artificial intelligence, machine learning - - - - - - 2024-03-01T15:25:40.503Z - pending - TRA042cdfc0-8130-4acf-88da-1a324d8a8d9c - Icon - 2024-03-01T15:25:40.503Z - square - true - pending - 2024-07-12T23:52:22.997Z - rhcc-audience:internal - no - Technology icon - DER042cdfc0-8130-4acf-88da-1a324d8a8d9c - pmeilleu@redhat.com - Black - yes - - - rhcc-product:red-hat-ansible-automation-platform - rhcc-product:red-hat-enterprise-linux - rhcc-product:red-hat-openshift - - - - - Technology icon - - - image/svg+xml - 2024-05-10T12:27:57.413Z - - - Red Hat Lightspeed icon - - - - - IBM watsonx code assistant, AI, ML, artificial - intelligence, machine learning - - - Activate - Activate - 2024-10-02T19:50:21.898Z - workflow-process-service - Activate - workflow-process-service - false - 2024-10-02T19:50:21.898Z - workflow-process-service - 2024-10-02T19:50:21.898Z - 38 - 38 - - - Use technology icons to represent Red Hat products and - components. Do not remove the icon from the bounding shape. - - - - - - - - - - - - - - - - - - - - - diff --git a/ui/apps/platform/src/services/AiIntegrationsService.ts b/ui/apps/platform/src/services/AiIntegrationsService.ts deleted file mode 100644 index 0e63df4d66a5d..0000000000000 --- a/ui/apps/platform/src/services/AiIntegrationsService.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios from './instance'; -import type { Empty } from './types'; - -const aiIntegrationsUrl = '/v2/ai-integrations'; - -export type AiIntegrationType = 'AI_INTEGRATION_TYPE_UNSPECIFIED' | 'AI_INTEGRATION_TYPE_OLS'; - -export type AiIntegration = { - id: string; - name: string; - type: AiIntegrationType; - serviceUrl: string; -}; - -type ListAiIntegrationsResponse = { - integrations: AiIntegration[]; -}; - -export function fetchAiIntegrations(): Promise<{ integrations: AiIntegration[] }> { - return axios - .get(aiIntegrationsUrl) - .then((response) => response.data); -} - -export function fetchAiIntegration(id: string): Promise { - return axios.get(`${aiIntegrationsUrl}/${id}`).then((response) => response.data); -} - -export function createAiIntegration(data: AiIntegration): Promise { - return axios.post(aiIntegrationsUrl, data).then((response) => response.data); -} - -export function updateAiIntegration(data: AiIntegration): Promise { - return axios - .put(`${aiIntegrationsUrl}/${data.id}`, data) - .then((response) => response.data); -} - -export function deleteAiIntegration(id: string): Promise { - return axios.delete(`${aiIntegrationsUrl}/${id}`).then((response) => response.data); -} - -export function deleteAiIntegrations(ids: string[]): Promise { - return Promise.all(ids.map(deleteAiIntegration)); -} - -export function testAiIntegration(data: AiIntegration): Promise { - return axios.post(`${aiIntegrationsUrl}/test`, data).then((response) => response.data); -} diff --git a/ui/apps/platform/src/services/DeploymentsService.ts b/ui/apps/platform/src/services/DeploymentsService.ts index dbbf201879552..a35ea856dde90 100644 --- a/ui/apps/platform/src/services/DeploymentsService.ts +++ b/ui/apps/platform/src/services/DeploymentsService.ts @@ -159,6 +159,11 @@ export type DeploymentRiskSummary = { summary: string; }; +export type TestLightspeedConnectionResponse = { + success: boolean; + message: string; +}; + /** * Fetches an AI-generated risk summary for a deployment by ID. */ @@ -167,6 +172,21 @@ export function fetchDeploymentRiskSummary(id: string): Promise(`${deploymentWithRiskUrl}/${id}/ai-summary`) + .get(`${deploymentWithRiskUrl}/${id}/ai-summary`, { timeout: 60000 }) .then((response) => response.data); } + +/** + * Tests connectivity to the Lightspeed AI service. + */ +export function testLightspeedConnection(): CancellableRequest { + return makeCancellableAxiosRequest((signal) => + axios + .post( + `${deploymentWithRiskUrl}/ai-summary/test`, + undefined, + { signal } + ) + .then((response) => response.data) + ); +} diff --git a/ui/apps/platform/src/services/IntegrationsService.ts b/ui/apps/platform/src/services/IntegrationsService.ts index a7eb4ec88feb1..02a9ce9f05485 100644 --- a/ui/apps/platform/src/services/IntegrationsService.ts +++ b/ui/apps/platform/src/services/IntegrationsService.ts @@ -12,7 +12,6 @@ export const serviceIntegrationSources = [ 'notifiers', 'signatureIntegrations', 'cloudSources', - 'aiIntegrations', ] as const; export type IntegrationSource = (typeof serviceIntegrationSources)[number]; @@ -35,8 +34,6 @@ function getPath(source: IntegrationSource): string { return '/v1/auth/m2m'; case 'cloudSources': return '/v1/cloud-sources'; - case 'aiIntegrations': - return '/v2/ai-integrations'; default: return ''; } diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index 000fd5b18b93f..4251f28f5af30 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -2,7 +2,6 @@ // However, add strings in alphabetical order to minimize merge conflicts when multiple people add or delete strings. // prettier-ignore export type FeatureFlagEnvVar = - | 'ROX_AI_INTEGRATIONS' | 'ROX_BASE_IMAGE_DETECTION' | 'ROX_CISA_KEV' | 'ROX_CVE_FIX_TIMESTAMP' diff --git a/ui/apps/platform/src/types/integration.ts b/ui/apps/platform/src/types/integration.ts index 4fe5aad6f4bc1..abd6823411a73 100644 --- a/ui/apps/platform/src/types/integration.ts +++ b/ui/apps/platform/src/types/integration.ts @@ -7,7 +7,6 @@ export const integrationSources = [ 'notifiers', 'backups', 'cloudSources', - 'aiIntegrations', 'authProviders', 'apiClients', ] as const; @@ -24,8 +23,7 @@ export type IntegrationType = | ImageIntegrationType | NotifierIntegrationType | SignatureIntegrationType - | CloudSourceIntegrationType - | AiIntegrationType; + | CloudSourceIntegrationType; export type AuthProviderType = 'apitoken' | 'machineAccess'; @@ -75,8 +73,6 @@ export type SignatureIntegrationType = 'signature'; export type CloudSourceIntegrationType = 'paladinCloud' | 'ocm'; -export type AiIntegrationType = 'lightspeed'; - export type BaseIntegration = { id: string; name: string; From ec6ee8976970fa5a59cc60dbcf74706c9731520b Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Tue, 8 Sep 2026 20:27:47 -0300 Subject: [PATCH 244/638] ROX-36127: switch vulnerability pipeline to production (#22484) --- .github/workflows/scanner-updater-bundle.yaml | 6 +----- .github/workflows/scanner-updater-export.yaml | 12 ++++++++---- .github/workflows/scanner-updater-schedule.yaml | 9 ++------- .../scanner-versioned-definitions-update.yaml | 2 -- scanner/updater/ci/source-config.yaml | 4 ++-- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.github/workflows/scanner-updater-bundle.yaml b/.github/workflows/scanner-updater-bundle.yaml index b4721c659fa45..8f26a5e8e6ba4 100644 --- a/.github/workflows/scanner-updater-bundle.yaml +++ b/.github/workflows/scanner-updater-bundle.yaml @@ -3,9 +3,6 @@ name: Scanner updater bundle # and assembles vulnerabilities.zip for each bundle stream defined in # VULNERABILITY_BUNDLE_VERSION. Missing or empty sources are skipped with a # Slack notification; if no sources are valid the job fails. -# TODO(ROX-36127): production cutover — update storage.bucket in -# source-config.yaml to definitions.stackrox.io and remove the schedule from -# scanner-versioned-definitions-update.yaml. on: schedule: @@ -73,8 +70,7 @@ jobs: - name: Authenticate with GCS uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 with: - # TODO(ROX-36127): production cutover — use GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER. - credentials_json: ${{ secrets.GOOGLE_SA_CIRCLECI_SCANNER }} + credentials_json: ${{ secrets.GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER }} - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 diff --git a/.github/workflows/scanner-updater-export.yaml b/.github/workflows/scanner-updater-export.yaml index 603d4e51db857..e986e6b4a0afa 100644 --- a/.github/workflows/scanner-updater-export.yaml +++ b/.github/workflows/scanner-updater-export.yaml @@ -56,8 +56,7 @@ jobs: - name: Authenticate with GCS uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 with: - # TODO(ROX-36127): production cutover — use GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER. - credentials_json: ${{ secrets.GOOGLE_SA_CIRCLECI_SCANNER }} + credentials_json: ${{ secrets.GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER }} - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 @@ -151,10 +150,15 @@ jobs: scanner/bin/updater export --manual-url "$MANUAL_URL" bundles - name: Authenticate with GCS for upload + if: ${{ !inputs.skip_freshness_check }} + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER }} + + - name: Authenticate with GCS for upload (PR) + if: ${{ inputs.skip_freshness_check }} uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 with: - # TODO(ROX-36127): production cutover — use GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER - # for non-PR runs (select via inputs.skip_freshness_check or a dedicated input). credentials_json: ${{ secrets.GOOGLE_SA_CIRCLECI_SCANNER }} - name: Set up Cloud SDK diff --git a/.github/workflows/scanner-updater-schedule.yaml b/.github/workflows/scanner-updater-schedule.yaml index 99eebdd5874a9..7999ec6045cbc 100644 --- a/.github/workflows/scanner-updater-schedule.yaml +++ b/.github/workflows/scanner-updater-schedule.yaml @@ -1,8 +1,4 @@ name: Scanner updater schedule -# This workflow writes to the test bucket while under validation. -# Once the bundler workflow is ready and the full pipeline is verified, -# update storage.bucket in source-config.yaml to definitions.stackrox.io -# and remove the schedule from scanner-versioned-definitions-update.yaml. on: schedule: @@ -39,8 +35,7 @@ jobs: if: github.event_name != 'pull_request' uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # ratchet:google-github-actions/auth@v3 with: - # TODO(ROX-36127): production cutover — use GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER. - credentials_json: ${{ secrets.GOOGLE_SA_CIRCLECI_SCANNER }} + credentials_json: ${{ secrets.GOOGLE_SA_STACKROX_HUB_VULN_DUMP_UPLOADER }} - name: Set up Cloud SDK if: github.event_name != 'pull_request' @@ -82,7 +77,7 @@ jobs: run: | set -euo pipefail sc=./.github/workflows/scripts/scanner-updater-config.sh - bucket=$("$sc" bucket) + bucket="scanner-v4-test" prefix=$("$sc" prefix) matrix="{\"include\":[{\"source\":\"manual\",\"ref\":\"${PR_SHA}\",\"object\":\"gs://${bucket}/${prefix}/pr-${PR_NUMBER}/sources/manual.json.zst\"}]}" printf '{"has_due":true,"has_errors":false,"errors":[],"matrix":%s}\n' "$matrix" > matrix-result.json diff --git a/.github/workflows/scanner-versioned-definitions-update.yaml b/.github/workflows/scanner-versioned-definitions-update.yaml index a210f69d5b340..3216f74a5bcf5 100644 --- a/.github/workflows/scanner-versioned-definitions-update.yaml +++ b/.github/workflows/scanner-versioned-definitions-update.yaml @@ -1,8 +1,6 @@ name: Scanner versioned vulnerabilities update on: - schedule: - - cron: "30 */4 * * *" pull_request: types: - opened diff --git a/scanner/updater/ci/source-config.yaml b/scanner/updater/ci/source-config.yaml index cdd4dcf6eef47..4ddcc3c5d0533 100644 --- a/scanner/updater/ci/source-config.yaml +++ b/scanner/updater/ci/source-config.yaml @@ -12,8 +12,8 @@ # as the pipeline evolves. storage: - bucket: scanner-v4-test - prefix: vulnerability-bundles + bucket: definitions.stackrox.io + prefix: v4/vulnerability-bundles bundles: dev: From b9e3240d019e57c703dd829c0749dd5269027472 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 04:17:52 +0000 Subject: [PATCH 245/638] Update Konflux references to fa53ef4 (#22724) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 2 +- .tekton/main-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 6960cb915120c..30a4fb2150faa 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -433,7 +433,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:fa53ef450ba538addafee1211ff6ac15b55865ce652fec15ac99b554680cb642 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index f46e834fb8197..76d2d5de1d1d2 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -455,7 +455,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:fa53ef450ba538addafee1211ff6ac15b55865ce652fec15ac99b554680cb642 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 9668d02c5bf1e..c56cb5c5c7da7 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -453,7 +453,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:c07d2befa8abf48d4a223a5bdf5ea2335e756d4d0bbc422761087c263060aa94 + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:fa53ef450ba538addafee1211ff6ac15b55865ce652fec15ac99b554680cb642 - name: kind value: task resolver: bundles From 15158debd7f22a7d0df96960e4b3cf7089766fa6 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 04:20:15 +0000 Subject: [PATCH 246/638] Refresh RPM lockfiles [SECURITY] (#22726) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 232 +++++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 104 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 71a6d84f3be39..fb563b41e14b1 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2878589 - checksum: sha256:87eed21015538c9fade652055715872c8a1d7fc5d38df349a3c44e6f27530804 + size: 2879929 + checksum: sha256:62e7fb8318b2c8d630c67139f09325dbc2bed069ff0b80c0b279396aa85ed1f1 name: kernel-headers - evr: 5.14.0-687.44.1.el9_8 - sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm + evr: 5.14.0-687.45.1.el9_8 + sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -340,13 +340,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.1.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 120882 - checksum: sha256:7cc4e533f63bdec0ead003d176cb262b8d4d2583dfd57b2e9cfeeed4ead13475 + size: 122400 + checksum: sha256:df004398da989f75bae9267cfb0ad402ada6e6db46ebef0932c869894c013c08 name: expat - evr: 2.5.0-6.el9_8.1 - sourcerpm: expat-2.5.0-6.el9_8.1.src.rpm + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 5003914 @@ -851,13 +851,13 @@ arches: name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 635826 - checksum: sha256:eab9afe4a8ef70912d8c59c0d8c429c0474436e756d37bcd9ee0589f9a1c9311 + size: 642233 + checksum: sha256:12271815e3a5d35dbc4a8e1e3b52442b0d00b833816ef3a923da427a3bca561a name: pam - evr: 1.5.1-28.el9 - sourcerpm: pam-1.5.1-28.el9.src.rpm + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pcre-8.44-4.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 187289 @@ -1214,12 +1214,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 8390481 - checksum: sha256:3ef9a4df767dbc5e75944b5c197d186f8b5ca8745522e07cdf1a195229d381b4 + size: 8403095 + checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 name: expat - evr: 2.5.0-6.el9_8.1 + evr: 2.5.0-6.el9_8.3 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 20486 @@ -1286,6 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + repoid: rhel-9-for-aarch64-baseos-source-rpms + size: 152427730 + checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + name: kernel + evr: 5.14.0-687.45.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1514,12 +1520,12 @@ arches: checksum: sha256:6f06a4f6812fce869528ff17ab002d655a8da18fc745eafa6c02594bb23dfeeb name: p11-kit evr: 0.26.4-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9_8.1.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 1133226 - checksum: sha256:d88f44d73e9ccb288d75fd5490dc1434b06e88966febed66648775a0b46fb50c + size: 1142205 + checksum: sha256:fe61734fdb226915dff402bcd1ad2002e0fd7a4dad9042af65688804f428b30b name: pam - evr: 1.5.1-28.el9 + evr: 1.5.1-28.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pcre-8.44-4.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 1624356 @@ -1629,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0f4d325d39208aaf50aeec23a12b0a49fa86588b9c3e291429f39252b895b605-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0e9a5114665a991528e7b5b7fc6df6f4e56c465bc1f69fa94d84dbbbb13a05fb-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 65846 - checksum: sha256:0f4d325d39208aaf50aeec23a12b0a49fa86588b9c3e291429f39252b895b605 + size: 65894 + checksum: sha256:0e9a5114665a991528e7b5b7fc6df6f4e56c465bc1f69fa94d84dbbbb13a05fb - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1705,13 +1711,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2902293 - checksum: sha256:0784f765e44b42d297bc6cbcdaa2e9a8970b7d1e909d0e2aa87c75f8c65197c3 + size: 2903733 + checksum: sha256:aabe7d848f91d233740825606a5773e8b5c85fb97a5caae0ac53434afc161788 name: kernel-headers - evr: 5.14.0-687.44.1.el9_8 - sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm + evr: 5.14.0-687.45.1.el9_8 + sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -1971,13 +1977,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/e/expat-2.5.0-6.el9_8.1.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 132202 - checksum: sha256:eae0d2c0c23adc9b878e86d46a7cd176e991a84fc5780877dd58f15552b6a5c6 + size: 133470 + checksum: sha256:731fb187d6550a4a5fc6227d43370464d67a68e9fb41481c2e86b95e4560345b name: expat - evr: 2.5.0-6.el9_8.1 - sourcerpm: expat-2.5.0-6.el9_8.1.src.rpm + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/f/filesystem-3.16-5.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 5003944 @@ -2489,13 +2495,13 @@ arches: name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pam-1.5.1-28.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 677440 - checksum: sha256:4d7ae59c6b8bfd1548bf5d0b44081b77d966f63aac6057f48731c0a0f1e794e8 + size: 683853 + checksum: sha256:a855ac273e5c783879fc57f27886a7d9e662e8c5988777113688736dc59a23fd name: pam - evr: 1.5.1-28.el9 - sourcerpm: pam-1.5.1-28.el9.src.rpm + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pcre-8.44-4.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 208333 @@ -2852,12 +2858,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 8390481 - checksum: sha256:3ef9a4df767dbc5e75944b5c197d186f8b5ca8745522e07cdf1a195229d381b4 + size: 8403095 + checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 name: expat - evr: 2.5.0-6.el9_8.1 + evr: 2.5.0-6.el9_8.3 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 20486 @@ -2924,6 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + repoid: rhel-9-for-ppc64le-baseos-source-rpms + size: 152427730 + checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + name: kernel + evr: 5.14.0-687.45.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3158,12 +3170,12 @@ arches: checksum: sha256:6f06a4f6812fce869528ff17ab002d655a8da18fc745eafa6c02594bb23dfeeb name: p11-kit evr: 0.26.4-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9_8.1.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 1133226 - checksum: sha256:d88f44d73e9ccb288d75fd5490dc1434b06e88966febed66648775a0b46fb50c + size: 1142205 + checksum: sha256:fe61734fdb226915dff402bcd1ad2002e0fd7a4dad9042af65688804f428b30b name: pam - evr: 1.5.1-28.el9 + evr: 1.5.1-28.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pcre-8.44-4.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 1624356 @@ -3273,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/955a1862a29b3f1fc04251f0e8cdf847e6549cde0f8c2866af52281b5685635e-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/4ba83a04fb5c3aeaa965553dca22ff7e512ec7e7b7fe88b3785b66fd19025b1a-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 65342 - checksum: sha256:955a1862a29b3f1fc04251f0e8cdf847e6549cde0f8c2866af52281b5685635e + size: 65561 + checksum: sha256:4ba83a04fb5c3aeaa965553dca22ff7e512ec7e7b7fe88b3785b66fd19025b1a - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3349,13 +3361,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2908493 - checksum: sha256:e2b6ad52e6746e5a0311be5cdd15298888d1f5af72088b724db7303657c08b72 + size: 2909877 + checksum: sha256:e0f15f18c9c6e2c8451b4ef0b722d9a979a4bf5dc419c75273da560e4da8c89f name: kernel-headers - evr: 5.14.0-687.44.1.el9_8 - sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm + evr: 5.14.0-687.45.1.el9_8 + sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -3615,13 +3627,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/e/expat-2.5.0-6.el9_8.1.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 123151 - checksum: sha256:a5b24ad347a1722a948f417a7289a89b1aee9047350e88866173e05157239142 + size: 124579 + checksum: sha256:e6bd000496dc070c1e4cfd17162d512111341820f12c10442b8752c0767c68a0 name: expat - evr: 2.5.0-6.el9_8.1 - sourcerpm: expat-2.5.0-6.el9_8.1.src.rpm + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/f/filesystem-3.16-5.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 5003897 @@ -4126,13 +4138,13 @@ arches: name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pam-1.5.1-28.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 630422 - checksum: sha256:25f76128edb27d622927cadb5dc485e0e72c546457598c9cceb4b8364cf53e6a + size: 636601 + checksum: sha256:6ed26e54f724fead3c4368e5da20237b9d61bbd6f6dbe1bc60970a6255cfbb17 name: pam - evr: 1.5.1-28.el9 - sourcerpm: pam-1.5.1-28.el9.src.rpm + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pcre-8.44-4.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 121477 @@ -4483,12 +4495,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 8390481 - checksum: sha256:3ef9a4df767dbc5e75944b5c197d186f8b5ca8745522e07cdf1a195229d381b4 + size: 8403095 + checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 name: expat - evr: 2.5.0-6.el9_8.1 + evr: 2.5.0-6.el9_8.3 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 20486 @@ -4555,6 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + repoid: rhel-9-for-s390x-baseos-source-rpms + size: 152427730 + checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + name: kernel + evr: 5.14.0-687.45.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4783,12 +4801,12 @@ arches: checksum: sha256:6f06a4f6812fce869528ff17ab002d655a8da18fc745eafa6c02594bb23dfeeb name: p11-kit evr: 0.26.4-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9_8.1.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 1133226 - checksum: sha256:d88f44d73e9ccb288d75fd5490dc1434b06e88966febed66648775a0b46fb50c + size: 1142205 + checksum: sha256:fe61734fdb226915dff402bcd1ad2002e0fd7a4dad9042af65688804f428b30b name: pam - evr: 1.5.1-28.el9 + evr: 1.5.1-28.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pcre-8.44-4.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 1624356 @@ -4898,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/c6082334ed48bc1d5349e998e1c23b9b71a58734ea71d2ebfe84f2334335dba7-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/668e057d4dcd8b7a8a8099b1bd2994fbd30b9a45965337108a656168fd5d4b7a-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 63808 - checksum: sha256:c6082334ed48bc1d5349e998e1c23b9b71a58734ea71d2ebfe84f2334335dba7 + size: 63885 + checksum: sha256:668e057d4dcd8b7a8a8099b1bd2994fbd30b9a45965337108a656168fd5d4b7a - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4981,13 +4999,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.44.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2917773 - checksum: sha256:e97b12ed737d41e2e34db2d3f3659a5f250ef940fcc9c4dd97235009c8706e7f + size: 2919193 + checksum: sha256:73240f2b54765a2e2cd551a836d927acc78fa4f882a7d3a924e65c8177e7e97a name: kernel-headers - evr: 5.14.0-687.44.1.el9_8 - sourcerpm: kernel-5.14.0-687.44.1.el9_8.src.rpm + evr: 5.14.0-687.45.1.el9_8 + sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5233,13 +5251,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.1.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 126909 - checksum: sha256:6a0c98d67b643fe620297e4cd7e990f99a86ed6bc90a8295a952c626ecbb9f62 + size: 128577 + checksum: sha256:3c9c96529f94f84fc19c8039d8852269e1085d16b5af2933f44ff286dec66a35 name: expat - evr: 2.5.0-6.el9_8.1 - sourcerpm: expat-2.5.0-6.el9_8.1.src.rpm + evr: 2.5.0-6.el9_8.3 + sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 5003807 @@ -5737,13 +5755,13 @@ arches: name: p11-kit-trust evr: 0.26.4-1.el9_8 sourcerpm: p11-kit-0.26.4-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pam-1.5.1-28.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 637912 - checksum: sha256:15a0a5000af6f57f47c30682f7aacf3959903788e68255132b6a05bb4b713ab1 + size: 645147 + checksum: sha256:9285aea93392dada0c8399b7d8a1c85ce7a6fdafd2fb5264727448549c15637c name: pam - evr: 1.5.1-28.el9 - sourcerpm: pam-1.5.1-28.el9.src.rpm + evr: 1.5.1-28.el9_8.1 + sourcerpm: pam-1.5.1-28.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pcre-8.44-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 205261 @@ -6100,12 +6118,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 8390481 - checksum: sha256:3ef9a4df767dbc5e75944b5c197d186f8b5ca8745522e07cdf1a195229d381b4 + size: 8403095 + checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 name: expat - evr: 2.5.0-6.el9_8.1 + evr: 2.5.0-6.el9_8.3 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 20486 @@ -6172,6 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + repoid: rhel-9-for-x86_64-baseos-source-rpms + size: 152427730 + checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + name: kernel + evr: 5.14.0-687.45.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6400,12 +6424,12 @@ arches: checksum: sha256:6f06a4f6812fce869528ff17ab002d655a8da18fc745eafa6c02594bb23dfeeb name: p11-kit evr: 0.26.4-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pam-1.5.1-28.el9_8.1.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 1133226 - checksum: sha256:d88f44d73e9ccb288d75fd5490dc1434b06e88966febed66648775a0b46fb50c + size: 1142205 + checksum: sha256:fe61734fdb226915dff402bcd1ad2002e0fd7a4dad9042af65688804f428b30b name: pam - evr: 1.5.1-28.el9 + evr: 1.5.1-28.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pcre-8.44-4.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 1624356 @@ -6515,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/a495c1c5432347fd4d0a098d82615cb3ec1f2b993088ff6b8f59f4fed922a288-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/f8b0da92fb3d890c8c68ac2b64b87aba3ff7b52a22debd0262f3d6e49ebea660-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 68056 - checksum: sha256:a495c1c5432347fd4d0a098d82615cb3ec1f2b993088ff6b8f59f4fed922a288 + size: 68115 + checksum: sha256:f8b0da92fb3d890c8c68ac2b64b87aba3ff7b52a22debd0262f3d6e49ebea660 From 453179c644ee6304532c610db752648b5d6df63d Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 04:35:22 +0000 Subject: [PATCH 247/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 4060c43 (#22725) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 30a4fb2150faa..af38e41601ed7 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index bd2719e0e832e..dd48051f11391 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 5935ce132d0c1..929939192dcbd 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 76d2d5de1d1d2..3f42185fecfd6 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 0c40d2393fb07..c900ed07f08c3 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 397ab1935b203..7c00de2ca5775 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index d08faf87dea1a..8574961b5d8c0 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index c56cb5c5c7da7..84fd734caa153 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c93950ffd9f9cbff681a16fe4f9ba77b729730b15d32e2cc5e07cdbf286940db + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 - name: kind value: task resolver: bundles From 1db9fb2f2b014d06b9387791c631ef4f4f901027 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Tue, 8 Sep 2026 23:17:55 -0700 Subject: [PATCH 248/638] (fixes): Lightspeed integration bug bash fixes (#22622) --- central/deployment/service/ai_prompt.go | 4 +- .../service/risk_ai_summary_test.go | 49 +++++++++---------- central/deployment/service/service_impl.go | 4 +- central/lightspeed/client/client.go | 5 +- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/central/deployment/service/ai_prompt.go b/central/deployment/service/ai_prompt.go index 86e9c60d66d87..69efe593c68e0 100644 --- a/central/deployment/service/ai_prompt.go +++ b/central/deployment/service/ai_prompt.go @@ -13,11 +13,11 @@ TONE: Brief incident report. Short declarative sentences. No filler. CONTEXT ALREADY VISIBLE TO THE USER: The user already sees the deployment name, namespace, cluster, risk score, and a stat summary bar (policy violations, CVE count, -image age, component count) in the UI. Do NOT restate any of that. +image age, component count) in the UI. Process arguments are redacted. Do NOT restate any of that. Start with the insight. If the deployment has no significant risk factors (normalized score -below 25), state that in one sentence. Do not generate a risk +below 5), state that in one sentence. Do not generate a risk breakdown or actions. Use these exact section labels with no additional text: diff --git a/central/deployment/service/risk_ai_summary_test.go b/central/deployment/service/risk_ai_summary_test.go index 790d85f5afac6..58a169b4371ad 100644 --- a/central/deployment/service/risk_ai_summary_test.go +++ b/central/deployment/service/risk_ai_summary_test.go @@ -125,9 +125,10 @@ func TestGetDeploymentRiskAISummary_Success(t *testing.T) { require.NoError(t, err) assert.Equal(t, "SUMMARY\nThis deployment runs as cluster-admin with a privileged container.", resp.GetSummary()) - // Verify prompt was passed correctly. - assert.Equal(t, aiSummaryPrompt, olsMock.captured.Query) - assert.NotEmpty(t, olsMock.captured.Context) + // Verify prompt and context were combined into the query. + assert.Contains(t, olsMock.captured.Query, aiSummaryPrompt) + assert.Contains(t, olsMock.captured.Query, "DEPLOYMENT AND RISK DATA:") + assert.Contains(t, olsMock.captured.Query, "nginx-frontend") } func TestGetDeploymentRiskAISummary_SensitiveFieldsStripped(t *testing.T) { @@ -191,7 +192,6 @@ func TestGetDeploymentRiskAISummary_SensitiveFieldsStripped(t *testing.T) { mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-2").Return(deployment, true, nil) mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) - var capturedContext string olsMock := &mockOLSClient{ response: &olsClient.QueryResponse{Response: "Low risk deployment."}, } @@ -205,36 +205,35 @@ func TestGetDeploymentRiskAISummary_SensitiveFieldsStripped(t *testing.T) { _, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-2"}) require.NoError(t, err) - capturedContext = olsMock.captured.Context + capturedQuery := olsMock.captured.Query - // Sensitive env vars must NOT appear in context sent to LLM. - assert.NotContains(t, capturedContext, "SERVICENOW_URL") - assert.NotContains(t, capturedContext, "https://company.service-now.com") - assert.NotContains(t, capturedContext, "SN_USER") - assert.NotContains(t, capturedContext, "admin") - assert.NotContains(t, capturedContext, "SN_SECRET_REF") - assert.NotContains(t, capturedContext, "vault:secret/data/sn-key") + // Sensitive env vars must NOT appear in query sent to LLM. + assert.NotContains(t, capturedQuery, "SERVICENOW_URL") + assert.NotContains(t, capturedQuery, "https://company.service-now.com") + assert.NotContains(t, capturedQuery, "SN_USER") + assert.NotContains(t, capturedQuery, "SN_SECRET_REF") + assert.NotContains(t, capturedQuery, "vault:secret/data/sn-key") // Secret mount paths must NOT appear. - assert.NotContains(t, capturedContext, "/var/run/secrets/servicenow") - assert.NotContains(t, capturedContext, "sn-credentials") + assert.NotContains(t, capturedQuery, "/var/run/secrets/servicenow") + assert.NotContains(t, capturedQuery, "sn-credentials") // Internal IDs must NOT appear. - assert.NotContains(t, capturedContext, "sha256:abc123") - assert.NotContains(t, capturedContext, "img-v2-id") - assert.NotContains(t, capturedContext, "dep-2") // deployment ID - assert.NotContains(t, capturedContext, "risk-2") // risk ID + assert.NotContains(t, capturedQuery, "sha256:abc123") + assert.NotContains(t, capturedQuery, "img-v2-id") + assert.NotContains(t, capturedQuery, "dep-2") // deployment ID + assert.NotContains(t, capturedQuery, "risk-2") // risk ID // Command, args, and directory must NOT appear. - assert.NotContains(t, capturedContext, "/bin/app") - assert.NotContains(t, capturedContext, "--port=8080") + assert.NotContains(t, capturedQuery, "/bin/app") + assert.NotContains(t, capturedQuery, "--port=8080") // Relevant fields MUST appear. - assert.Contains(t, capturedContext, "mid-server") - assert.Contains(t, capturedContext, "operations") - assert.Contains(t, capturedContext, "cluster-west") - assert.Contains(t, capturedContext, "registry.example.com/app:v2") - assert.Contains(t, capturedContext, "Image is 180 days old") + assert.Contains(t, capturedQuery, "mid-server") + assert.Contains(t, capturedQuery, "operations") + assert.Contains(t, capturedQuery, "cluster-west") + assert.Contains(t, capturedQuery, "registry.example.com/app:v2") + assert.Contains(t, capturedQuery, "Image is 180 days old") } func TestGetDeploymentRiskAISummary_DeploymentNotFound(t *testing.T) { diff --git a/central/deployment/service/service_impl.go b/central/deployment/service/service_impl.go index 447031681e6c2..e23fc453a496b 100644 --- a/central/deployment/service/service_impl.go +++ b/central/deployment/service/service_impl.go @@ -308,9 +308,9 @@ func (s *serviceImpl) GetDeploymentRiskAISummary(ctx context.Context, request *v return nil, errors.Wrap(err, "building risk context for AI summary") } + query := aiSummaryPrompt + "\n\nDEPLOYMENT AND RISK DATA:\n" + contextJSON olsResp, err := s.lightspeedClient.Query(ctx, &olsClient.QueryRequest{ - Query: aiSummaryPrompt, - Context: contextJSON, + Query: query, }) if err != nil { log.Errorf("Lightspeed query failed for deployment %s: %v", request.GetId(), err) diff --git a/central/lightspeed/client/client.go b/central/lightspeed/client/client.go index 975d2679c0826..abf1aa53e90e1 100644 --- a/central/lightspeed/client/client.go +++ b/central/lightspeed/client/client.go @@ -20,7 +20,7 @@ import ( const ( serviceOperatorCAPath = "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" - defaultRequestTimeout = 30 * time.Second + defaultRequestTimeout = 120 * time.Second ) var ( @@ -31,8 +31,7 @@ var ( // QueryRequest is the request payload for the OLS query API. type QueryRequest struct { - Query string `json:"query"` - Context string `json:"context,omitempty"` + Query string `json:"query"` } // QueryResponse is the response payload from the OLS query API. From af193be5f0d6f25a452e212e873f20626bd8f737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Valero=20Mart=C3=ADn?= Date: Wed, 9 Sep 2026 10:55:17 +0200 Subject: [PATCH 249/638] ROX-35964: add compatibility to telemetry (#22130) Co-authored-by: Claude Opus 4.6 (1M context) --- central/cluster/datastore/telemetry.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/central/cluster/datastore/telemetry.go b/central/cluster/datastore/telemetry.go index 6d8186d474456..bd636efd5f689 100644 --- a/central/cluster/datastore/telemetry.go +++ b/central/cluster/datastore/telemetry.go @@ -180,6 +180,8 @@ func UpdateSecuredClusterIdentity(ctx context.Context, clusterID string, metrics } props["Orchestrator Version"] = omd.GetVersion() + props["Sensor Version Compatibility"] = cluster.GetStatus().GetSensorVersionCompatibility().String() + if vmTraits := buildVMTraits(hasVMTelemetryCap, metrics.GetVirtualMachineMetrics()); vmTraits != nil { maps.Copy(props, vmTraits) } From 954c680f1967dbb3899ff53a1400527dbfffcce7 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:14:00 +0200 Subject: [PATCH 250/638] fix: honor namespace overrides related to scanner V4 (#22697) --- .../scanner-v4/indexer-config.yaml.tpl | 10 +++---- .../scanner-v4/matcher-config.yaml.tpl | 6 ++-- .../02-scanner-v4-00-serviceaccount.yaml | 2 +- .../templates/02-scanner-v4-01-psps.yaml | 4 +-- .../templates/02-scanner-v4-01-security.yaml | 6 ++-- .../02-scanner-v4-02-db-password-secret.yaml | 2 +- .../02-scanner-v4-03-db-tls-secret.yaml | 2 +- .../02-scanner-v4-03-indexer-tls-secret.yaml | 2 +- .../02-scanner-v4-03-matcher-tls-secret.yaml | 2 +- .../templates/02-scanner-v4-04-db-config.yaml | 2 +- .../02-scanner-v4-04-indexer-config.yaml | 2 +- .../02-scanner-v4-04-matcher-config.yaml | 2 +- .../02-scanner-v4-05-db-network-policy.yaml | 2 +- ...-scanner-v4-05-indexer-network-policy.yaml | 6 ++-- ...-scanner-v4-05-matcher-network-policy.yaml | 6 ++-- .../templates/02-scanner-v4-06-db-pvc.yaml | 4 +-- .../02-scanner-v4-07-db-deployment.yaml | 4 +-- .../02-scanner-v4-07-indexer-deployment.yaml | 4 +-- .../02-scanner-v4-07-matcher-deployment.yaml | 4 +-- .../02-scanner-v4-08-db-service.yaml | 2 +- .../02-scanner-v4-08-indexer-service.yaml | 2 +- .../02-scanner-v4-08-matcher-service.yaml | 2 +- .../02-scanner-v4-09-indexer-hpa.yaml | 2 +- .../02-scanner-v4-09-matcher-hpa.yaml | 2 +- .../99-scanner-v4-openshift-monitoring.yaml | 8 +++--- .../helm/shared/templates/_crypto.tpl | 2 +- .../templates/_scanner-v4_init.tpl.htpl | 10 +++---- .../internal/bootstrap-defaults.yaml.tpl | 2 ++ .../templates/sensor.yaml.htpl | 2 +- .../testdata/helmtest/upgrade.test.yaml | 28 +++++++++++++++++++ 30 files changed, 82 insertions(+), 52 deletions(-) diff --git a/image/templates/helm/shared/config-templates/scanner-v4/indexer-config.yaml.tpl b/image/templates/helm/shared/config-templates/scanner-v4/indexer-config.yaml.tpl index c5d28fc97879a..78b2f322eda80 100644 --- a/image/templates/helm/shared/config-templates/scanner-v4/indexer-config.yaml.tpl +++ b/image/templates/helm/shared/config-templates/scanner-v4/indexer-config.yaml.tpl @@ -11,7 +11,7 @@ indexer: enable: true database: conn_string: > - host=scanner-v4-db.{{ .Release.Namespace }}.svc + host=scanner-v4-db.{{ ._rox._namespace }}.svc port=5432 sslrootcert=/run/secrets/stackrox.io/certs/ca.pem user=postgres @@ -29,11 +29,11 @@ indexer: password_file: /run/secrets/stackrox.io/secrets/password get_layer_timeout: 1m {{- if ._rox.env.centralServices }} - repository_to_cpe_url: https://central.{{ .Release.Namespace }}.svc/api/extensions/scannerdefinitions?file=repo2cpe - name_to_repos_url: https://central.{{ .Release.Namespace }}.svc/api/extensions/scannerdefinitions?file=name2repos + repository_to_cpe_url: https://central.{{ ._rox._namespace }}.svc/api/extensions/scannerdefinitions?file=repo2cpe + name_to_repos_url: https://central.{{ ._rox._namespace }}.svc/api/extensions/scannerdefinitions?file=name2repos {{- else }} - repository_to_cpe_url: https://sensor.{{ .Release.Namespace }}.svc/scanner/definitions?file=repo2cpe - name_to_repos_url: https://sensor.{{ .Release.Namespace }}.svc/scanner/definitions?file=name2repos + repository_to_cpe_url: https://sensor.{{ ._rox._namespace }}.svc/scanner/definitions?file=repo2cpe + name_to_repos_url: https://sensor.{{ ._rox._namespace }}.svc/scanner/definitions?file=name2repos {{- end }} repository_to_cpe_file: /run/mappings/repository-to-cpe.json name_to_repos_file: /run/mappings/container-name-repos-map.json diff --git a/image/templates/helm/shared/config-templates/scanner-v4/matcher-config.yaml.tpl b/image/templates/helm/shared/config-templates/scanner-v4/matcher-config.yaml.tpl index 95b08b2c5275b..4f0545a43e912 100644 --- a/image/templates/helm/shared/config-templates/scanner-v4/matcher-config.yaml.tpl +++ b/image/templates/helm/shared/config-templates/scanner-v4/matcher-config.yaml.tpl @@ -13,7 +13,7 @@ matcher: enable: true database: conn_string: > - host=scanner-v4-db.{{ .Release.Namespace }}.svc + host=scanner-v4-db.{{ ._rox._namespace }}.svc port=5432 sslrootcert=/run/secrets/stackrox.io/certs/ca.pem user=postgres @@ -29,8 +29,8 @@ matcher: {{- end }} client_encoding=UTF8 password_file: /run/secrets/stackrox.io/secrets/password - vulnerabilities_url: https://central.{{ .Release.Namespace }}.svc/api/extensions/scannerdefinitions?version=ROX_VULNERABILITY_VERSION - indexer_addr: scanner-v4-indexer.{{ .Release.Namespace }}.svc:8443 + vulnerabilities_url: https://central.{{ ._rox._namespace }}.svc/api/extensions/scannerdefinitions?version=ROX_VULNERABILITY_VERSION + indexer_addr: scanner-v4-indexer.{{ ._rox._namespace }}.svc:8443 log_level: "{{ ._rox.scannerV4.matcher.logLevel }}" grpc_listen_addr: 0.0.0.0:8443 http_listen_addr: 0.0.0.0:9443 diff --git a/image/templates/helm/shared/templates/02-scanner-v4-00-serviceaccount.yaml b/image/templates/helm/shared/templates/02-scanner-v4-00-serviceaccount.yaml index 7dbce565b4133..101d7a9dbf226 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-00-serviceaccount.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-00-serviceaccount.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: scanner-v4 - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "serviceaccount" "scanner-v4") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-01-psps.yaml b/image/templates/helm/shared/templates/02-scanner-v4-01-psps.yaml index 14e38b4378299..a017ef6b8439d 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-01-psps.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-01-psps.yaml @@ -24,7 +24,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: stackrox-scanner-v4-psp - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "rolebinding" "stackrox-scanner-v4-psp") | nindent 4 }} annotations: @@ -36,7 +36,7 @@ roleRef: subjects: - kind: ServiceAccount name: scanner-v4 - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} --- diff --git a/image/templates/helm/shared/templates/02-scanner-v4-01-security.yaml b/image/templates/helm/shared/templates/02-scanner-v4-01-security.yaml index 79df5bb86ea4a..cded3003a2051 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-01-security.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-01-security.yaml @@ -9,7 +9,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: use-scanner-v4-scc - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "role" "use-scanner-v4-scc") | nindent 4 }} annotations: @@ -31,7 +31,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: scanner-v4-use-scc - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "rolebinding" "scanner-v4-use-scc") | nindent 4 }} annotations: @@ -43,6 +43,6 @@ roleRef: subjects: - kind: ServiceAccount name: scanner-v4 - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} {{- end }} {{- end }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-02-db-password-secret.yaml b/image/templates/helm/shared/templates/02-scanner-v4-02-db-password-secret.yaml index a382d3eabfd20..530ba8a4f8650 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-02-db-password-secret.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-02-db-password-secret.yaml @@ -5,7 +5,7 @@ apiVersion: v1 kind: Secret metadata: name: scanner-v4-db-password - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "secret" "scanner-v4-db-password") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-03-db-tls-secret.yaml b/image/templates/helm/shared/templates/02-scanner-v4-03-db-tls-secret.yaml index de9eacaccbbf2..f413d00d75a90 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-03-db-tls-secret.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-03-db-tls-secret.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Secret metadata: name: scanner-v4-db-tls - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: rhacs.redhat.com/tls: "true" {{- include "srox.labels" (list . "secret" "scanner-v4-db-tls") | nindent 4 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-03-indexer-tls-secret.yaml b/image/templates/helm/shared/templates/02-scanner-v4-03-indexer-tls-secret.yaml index 215ca93a85cdf..cdf8276c615a1 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-03-indexer-tls-secret.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-03-indexer-tls-secret.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Secret metadata: name: scanner-v4-indexer-tls - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: rhacs.redhat.com/tls: "true" {{- include "srox.labels" (list . "secret" "scanner-v4-indexer-tls") | nindent 4 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-03-matcher-tls-secret.yaml b/image/templates/helm/shared/templates/02-scanner-v4-03-matcher-tls-secret.yaml index 00ed57012ca03..f6909ccaab52c 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-03-matcher-tls-secret.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-03-matcher-tls-secret.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Secret metadata: name: scanner-v4-matcher-tls - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: rhacs.redhat.com/tls: "true" {{- include "srox.labels" (list . "secret" "scanner-v4-matcher-tls") | nindent 4 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-04-db-config.yaml b/image/templates/helm/shared/templates/02-scanner-v4-04-db-config.yaml index 439cfe826963a..79b062cc22a3f 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-04-db-config.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-04-db-config.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: scanner-v4-db-config - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "configmap" "scanner-v4-db-config") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-04-indexer-config.yaml b/image/templates/helm/shared/templates/02-scanner-v4-04-indexer-config.yaml index 26705b8ec57f6..3efd971e90f5f 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-04-indexer-config.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-04-indexer-config.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: scanner-v4-indexer-config - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "configmap" "scanner-v4-indexer-config") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-04-matcher-config.yaml b/image/templates/helm/shared/templates/02-scanner-v4-04-matcher-config.yaml index 67043678686b9..bd43e2caa2e6b 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-04-matcher-config.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-04-matcher-config.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: scanner-v4-matcher-config - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "configmap" "scanner-v4-matcher-config") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-05-db-network-policy.yaml b/image/templates/helm/shared/templates/02-scanner-v4-05-db-network-policy.yaml index 875aba871891a..b91e677bb4528 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-05-db-network-policy.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-05-db-network-policy.yaml @@ -5,7 +5,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-db - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-db") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-05-indexer-network-policy.yaml b/image/templates/helm/shared/templates/02-scanner-v4-05-indexer-network-policy.yaml index 460801a9a0aa2..6b11b7a2b34e4 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-05-indexer-network-policy.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-05-indexer-network-policy.yaml @@ -5,7 +5,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-indexer - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-indexer") | nindent 4 }} annotations: @@ -48,7 +48,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-indexer-monitoring - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-indexer-monitoring") | nindent 4 }} annotations: @@ -70,7 +70,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-indexer-monitoring-tls - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-indexer-monitoring-tls") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-05-matcher-network-policy.yaml b/image/templates/helm/shared/templates/02-scanner-v4-05-matcher-network-policy.yaml index fbc3525042b91..592a1662211e5 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-05-matcher-network-policy.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-05-matcher-network-policy.yaml @@ -5,7 +5,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-matcher - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-matcher") | nindent 4 }} annotations: @@ -32,7 +32,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-matcher-monitoring - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-matcher-monitoring") | nindent 4 }} annotations: @@ -54,7 +54,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: scanner-v4-matcher-monitoring-tls - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "networkpolicy" "scanner-v4-matcher-monitoring-tls") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-06-db-pvc.yaml b/image/templates/helm/shared/templates/02-scanner-v4-06-db-pvc.yaml index 8b276340430a4..59bf3cf7b70fd 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-06-db-pvc.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-06-db-pvc.yaml @@ -33,7 +33,7 @@ spec: accessModes: - ReadWriteOnce claimRef: - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} name: {{ $claimName }} {{- toYaml $pvcCfg.volume.volumeSpec | nindent 2 }} @@ -44,7 +44,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: {{ $claimName }} - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: # this is used for backwards compatibility for operator if there are PVCs without annotation # owned by the operator it will assume they belong to the deprecated central-db diff --git a/image/templates/helm/shared/templates/02-scanner-v4-07-db-deployment.yaml b/image/templates/helm/shared/templates/02-scanner-v4-07-db-deployment.yaml index f1284e334dc13..e66399b79590e 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-07-db-deployment.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-07-db-deployment.yaml @@ -4,7 +4,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: scanner-v4-db - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-db {{- include "srox.labels" (list . "deployment" "scanner-v4-db") | nindent 4 }} @@ -20,7 +20,7 @@ spec: type: Recreate template: metadata: - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-db {{- include "srox.podLabels" (list . "deployment" "scanner-v4-db") | nindent 8 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-07-indexer-deployment.yaml b/image/templates/helm/shared/templates/02-scanner-v4-07-indexer-deployment.yaml index 997c7bfbdead4..be7afd1b90a12 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-07-indexer-deployment.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-07-indexer-deployment.yaml @@ -4,7 +4,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: scanner-v4-indexer - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-indexer {{- include "srox.labels" (list . "deployment" "scanner-v4-indexer") | nindent 4 }} @@ -22,7 +22,7 @@ spec: type: RollingUpdate template: metadata: - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-indexer {{- include "srox.podLabels" (list . "deployment" "scanner-v4-indexer") | nindent 8 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-07-matcher-deployment.yaml b/image/templates/helm/shared/templates/02-scanner-v4-07-matcher-deployment.yaml index e97876b182606..9e0685786f949 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-07-matcher-deployment.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-07-matcher-deployment.yaml @@ -4,7 +4,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: scanner-v4-matcher - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-matcher {{- include "srox.labels" (list . "deployment" "scanner-v4-matcher") | nindent 4 }} @@ -22,7 +22,7 @@ spec: type: RollingUpdate template: metadata: - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: app: scanner-v4-matcher {{- include "srox.podLabels" (list . "deployment" "scanner-v4-matcher") | nindent 8 }} diff --git a/image/templates/helm/shared/templates/02-scanner-v4-08-db-service.yaml b/image/templates/helm/shared/templates/02-scanner-v4-08-db-service.yaml index 58a663f3108a3..39ea7e2b29287 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-08-db-service.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-08-db-service.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Service metadata: name: scanner-v4-db - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "service" "scanner-v4-db") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-08-indexer-service.yaml b/image/templates/helm/shared/templates/02-scanner-v4-08-indexer-service.yaml index 72cbbeb7eb889..0d2a9200e84d5 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-08-indexer-service.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-08-indexer-service.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Service metadata: name: scanner-v4-indexer - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "service" "scanner-v4-indexer") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-08-matcher-service.yaml b/image/templates/helm/shared/templates/02-scanner-v4-08-matcher-service.yaml index 7ce2b8f1d804d..f7a7eb28b5360 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-08-matcher-service.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-08-matcher-service.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Service metadata: name: scanner-v4-matcher - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "service" "scanner-v4-matcher") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-09-indexer-hpa.yaml b/image/templates/helm/shared/templates/02-scanner-v4-09-indexer-hpa.yaml index 91e48325066e7..5658fba13df6c 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-09-indexer-hpa.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-09-indexer-hpa.yaml @@ -4,7 +4,7 @@ apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: scanner-v4-indexer - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "horizontalpodautoscaler" "scanner-v4-indexer") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/02-scanner-v4-09-matcher-hpa.yaml b/image/templates/helm/shared/templates/02-scanner-v4-09-matcher-hpa.yaml index d90bfcbd281b5..7b585ca8e454c 100644 --- a/image/templates/helm/shared/templates/02-scanner-v4-09-matcher-hpa.yaml +++ b/image/templates/helm/shared/templates/02-scanner-v4-09-matcher-hpa.yaml @@ -4,7 +4,7 @@ apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: scanner-v4-matcher - namespace: {{ .Release.Namespace }} + namespace: {{ quote ._rox._namespace }} labels: {{- include "srox.labels" (list . "horizontalpodautoscaler" "scanner-v4-matcher") | nindent 4 }} annotations: diff --git a/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml b/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml index 20359701bab7b..e45dbdb4eb0cd 100644 --- a/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml +++ b/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml @@ -22,7 +22,7 @@ spec: caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt certFile: /etc/prometheus/secrets/metrics-client-certs/tls.crt keyFile: /etc/prometheus/secrets/metrics-client-certs/tls.key - serverName: "scanner-v4-indexer.{{ .Release.Namespace }}.svc" + serverName: "scanner-v4-indexer.{{ ._rox._namespace }}.svc" {{- end }} {{- if ._rox.scannerV4._matcherEnabled }} - interval: 30s @@ -33,14 +33,14 @@ spec: caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt certFile: /etc/prometheus/secrets/metrics-client-certs/tls.crt keyFile: /etc/prometheus/secrets/metrics-client-certs/tls.key - serverName: "scanner-v4-matcher.{{ .Release.Namespace }}.svc" + serverName: "scanner-v4-matcher.{{ ._rox._namespace }}.svc" {{- end }} selector: matchLabels: app.kubernetes.io/component: scanner-v4 namespaceSelector: matchNames: - - "{{ .Release.Namespace }}" + - "{{ ._rox._namespace }}" --- {{/* TODO(ROX-22188): Switch to a ClusterRoleBinding to avoid leaking to kube-system namespace. */ -}} @@ -60,6 +60,6 @@ roleRef: subjects: - kind: ServiceAccount name: scanner-v4 - namespace: "{{ .Release.Namespace }}" + namespace: "{{ ._rox._namespace }}" {{- end }} {{- end }} diff --git a/image/templates/helm/shared/templates/_crypto.tpl b/image/templates/helm/shared/templates/_crypto.tpl index 4ac1face96d6c..28afe0e32dcb5 100644 --- a/image/templates/helm/shared/templates/_crypto.tpl +++ b/image/templates/helm/shared/templates/_crypto.tpl @@ -235,7 +235,7 @@ {{ $ := index . 0 }} {{ $out := index . 1 }} {{ $svcName := index . 2 }} -{{ $releaseNS := $.Release.Namespace }} +{{ $releaseNS := $._rox._namespace }} {{ $sans := list }} {{ range $ns := list $releaseNS "stackrox" | uniq | sortAlpha }} {{ $baseDNS := printf "%s.%s" $svcName $ns }} diff --git a/image/templates/helm/shared/templates/_scanner-v4_init.tpl.htpl b/image/templates/helm/shared/templates/_scanner-v4_init.tpl.htpl index 323bfa442fe7f..f529c2a927b60 100644 --- a/image/templates/helm/shared/templates/_scanner-v4_init.tpl.htpl +++ b/image/templates/helm/shared/templates/_scanner-v4_init.tpl.htpl @@ -67,7 +67,7 @@ {{/* And in case scanner V4 was not deployed earlier, we need to make sure that these resources are correctly initialized. */}} {{- if $.Release.IsUpgrade -}} {{- $lookupOut := dict -}} - {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $.Release.Namespace "scanner-v4-indexer-tls") -}} + {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $._rox._namespace "scanner-v4-indexer-tls") -}} {{- if not $lookupOut.result -}} {{/* If generate=null and the resource does not exist yet, attempt to create it. */}} {{/* If lookup is not possible (e.g. in the operator), then 'generate' needs to be set correctly. */}} @@ -87,7 +87,7 @@ {{/* And in case scanner V4 was not deployed earlier, we need to make sure that these resources are correctly initialized. */}} {{- if $.Release.IsUpgrade -}} {{- $lookupOut := dict -}} - {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $.Release.Namespace "scanner-v4-matcher-tls") -}} + {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $._rox._namespace "scanner-v4-matcher-tls") -}} {{- if not $lookupOut.result -}} {{/* If generate=null and the resource does not exist yet, attempt to create it. */}} {{/* If lookup is not possible (e.g. in the operator), then 'generate' needs to be set correctly. */}} @@ -105,7 +105,7 @@ {{/* And in case scanner V4 was not deployed earlier, we need to make sure that these resources are correctly initialized. */}} {{- if $.Release.IsUpgrade -}} {{- $lookupOut := dict -}} - {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $.Release.Namespace "scanner-v4-db-tls") -}} + {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $._rox._namespace "scanner-v4-db-tls") -}} {{- if not $lookupOut.result -}} {{/* If generate=null and the resource does not exist yet, attempt to create it. */}} {{/* If lookup is not possible (e.g. in the operator), then 'generate' needs to be set correctly. */}} @@ -117,7 +117,7 @@ {{/* We need special handling here, because 'generate' will default to 'false' on upgrades. */}} {{/* And in case scanner V4 was not deployed earlier, we need to make sure that these resources are correctly initialized. */}} {{- $lookupOut := dict -}} - {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $.Release.Namespace "scanner-v4-db-password") -}} + {{- $_ := include "srox.safeLookup" (list $ $lookupOut "v1" "Secret" $._rox._namespace "scanner-v4-db-password") -}} {{- if not $lookupOut.result -}} {{/* If generate=null and the resource does not exist yet, attempt to create it. */}} {{/* If lookup is not possible (e.g. in the operator), then 'generate' needs to be set correctly. */}} @@ -139,7 +139,7 @@ {{/* Special handling for the secured-cluster-services chart in case it gets deployed to the same namespace as central-services. */}} {{ $centralDeployment := dict }} - {{ include "srox.safeLookup" (list $ $centralDeployment "apps/v1" "Deployment" $.Release.Namespace "central") }} + {{ include "srox.safeLookup" (list $ $centralDeployment "apps/v1" "Deployment" $._rox._namespace "central") }} {{ if $centralDeployment.result }} {{ include "srox.note" (list $ "Detected central running in the same namespace. Not deploying scanner-v4-indexer from this chart and configuring sensor to use existing scanner-v4-indexer instance, if any.") }} {{ $_ := set $components "indexer" false }} diff --git a/image/templates/helm/stackrox-central/internal/bootstrap-defaults.yaml.tpl b/image/templates/helm/stackrox-central/internal/bootstrap-defaults.yaml.tpl index 8f8e5590fc3a6..2c9a9ab40d7b4 100644 --- a/image/templates/helm/stackrox-central/internal/bootstrap-defaults.yaml.tpl +++ b/image/templates/helm/stackrox-central/internal/bootstrap-defaults.yaml.tpl @@ -11,6 +11,8 @@ allowNonstandardNamespace: false allowNonstandardReleaseName: false {{- end }} +_namespace: {{ quote .Release.Namespace }} + meta: useLookup: true fileOverrides: {} diff --git a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl index b09d522a51afd..fb01669747cb3 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl @@ -242,7 +242,7 @@ spec: value: {{ quote ._rox._configFP }} {{- if ._rox._scannerV4Enabled }} - name: ROX_SCANNER_V4_INDEXER_ENDPOINT - value: {{ printf "scanner-v4-indexer.%s.svc:8443" .Release.Namespace }} + value: {{ printf "scanner-v4-indexer.%s.svc:8443" ._rox._namespace }} - name: ROX_SCANNER_V4 value: "true" {{- end }} diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/upgrade.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/upgrade.test.yaml index d6a979c0f2fc4..c3fa7f4751718 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/upgrade.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/upgrade.test.yaml @@ -1,4 +1,6 @@ values: + scannerV4: + disable: false imagePullSecrets: allowNone: true release: @@ -22,3 +24,29 @@ tests: meta.namespaceOverride: my-stackrox expect: | verifyNamespace("my-stackrox") +- name: "should honor the namespace override for scanner V4 indexer endpoint" + release: + namespace: default + set: + meta.namespaceOverride: my-stackrox + expect: | + envVars(.deployments.sensor; "sensor")["ROX_SCANNER_V4_INDEXER_ENDPOINT"] + | assertThat(. == "scanner-v4-indexer.my-stackrox.svc:8443") +- name: "should honor the namespace override for scanner V4 monitoring" + server: + availableSchemas: + - openshift-4.12 + - com.coreos + release: + namespace: default + set: + meta.namespaceOverride: my-stackrox + env.openshift: 4 + monitoring.openshift.enabled: true + expect: | + .servicemonitors["scanner-v4-monitor-default"] + | assertThat(.spec.namespaceSelector.matchNames[0] == "my-stackrox") + .servicemonitors["scanner-v4-monitor-default"] + | .spec.endpoints[] | .tlsConfig.serverName | assertThat(endswith(".my-stackrox.svc")) + .rolebindings["rhacs-scanner-v4-auth-reader-default"] + | .subjects[] | select(.name == "scanner-v4") | assertThat(.namespace == "my-stackrox") From efb4d8203d686fed49b93ee3cb503eeadf602469 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 9 Sep 2026 12:43:39 +0200 Subject: [PATCH 251/638] chore: Update ROXIE_VERSION to 0.4.11 (#22691) --- ROXIE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROXIE_VERSION b/ROXIE_VERSION index 76914ddc02f84..5f749c13663ae 100644 --- a/ROXIE_VERSION +++ b/ROXIE_VERSION @@ -1 +1 @@ -0.4.9 +0.4.11 From 253cf67aa05619b44ad9ba9b199b1e4c00898ad3 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 9 Sep 2026 13:59:23 +0200 Subject: [PATCH 252/638] ROX-35773: Bump wait time (#22699) --- tests/policy_as_code_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/policy_as_code_test.go b/tests/policy_as_code_test.go index 9a77111bb791d..6f55908167678 100644 --- a/tests/policy_as_code_test.go +++ b/tests/policy_as_code_test.go @@ -468,7 +468,7 @@ func (pc *PolicyAsCodeSuite) createCRAndObserveInCentral(policyCR *v1alpha1.Secu } } assert.NotEmpty(collect, policyId) - }, time.Second*5, time.Millisecond*30) + }, time.Second*30, time.Millisecond*30) return policyId } From 3e49eceb2b3d0e17664b2b0e9b4988ab1214964a Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:07:53 +0200 Subject: [PATCH 253/638] ROX-33973: Create/plug OIDC AuthProvider with role resolution from ACM (#22346) --- central/main.go | 4 ++ .../oidc/backend_factory_impl.go | 4 ++ pkg/auth/authproviders/registry_impl.go | 49 ++++++++++++++++--- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/central/main.go b/central/main.go index 6b375b4956f24..11bf834c78731 100755 --- a/central/main.go +++ b/central/main.go @@ -590,6 +590,10 @@ func startGRPCServer() { } } + if features.ACMAccessControlDelegation.Enabled() { + authProviderBackendFactories[oidc.TypeNameWithACMAccessControlDelegation] = oidc.NewFactory + } + for typeName, factoryCreator := range authProviderBackendFactories { if err := registry.RegisterBackendFactory(authProviderRegisteringCtx, typeName, factoryCreator); err != nil { log.Panicf("Could not register %s auth provider factory: %v", typeName, err) diff --git a/pkg/auth/authproviders/oidc/backend_factory_impl.go b/pkg/auth/authproviders/oidc/backend_factory_impl.go index 2e745e545063a..e3f16b348f5d1 100644 --- a/pkg/auth/authproviders/oidc/backend_factory_impl.go +++ b/pkg/auth/authproviders/oidc/backend_factory_impl.go @@ -20,6 +20,10 @@ const ( // TypeName is the standard type name for OIDC auth providers. TypeName = "oidc" + // TypeNameWithACMAccessControlDelegation is the type name for OIDC auth providers + // with role resolution from ACM. + TypeNameWithACMAccessControlDelegation = authproviders.OIDCTypeNameWithACMRoles + callbackRelativePath = "callback" nonceTTL = 1 * time.Minute diff --git a/pkg/auth/authproviders/registry_impl.go b/pkg/auth/authproviders/registry_impl.go index 2e01d81d1a329..c09c10178af9d 100644 --- a/pkg/auth/authproviders/registry_impl.go +++ b/pkg/auth/authproviders/registry_impl.go @@ -24,6 +24,7 @@ import ( const ( OpenShiftTypeNameWithACMAccessControlDelegation = "openshift-with-acm-roles" + OIDCTypeNameWithACMRoles = "oidc-with-acm-roles" ) var ( @@ -381,6 +382,12 @@ func (r *registryImpl) issueTokenForResponse(ctx context.Context, provider Provi return nil, nil, err } roxClaims.InternalRoles = roles + case OIDCTypeNameWithACMRoles: + roles, err := getRolesForOIDCResponse(ctx, authResp, r.clusterResolver) + if err != nil { + return nil, nil, err + } + roxClaims.InternalRoles = roles default: roxClaims.ExternalUser = authResp.Claims } @@ -453,13 +460,45 @@ func getRolesForOpenshiftResponse( if err != nil { return nil, errox.InvalidArgs.CausedByf("failed to parse token: %v", err) } + acmClientObj, err := getACMClientForToken(tokenData.AccessToken) + if err != nil { + return nil, errors.Wrap(err, "failed to get ACM client") + } + roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch role information") + } + return roles, nil +} + +func getRolesForOIDCResponse( + ctx context.Context, + authResp *AuthResponse, + clusterIDResolver tokens.ClusterResolver, +) ([]*tokens.InternalRole, error) { + if authResp == nil { + return nil, errox.InvalidArgs.CausedBy("auth response must not be nil") + } + acmClientObj, err := getACMClientForToken(authResp.IdpToken) + if err != nil { + return nil, errors.Wrap(err, "failed to get ACM client") + } + roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch role information") + } + return roles, nil + +} + +func getACMClientForToken(token string) (*acm.Client, error) { // Retrieve OpenShift cluster config cfg, err := rest.InClusterConfig() if err != nil { return nil, errors.Wrap(err, "failed to get cluster config") } // Enrich the config with the OpenShift Auth Token - cfg.BearerToken = tokenData.AccessToken + cfg.BearerToken = token // Clear BearerTokenFile so that BearerToken takes precedence. // InClusterConfig() sets BearerTokenFile to the service account token, // which would override the user's OAuth token we just set. @@ -468,17 +507,15 @@ func getRolesForOpenshiftResponse( if err != nil { return nil, errors.Wrap(err, "failed to instantiate ACM client") } - roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) - if err != nil { - return nil, errors.Wrap(err, "failed to fetch role information") - } - return roles, nil + return acmClientObj, nil } func isACMRoleDelegationProvider(provider Provider) bool { switch provider.Type() { case OpenShiftTypeNameWithACMAccessControlDelegation: return true + case OIDCTypeNameWithACMRoles: + return true } return false } From 418043feecc4e0e7783d093959ba44fdbc8ea752 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:45:22 +0200 Subject: [PATCH 254/638] fix(sensor): preserve VM scrape NACK against late commit (#22480) --- sensor/common/virtualmachine/ACK_MODEL.md | 2 +- .../virtualmachine/vmscraper/scraper.go | 42 ++++--- .../virtualmachine/vmscraper/scraper_test.go | 106 ++++++++++-------- 3 files changed, 81 insertions(+), 69 deletions(-) diff --git a/sensor/common/virtualmachine/ACK_MODEL.md b/sensor/common/virtualmachine/ACK_MODEL.md index 11b71adc01bed..40256ab764153 100644 --- a/sensor/common/virtualmachine/ACK_MODEL.md +++ b/sensor/common/virtualmachine/ACK_MODEL.md @@ -41,4 +41,4 @@ Using only the vsock CID is unsafe because a CID can be reused by a different VM The pair is not a per-report unique ID: multiple in-flight reports from the same VM with the same CID are still not fully distinguishable, so a stale ACK can match the latest entry instead of the one it was actually for. -A NACK can also lose a race with the post-forward commit of `lastToken`, in which case the next poll may still see an "unchanged" delta. +A NACK that changes `lastToken` or backoff before `commitVMState` runs is not overwritten by that commit. If `lastToken` is already empty and backoff is already at the cap, `nextBackoff` is a no-op, so that NACK is invisible to the commit. diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index d164ee3935ac4..04797f0149d4e 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -231,11 +231,8 @@ func (s *VMScraper) ProcessMessage(_ context.Context, msg *central.MsgToSensor) return nil } -// handleNACK clears the cached token and applies the shared backoff so -// the next tick resends a full report without tight-looping on persistent NACKs. -// -// Race with commitVMState after forward is accepted: a fast NACK may be -// overwritten by a late success commit. +// handleNACK clears lastToken and applies retry backoff so the next tick +// resends a full report without tight-looping on persistent NACKs. func (s *VMScraper) handleNACK(resourceID string) { key := s.findKeyByVMID(vmIDFromResourceID(resourceID)) if key == "" { @@ -557,9 +554,8 @@ func (s *VMScraper) scrapeVM(ctx context.Context, vm *virtualmachine.Info) bool } newToken := result.Meta.GetReportToken() - s.commitVMState(key, vm.ID, newToken, result.Meta.GetAgentVersion()) + next := s.commitVMState(key, vm.ID, snap.lastToken, snap.backoff, newToken, result.Meta.GetAgentVersion()) s.observeForwardInterarrival() - next := s.scheduleAfterAttempt(key, vm.ID, scrapeOK) log.Infof("VMScraper: scrape %q ok outcome=forwarded next=%s", key, next) totalElapsed := s.now().Sub(totalStart) @@ -860,26 +856,28 @@ func (s *VMScraper) snapshotVMState(key string) vmStateSnapshot { }) } -// commitVMState records the token from a just-sent report as key's cached scrape state. -// -// Race: If a NACK arrives from Central faster than this function completes, for example, due to: -// - a scheduling delay on the caller's goroutine between forward returning and this function -// acquiring `s.mu` (a GC pause, CPU throttling, or GOMAXPROCS contention), -// - contention on `s.mu` itself, from other concurrent scrapes or NACKs delaying this -// function's lock acquisition, -// -// then the NACK will overwrite lastToken / backoff / nextAttemptAt, and then this code -// will set the token (and a later scheduleAfterAttempt(scrapeOK) will reset backoff schedule). -// This race is accepted for v1. -func (s *VMScraper) commitVMState(key string, vmID virtualmachine.VMID, newToken string, agentVersion string) { - concurrency.WithLock(&s.mu, func() { +// commitVMState records the just-sent report's token and agent version and +// applies poll-cadence scheduling in the same lock, so a concurrent NACK +// that changed lastToken or backoff is not overwritten. +func (s *VMScraper) commitVMState(key string, vmID virtualmachine.VMID, expectedToken string, expectedBackoff time.Duration, newToken string, agentVersion string) time.Duration { + return concurrency.WithLock1(&s.mu, func() time.Duration { state, ok := s.vmState[key] if !ok || state.vmID != vmID { - return + return 0 + } + if state.lastToken != expectedToken || state.backoff != expectedBackoff { + return max(0, state.nextAttemptAt.Sub(s.now())) } + now := s.now() state.lastToken = newToken - state.lastForwardedAt = s.now() + state.lastForwardedAt = now state.lastAgentVersion = agentVersion + state.backoff = 0 + offset := randOffset(steadySpreadWidth(s.interval, s.spreadFraction), s.randFloat64()) + metrics.PullScheduleOffsetSeconds.Observe(offset.Seconds()) + delay := s.interval + offset + state.nextAttemptAt = now.Add(delay) + return delay }) } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 75aecaf3a0769..7ac9687de85ce 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -848,52 +848,66 @@ func TestVMScraper_NACK(t *testing.T) { } } -// TestVMScraper_InFlightSendCanOverwriteNACKReset exercises a known race involving -// an unusually slow execution of `commitVMState` and a quick NACK from Central. -func TestVMScraper_InFlightSendCanOverwriteNACKReset(t *testing.T) { - synctest.Test(t, func(t *testing.T) { - vmA := makeVM("ns1", "vm-a", 100) - vmA.ID = "vm-a-id" - store := &mockStore{vms: []*virtualmachine.Info{vmA}} - client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}} - s, clock := newTestScraper(t, store, &mockDialer{}, client) - - s.pollOnce(t.Context()) - require.Equal(t, "1", cachedToken(t, s, "ns1/vm-a")) - require.Equal(t, 1, forwardedCount(s)) - - s.toCentral = make(chan *message.ExpiringMessage) - client.reset() - client.resultQueue = []*vsockclient.GetReportResult{makeReport("2")} - clock.Advance(s.interval) - - done := make(chan struct{}) - go func() { - defer close(done) - s.pollOnce(t.Context()) - }() - - // Block until the scrape goroutine is durably blocked in forwardReport, - // i.e. the report is on toCentral but not yet committed. - synctest.Wait() - - require.NoError(t, s.ProcessMessage(t.Context(), ¢ral.MsgToSensor{ - Msg: ¢ral.MsgToSensor_SensorAck{ - SensorAck: ¢ral.SensorACK{ - MessageType: central.SensorACK_VM_INDEX_REPORT, - Action: central.SensorACK_NACK, - ResourceId: "vm-a-id:100", - }, - }, - })) - assert.Empty(t, cachedToken(t, s, "ns1/vm-a"), - "NACK applies immediately while the send it targets is still in flight") - - <-s.toCentral - <-done - assert.Equal(t, "2", cachedToken(t, s, "ns1/vm-a"), - "the in-flight send's commit runs after the NACK reset and overwrites it unconditionally") - }) +// TestVMScraper_InFlightSendDoesNotOverwriteNACKReset covers NACK while +// forwardReport is blocked: commit must not restore lastToken or clear NACK backoff. +func TestVMScraper_InFlightSendDoesNotOverwriteNACKReset(t *testing.T) { + cases := map[string]struct { + priorPoll bool + sendToken string + }{ + "after a prior successful commit": {priorPoll: true, sendToken: "2"}, + "when lastToken is already empty": {priorPoll: false, sendToken: "1"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + vmA := makeVM("ns1", "vm-a", 100) + vmA.ID = "vm-a-id" + store := &mockStore{vms: []*virtualmachine.Info{vmA}} + client := &mockProtocolClient{resultQueue: []*vsockclient.GetReportResult{makeReport("1")}} + s, clock := newTestScraper(t, store, &mockDialer{}, client) + + if tc.priorPoll { + s.pollOnce(t.Context()) + require.Equal(t, "1", cachedToken(t, s, "ns1/vm-a")) + clock.Advance(s.interval) + } + + s.toCentral = make(chan *message.ExpiringMessage) + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{makeReport(tc.sendToken)} + + done := make(chan struct{}) + go func() { + defer close(done) + s.pollOnce(t.Context()) + }() + + // Block until the scrape goroutine is durably blocked in forwardReport, + // i.e. the report is on toCentral but not yet committed. + synctest.Wait() + + require.NoError(t, s.ProcessMessage(t.Context(), ¢ral.MsgToSensor{ + Msg: ¢ral.MsgToSensor_SensorAck{ + SensorAck: ¢ral.SensorACK{ + MessageType: central.SensorACK_VM_INDEX_REPORT, + Action: central.SensorACK_NACK, + ResourceId: "vm-a-id:100", + }, + }, + })) + assert.Empty(t, cachedToken(t, s, "ns1/vm-a"), + "NACK applies immediately while the send it targets is still in flight") + + <-s.toCentral + <-done + assert.Empty(t, cachedToken(t, s, "ns1/vm-a"), + "the in-flight send must not restore lastToken after a NACK") + assert.Equal(t, initialBackoff, cachedBackoff(t, s, "ns1/vm-a"), + "success scheduling must not clear the NACK backoff") + }) + }) + } } func TestVMScraper_HandlesDialAndProtocolFailures(t *testing.T) { From 30b3948087ca440bca41c6eb0fd433ed596091aa Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 9 Sep 2026 16:16:38 +0200 Subject: [PATCH 255/638] chore(CI): Fix CRD apply wait method (#22739) --- tests/complianceoperator/create.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/complianceoperator/create.sh b/tests/complianceoperator/create.sh index 4a7491d827d7c..969273e109817 100755 --- a/tests/complianceoperator/create.sh +++ b/tests/complianceoperator/create.sh @@ -15,5 +15,22 @@ if ! kubectl get crd compliancecheckresults.compliance.openshift.io; then while IFS='' read -r single_crd; do all_crds+=("$single_crd"); done < <(grep -rh '^ name:.*\.compliance\.openshift\.io' "${DIR}/crds/" | awk '{print "crd/" $2}') kubectl wait --for condition=established --timeout=60s "${all_crds[@]}" + # `kubectl wait --for condition=established` can fail immediately + # (not time out) when a just-applied CRD has not had its + # .status.conditions populated by the API server yet. + established=false + for _ in $(seq 1 5); do + if kubectl wait --for condition=established --timeout=60s "${all_crds[@]}"; then + established=true + break + fi + echo "kubectl wait for established compliance CRDs failed (status may not be populated yet); retrying in 5s..." + sleep 5 + done + if [ "${established}" != "true" ]; then + echo "ERROR: compliance CRDs did not become established in time" >&2 + exit 1 + fi + kubectl apply -R -f "${DIR}/resources" fi From 66e5ef5151780d25ff3912de1a2f8145169872c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:17:16 +0200 Subject: [PATCH 256/638] chore(deps): bump stackrox/actions/infra/install-infractl from 1.0.39 to 1.0.44 (#22362) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-qa-tests.yaml | 2 +- .github/workflows/performance-tests.yml | 2 +- .github/workflows/upgrade-test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index 6ba54bab9cead..56b47f044edf6 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -200,7 +200,7 @@ jobs: | tar xz -C /usr/local/bin oc oc version --client - - uses: stackrox/actions/infra/install-infractl@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/install-infractl@v1 + - uses: stackrox/actions/infra/install-infractl@3b27674f0fa69f2d8c31ab26aa63c9f76401c64e # ratchet:stackrox/actions/infra/install-infractl@v1 if: inputs.cluster-provisioning-method == 'infra' - name: Wait for infra cluster and get kubeconfig diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml index 3cc84c9082110..c1561db61871e 100644 --- a/.github/workflows/performance-tests.yml +++ b/.github/workflows/performance-tests.yml @@ -69,7 +69,7 @@ jobs: wait: true # Fetch infractl artifacts - - uses: stackrox/actions/infra/install-infractl@4efb76aa74694b9ee55e95c4ead57fdf50d79ddf # ratchet:stackrox/actions/infra/install-infractl@v1 + - uses: stackrox/actions/infra/install-infractl@3b27674f0fa69f2d8c31ab26aa63c9f76401c64e # ratchet:stackrox/actions/infra/install-infractl@v1 - uses: 'google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093' # ratchet:google-github-actions/auth@v3 with: credentials_json: "${{ secrets.GCP_PERFORMANCE_TESTER_CI_SA }}" diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index 0b710b0c9959a..78ddcbeb0c98d 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -89,7 +89,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: ref: ${{ github.ref_name }} - - uses: stackrox/actions/infra/install-infractl@4efb76aa74694b9ee55e95c4ead57fdf50d79ddf # ratchet:stackrox/actions/infra/install-infractl@v1 + - uses: stackrox/actions/infra/install-infractl@3b27674f0fa69f2d8c31ab26aa63c9f76401c64e # ratchet:stackrox/actions/infra/install-infractl@v1 - uses: 'google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093' # ratchet:google-github-actions/auth@v3 with: credentials_json: "${{ secrets.GCP_RELEASE_AUTOMATION_SA }}" From 3a7e8b111d22a86bb68858165476a87583b8c541 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:27:44 +0200 Subject: [PATCH 257/638] ROX-36885: Show unmatched VM packages as not scanned (#22737) --- .../convert/v1tov2storage/vm_scan_parts.go | 17 +++++++ .../v1tov2storage/vm_scan_parts_test.go | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/central/convert/v1tov2storage/vm_scan_parts.go b/central/convert/v1tov2storage/vm_scan_parts.go index aa9710f7964d6..9f83ccb53dcce 100644 --- a/central/convert/v1tov2storage/vm_scan_parts.go +++ b/central/convert/v1tov2storage/vm_scan_parts.go @@ -54,6 +54,7 @@ func ScanPartsFromV1Scan(vmID string, scan *storage.VirtualMachineScan) *common. }, FixedBy: highestFixedBy(comp.GetVulnerabilities()), CveCount: compCveCount, + Notes: convertComponentNotes(comp.GetNotes()), }) } @@ -147,6 +148,22 @@ func convertCVEBaseInfo(info *storage.VirtualMachineCVEInfo) *storage.CVEInfo { } } +func convertComponentNotes(notes []storage.EmbeddedVirtualMachineScanComponent_Note) []storage.VirtualMachineComponentV2_Note { + if len(notes) == 0 { + return nil + } + out := make([]storage.VirtualMachineComponentV2_Note, 0, len(notes)) + for _, n := range notes { + switch n { + case storage.EmbeddedVirtualMachineScanComponent_UNSCANNED: + out = append(out, storage.VirtualMachineComponentV2_UNSCANNED) + default: + out = append(out, storage.VirtualMachineComponentV2_UNSPECIFIED) + } + } + return out +} + func convertScanNotes(notes []storage.VirtualMachineScan_Note) []storage.VirtualMachineScanV2_Note { if len(notes) == 0 { return nil diff --git a/central/convert/v1tov2storage/vm_scan_parts_test.go b/central/convert/v1tov2storage/vm_scan_parts_test.go index 57d09ba60d726..7810642a9e9ee 100644 --- a/central/convert/v1tov2storage/vm_scan_parts_test.go +++ b/central/convert/v1tov2storage/vm_scan_parts_test.go @@ -3,6 +3,8 @@ package v1tov2storage import ( "testing" + "github.com/stackrox/rox/central/convert/storagetov2" + v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -116,6 +118,50 @@ func TestScanPartsFromV1Scan_WithComponentsAndVulns(t *testing.T) { assert.Equal(t, "openssl", result.SourceComponents[0].GetName()) } +func TestScanPartsFromV1Scan_PreservesComponentNotes(t *testing.T) { + tests := map[string]struct { + notes []storage.EmbeddedVirtualMachineScanComponent_Note + wantNotes []storage.VirtualMachineComponentV2_Note + wantScanStatus v2.ScanStatus + }{ + "should map UNSCANNED notes to NOT_SCANNED on the V2 row": { + notes: []storage.EmbeddedVirtualMachineScanComponent_Note{ + storage.EmbeddedVirtualMachineScanComponent_UNSCANNED, + }, + wantNotes: []storage.VirtualMachineComponentV2_Note{ + storage.VirtualMachineComponentV2_UNSCANNED, + }, + wantScanStatus: v2.ScanStatus_NOT_SCANNED, + }, + "should treat a component with no notes as scanned": { + wantScanStatus: v2.ScanStatus_SCANNED, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + result := ScanPartsFromV1Scan("vm-1", &storage.VirtualMachineScan{ + OperatingSystem: "rhel:9", + Components: []*storage.EmbeddedVirtualMachineScanComponent{ + { + Name: "curl", + Version: "7.0", + Source: storage.SourceType_OS, + Notes: tt.notes, + }, + }, + }) + require.NotNil(t, result) + require.Len(t, result.Components, 1) + assert.Equal(t, tt.wantNotes, result.Components[0].GetNotes()) + + row := storagetov2.VirtualMachineComponentV2ToRow(result.Components[0]) + require.NotNil(t, row) + assert.Equal(t, tt.wantScanStatus, row.GetScanStatus()) + }) + } +} + func TestCompareVersionSegments(t *testing.T) { tests := []struct { name string From 9ed44d4319a2988495a4c954b4991fc33bf1b8fe Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:33:26 -0400 Subject: [PATCH 258/638] ROX-36265: Add ROX_CENTRAL_WORKER_ENABLED to deploy scripts (#22539) Co-authored-by: Claude Opus 4.6 (1M context) --- deploy/common/env.sh | 7 +++++++ deploy/common/k8sbased.sh | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/deploy/common/env.sh b/deploy/common/env.sh index e4531d5698095..4c0c1219aa163 100644 --- a/deploy/common/env.sh +++ b/deploy/common/env.sh @@ -88,6 +88,13 @@ echo "ROX_DECLARATIVE_CONFIGURATION is set to ${ROX_DECLARATIVE_CONFIGURATION}" export ROX_MANAGED_CENTRAL="${ROX_MANAGED_CENTRAL:-false}" echo "ROX_MANAGED_CENTRAL is set to ${ROX_MANAGED_CENTRAL}" +export ROX_CENTRAL_WORKER_ENABLED="${ROX_CENTRAL_WORKER_ENABLED:-false}" +echo "ROX_CENTRAL_WORKER_ENABLED is set to ${ROX_CENTRAL_WORKER_ENABLED}" +if [[ "${ROX_CENTRAL_WORKER_ENABLED}" == "true" && "${OUTPUT_FORMAT}" != "helm" ]]; then + echo "ROX_CENTRAL_WORKER_ENABLED requires OUTPUT_FORMAT=helm, overriding" + export OUTPUT_FORMAT="helm" +fi + export SENSOR_DEV_RESOURCES="${SENSOR_DEV_RESOURCES:-true}" echo "SENSOR_DEV_RESOURCES is set to ${SENSOR_DEV_RESOURCES}" diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index fd25f055ce57b..172cdb1bc1f5c 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -463,6 +463,12 @@ function launch_central { fi fi + if [[ "${ROX_CENTRAL_WORKER_ENABLED:-}" == "true" ]]; then + helm_args+=( + --set centralWorker.enabled=true + ) + fi + if [[ -n "$EXTERNAL_DB" ]]; then helm_args+=( --set "central.db.password.value=${EXTERNAL_DB_PASSWORD}" From 86d35d1c3185cfaa2c642b3ad756135628db1e87 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:22:24 +0200 Subject: [PATCH 259/638] ROX-36889: Delete VM inventory when removing a secured cluster (#22742) --- central/cluster/datastore/datastore.go | 6 + central/cluster/datastore/datastore_impl.go | 45 +++++++ .../datastore/datastore_impl_postgres_test.go | 110 +++++++++++++++++- .../cluster/datastore/datastore_impl_test.go | 38 ++++++ .../datastore/datastore_test_constructors.go | 6 +- central/cluster/datastore/singleton.go | 6 +- central/graphql/resolvers/test_setup_utils.go | 3 +- central/pruning/pruning_test.go | 4 +- .../datastore/datastore_test_constructors.go | 13 +++ .../datastore/datastore_test_constructors.go | 14 +++ 10 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 central/virtualmachine/datastore/datastore_test_constructors.go create mode 100644 central/virtualmachine/v2/datastore/datastore_test_constructors.go diff --git a/central/cluster/datastore/datastore.go b/central/cluster/datastore/datastore.go index 1b9bc7a01b1c9..fc119157ef87e 100644 --- a/central/cluster/datastore/datastore.go +++ b/central/cluster/datastore/datastore.go @@ -23,6 +23,8 @@ import ( secretDataStore "github.com/stackrox/rox/central/secret/datastore" "github.com/stackrox/rox/central/sensor/service/connection" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" + virtualMachineDataStore "github.com/stackrox/rox/central/virtualmachine/datastore" + virtualMachineV2DataStore "github.com/stackrox/rox/central/virtualmachine/v2/datastore" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" @@ -101,6 +103,8 @@ func New( networkBaselineMgr networkBaselineManager.Manager, compliancePruner compliancePruning.Pruner, clusterInitStore clusterInitStore.Store, + virtualMachines virtualMachineDataStore.DataStore, + virtualMachinesV2 virtualMachineV2DataStore.DataStore, ) (DataStore, error) { ds := &datastoreImpl{ clusterStorage: clusterStorage, @@ -127,6 +131,8 @@ func New( nameToIDCache: simplecache.New(), compliancePruner: compliancePruner, clusterInitStore: clusterInitStore, + virtualMachineDataStore: virtualMachines, + virtualMachineV2DataStore: virtualMachinesV2, } if err := ds.buildCache(sac.WithAllAccess(context.Background())); err != nil { diff --git a/central/cluster/datastore/datastore_impl.go b/central/cluster/datastore/datastore_impl.go index 1783f867ed976..62ed1dbc6dc4f 100644 --- a/central/cluster/datastore/datastore_impl.go +++ b/central/cluster/datastore/datastore_impl.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "maps" + "math" "regexp" "slices" "strings" @@ -34,6 +35,8 @@ import ( "github.com/stackrox/rox/central/sensor/service/common" "github.com/stackrox/rox/central/sensor/service/connection" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" + virtualMachineDataStore "github.com/stackrox/rox/central/virtualmachine/datastore" + virtualMachineV2DataStore "github.com/stackrox/rox/central/virtualmachine/v2/datastore" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" @@ -96,6 +99,9 @@ type datastoreImpl struct { compliancePruner compliancePruning.Pruner cm connection.Manager networkBaselineMgr networkBaselineManager.Manager + virtualMachineDataStore virtualMachineDataStore.DataStore + // virtualMachineV2DataStore is nil when VirtualMachinesEnhancedDataModel is off. + virtualMachineV2DataStore virtualMachineV2DataStore.DataStore notifier notifierProcessor.Processor clusterRanker *ranking.Ranker @@ -691,6 +697,8 @@ func (ds *datastoreImpl) postRemoveCluster(ctx context.Context, cluster *storage log.Errorf("failed to remove nodes for cluster %s: %v", cluster.GetId(), err) } + ds.removeClusterVirtualMachines(ctx, cluster) + if err := ds.netEntityDataStore.DeleteExternalNetworkEntitiesForCluster(ctx, cluster.GetId()); err != nil { log.Errorf("failed to delete external network graph entities for removed cluster %s: %v", cluster.GetId(), err) } @@ -768,6 +776,43 @@ func (ds *datastoreImpl) removeClusterPods(ctx context.Context, cluster *storage } } +// removeClusterVirtualMachines deletes V1 and V2 VM inventory for the cluster. +// Child scan/component/CVE rows cascade from the VM; ClusterID has no FK to clusters. +func (ds *datastoreImpl) removeClusterVirtualMachines(ctx context.Context, cluster *storage.Cluster) { + q := pkgSearch.NewQueryBuilder().AddExactMatches(pkgSearch.ClusterID, cluster.GetId()).ProtoQuery() + q.Pagination = &v1.QueryPagination{Limit: math.MaxInt32} + + if ds.virtualMachineDataStore != nil { + vms, err := ds.virtualMachineDataStore.SearchRawVirtualMachines(ctx, q) + if err != nil { + log.Errorf("Failed to get virtual machines for removed cluster %s: %v", cluster.GetId(), err) + } else if ids := virtualMachineIDs(vms); len(ids) > 0 { + if err := ds.virtualMachineDataStore.DeleteVirtualMachines(ctx, ids...); err != nil { + log.Errorf("Failed to remove virtual machines as part of removal of cluster %s: %v", cluster.GetId(), err) + } + } + } + + if ds.virtualMachineV2DataStore != nil { + results, err := ds.virtualMachineV2DataStore.Search(ctx, q) + if err != nil { + log.Errorf("Failed to get v2 virtual machines for removed cluster %s: %v", cluster.GetId(), err) + } else if ids := pkgSearch.ResultsToIDs(results); len(ids) > 0 { + if err := ds.virtualMachineV2DataStore.DeleteVirtualMachines(ctx, ids...); err != nil { + log.Errorf("Failed to remove v2 virtual machines as part of removal of cluster %s: %v", cluster.GetId(), err) + } + } + } +} + +func virtualMachineIDs(vms []*storage.VirtualMachine) []string { + ids := make([]string, 0, len(vms)) + for _, vm := range vms { + ids = append(ids, vm.GetId()) + } + return ids +} + func (ds *datastoreImpl) removeClusterDeployments(ctx context.Context, cluster *storage.Cluster) []string { q := pkgSearch.NewQueryBuilder().AddExactMatches(pkgSearch.ClusterID, cluster.GetId()).ProtoQuery() deployments, err := ds.deploymentDataStore.Search(ctx, q) diff --git a/central/cluster/datastore/datastore_impl_postgres_test.go b/central/cluster/datastore/datastore_impl_postgres_test.go index 13af916ca3ac2..ed85aee19f140 100644 --- a/central/cluster/datastore/datastore_impl_postgres_test.go +++ b/central/cluster/datastore/datastore_impl_postgres_test.go @@ -30,6 +30,10 @@ import ( secretDataStore "github.com/stackrox/rox/central/secret/datastore" "github.com/stackrox/rox/central/sensor/service/connection" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" + vmCVEDatastore "github.com/stackrox/rox/central/virtualmachine/cve/v2/datastore" + vmDatastore "github.com/stackrox/rox/central/virtualmachine/datastore" + vmV2Datastore "github.com/stackrox/rox/central/virtualmachine/v2/datastore" + "github.com/stackrox/rox/central/virtualmachine/v2/datastore/store/common" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" @@ -78,6 +82,9 @@ type ClusterPostgresDataStoreTestSuite struct { imageIntegrationDatastore imageIntegrationDataStore.DataStore clusterDatastore DataStore clusterInitStore clusterInitStore.Store + vmDatastore vmDatastore.DataStore + vmV2Datastore vmV2Datastore.DataStore + vmCVEDatastore vmCVEDatastore.DataStore clusterHealthDBStore clusterHealthPostgresStore.Store } @@ -112,11 +119,15 @@ func (s *ClusterPostgresDataStoreTestSuite) SetupTest() { s.roleBindingDatastore = k8sRoleBindingDataStore.GetTestPostgresDataStore(s.T(), s.db.DB) s.imageIntegrationDatastore = imageIntegrationDataStore.GetTestPostgresDataStore(s.T(), s.db.DB) s.clusterInitStore = clusterInitStore.GetTestPostgresDataStore(s.T(), s.db.DB) + s.vmDatastore = vmDatastore.GetTestPostgresDataStore(s.T(), s.db) + s.vmV2Datastore = vmV2Datastore.GetTestPostgresDataStore(s.T(), s.db) + s.vmCVEDatastore = vmCVEDatastore.GetTestPostgresDataStore(s.T(), s.db) s.clusterDatastore, err = New(clusterDBStore, s.clusterHealthDBStore, clusterCVEStore, s.alertDatastore, s.imageIntegrationDatastore, s.nsDatastore, s.deploymentDatastore, nodeStore, s.podDatastore, s.secretDatastore, netFlowStore, netEntityStore, s.serviceAccountDatastore, s.roleDatastore, s.roleBindingDatastore, sensorCnxMgr, nil, - clusterRanker, networkBaselineM, compliancePruner, s.clusterInitStore) + clusterRanker, networkBaselineM, compliancePruner, s.clusterInitStore, + s.vmDatastore, s.vmV2Datastore) s.NoError(err) } @@ -170,6 +181,43 @@ func (s *ClusterPostgresDataStoreTestSuite) TestRemoveCluster() { s.NotEmpty(imageIntegrationId) s.NoError(imageIntegrationAddErr) + testV1VM := &storage.VirtualMachine{ + Id: fixtureconsts.VirtualMachine1, + Name: "retired-vm-v1", + Namespace: "default", + ClusterId: clusterId, + Scan: &storage.VirtualMachineScan{ + Components: []*storage.EmbeddedVirtualMachineScanComponent{ + {Name: "openssl", Version: "1.1.1"}, + }, + }, + } + s.NoError(s.vmDatastore.UpsertVirtualMachine(ctx, testV1VM)) + + testV2VM := &storage.VirtualMachineV2{ + Id: fixtureconsts.VirtualMachine2, + Name: "retired-vm-v2", + Namespace: "default", + ClusterId: clusterId, + ClusterName: testCluster.GetName(), + GuestOs: "rhel9", + State: storage.VirtualMachineV2_RUNNING, + } + s.NoError(s.vmV2Datastore.UpsertVirtualMachine(ctx, testV2VM)) + s.NoError(s.vmV2Datastore.UpsertScan(ctx, testV2VM.GetId(), clusterRemovalTestScanParts(testV2VM.GetId()))) + + keptCluster := &storage.Cluster{Name: fixtureconsts.ClusterName2, MainImage: mainImage, CentralApiEndpoint: centralEndpoint} + keptClusterID, keptClusterAddErr := s.clusterDatastore.AddCluster(ctx, keptCluster) + s.NotEmpty(keptClusterID) + s.NoError(keptClusterAddErr) + keptV1VM := &storage.VirtualMachine{ + Id: fixtureconsts.VirtualMachineFake, + Name: "kept-vm-v1", + Namespace: "default", + ClusterId: keptClusterID, + } + s.NoError(s.vmDatastore.UpsertVirtualMachine(ctx, keptV1VM)) + // Remove cluster and verify that the removal has been cascaded to all related components doneSignal := concurrency.NewSignal() clusterRemoveErr := s.clusterDatastore.RemoveCluster(ctx, clusterId, &doneSignal) @@ -213,6 +261,66 @@ func (s *ClusterPostgresDataStoreTestSuite) TestRemoveCluster() { _, imageIntegrationFound, imageIntegrationGetErr := s.imageIntegrationDatastore.GetImageIntegration(ctx, testImageIntegration.GetId()) s.NoError(imageIntegrationGetErr) s.False(imageIntegrationFound) + + v1Count, v1CountErr := s.vmDatastore.CountVirtualMachines(ctx, pkgSearch.NewQueryBuilder().AddExactMatches(pkgSearch.ClusterID, clusterId).ProtoQuery()) + s.NoError(v1CountErr) + s.Zero(v1Count) + _, v1Found, v1GetErr := s.vmDatastore.GetVirtualMachine(ctx, testV1VM.GetId()) + s.NoError(v1GetErr) + s.False(v1Found) + + v2Count, v2CountErr := s.vmV2Datastore.CountVirtualMachines(ctx, pkgSearch.NewQueryBuilder().AddExactMatches(pkgSearch.ClusterID, clusterId).ProtoQuery()) + s.NoError(v2CountErr) + s.Zero(v2Count) + _, v2Found, v2GetErr := s.vmV2Datastore.GetVirtualMachine(ctx, testV2VM.GetId()) + s.NoError(v2GetErr) + s.False(v2Found) + + cves, cveSearchErr := s.vmCVEDatastore.SearchRawVMCVEs(ctx, pkgSearch.EmptyQuery()) + s.NoError(cveSearchErr) + s.Empty(cves) + + _, keptFound, keptGetErr := s.vmDatastore.GetVirtualMachine(ctx, keptV1VM.GetId()) + s.NoError(keptGetErr) + s.True(keptFound) +} + +func clusterRemovalTestScanParts(vmID string) common.VMScanParts { + scanID := uuid.NewV4().String() + compID := uuid.NewV4().String() + cveID := uuid.NewV4().String() + return common.VMScanParts{ + Scan: &storage.VirtualMachineScanV2{ + Id: scanID, + VmV2Id: vmID, + ScanOs: "rhel9", + }, + Components: []*storage.VirtualMachineComponentV2{ + { + Id: compID, + VmScanId: scanID, + Name: "openssl", + Version: "1.1.1", + Source: storage.SourceType_OS, + OperatingSystem: "rhel:9", + }, + }, + CVEs: []*storage.VirtualMachineCVEV2{ + { + Id: cveID, + VmV2Id: vmID, + VmComponentId: compID, + CveBaseInfo: &storage.CVEInfo{ + Cve: "CVE-2024-0001", + Summary: "test vulnerability", + }, + PreferredCvss: 7.5, + Severity: storage.VulnerabilitySeverity_IMPORTANT_VULNERABILITY_SEVERITY, + IsFixable: true, + HasFixedBy: &storage.VirtualMachineCVEV2_FixedBy{FixedBy: "1.1.2"}, + }, + }, + } } func (s *ClusterPostgresDataStoreTestSuite) TestPopulateClusterHealthInfo() { diff --git a/central/cluster/datastore/datastore_impl_test.go b/central/cluster/datastore/datastore_impl_test.go index 18fb22376ce0c..757824b7fa839 100644 --- a/central/cluster/datastore/datastore_impl_test.go +++ b/central/cluster/datastore/datastore_impl_test.go @@ -2,6 +2,7 @@ package datastore import ( "errors" + "math" "regexp" "testing" "time" @@ -27,6 +28,9 @@ import ( secretDataStoreMocks "github.com/stackrox/rox/central/secret/datastore/mocks" connectionMocks "github.com/stackrox/rox/central/sensor/service/connection/mocks" serviceAccountDataStoreMocks "github.com/stackrox/rox/central/serviceaccount/datastore/mocks" + virtualMachineDSMocks "github.com/stackrox/rox/central/virtualmachine/datastore/mocks" + virtualMachineV2DSMocks "github.com/stackrox/rox/central/virtualmachine/v2/datastore/mocks" + v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" clusterPkg "github.com/stackrox/rox/pkg/cluster" @@ -80,6 +84,8 @@ type clusterDataStoreTestSuite struct { k8sRoleBindingDS *roleBindingDataStoreMocks.MockDataStore secretDS *secretDataStoreMocks.MockDataStore serviceAccountDS *serviceAccountDataStoreMocks.MockDataStore + vmDS *virtualMachineDSMocks.MockDataStore + vmV2DS *virtualMachineV2DSMocks.MockDataStore networkBaselineMgr *networkBaselineManagerMocks.MockManager @@ -112,6 +118,8 @@ func (s *clusterDataStoreTestSuite) SetupTest() { s.k8sRoleBindingDS = roleBindingDataStoreMocks.NewMockDataStore(s.mockCtrl) s.secretDS = secretDataStoreMocks.NewMockDataStore(s.mockCtrl) s.serviceAccountDS = serviceAccountDataStoreMocks.NewMockDataStore(s.mockCtrl) + s.vmDS = virtualMachineDSMocks.NewMockDataStore(s.mockCtrl) + s.vmV2DS = virtualMachineV2DSMocks.NewMockDataStore(s.mockCtrl) s.networkBaselineMgr = networkBaselineManagerMocks.NewMockManager(s.mockCtrl) @@ -138,6 +146,8 @@ func (s *clusterDataStoreTestSuite) SetupTest() { serviceAccountDataStore: s.serviceAccountDS, roleDataStore: s.k8sRoleDS, roleBindingDataStore: s.k8sRoleBindingDS, + virtualMachineDataStore: s.vmDS, + virtualMachineV2DataStore: s.vmV2DS, cm: s.sensorConnectionMgr, notifier: s.notifierProcessor, clusterRanker: ranking.NewRanker(), @@ -177,6 +187,9 @@ func (s *clusterDataStoreTestSuite) TestPostRemoveCluster() { clusterIDSearchQuery := pkgSearch.NewQueryBuilder().AddExactMatches(pkgSearch.ClusterID, clusterID).ProtoQuery() matchClusterIDSearchQuery := protomock.GoMockMatcherEqualMessage(clusterIDSearchQuery) + vmClusterIDSearchQuery := clusterIDSearchQuery.CloneVT() + vmClusterIDSearchQuery.Pagination = &v1.QueryPagination{Limit: math.MaxInt32} + matchVMClusterIDSearchQuery := protomock.GoMockMatcherEqualMessage(vmClusterIDSearchQuery) testError := errors.New("test error") @@ -300,6 +313,31 @@ func (s *clusterDataStoreTestSuite) TestPostRemoveCluster() { Times(1). Return(tc.status) + // Remove virtual machines (v1 and v2) + v1VMs := make([]*storage.VirtualMachine, 0, len(tc.results)) + for _, result := range tc.results { + v1VMs = append(v1VMs, &storage.VirtualMachine{Id: result.ID}) + } + s.vmDS.EXPECT(). + SearchRawVirtualMachines(gomock.Any(), matchVMClusterIDSearchQuery). + Times(1). + Return(v1VMs, tc.status) + s.vmV2DS.EXPECT(). + Search(gomock.Any(), matchVMClusterIDSearchQuery). + Times(1). + Return(searchResults, tc.status) + if tc.status == nil && len(resultIDs) > 0 { + var deleteErr error + for _, result := range tc.results { + if result.status != nil { + deleteErr = result.status + break + } + } + s.vmDS.EXPECT().DeleteVirtualMachines(gomock.Any(), resultIDs[0], resultIDs[1]).Times(1).Return(deleteErr) + s.vmV2DS.EXPECT().DeleteVirtualMachines(gomock.Any(), resultIDs[0], resultIDs[1]).Times(1).Return(deleteErr) + } + // 9. Delete external network entities for cluster s.networkEntityDS.EXPECT(). DeleteExternalNetworkEntitiesForCluster(gomock.Any(), clusterID). diff --git a/central/cluster/datastore/datastore_test_constructors.go b/central/cluster/datastore/datastore_test_constructors.go index a3c35532a3601..b231c9eacfc82 100644 --- a/central/cluster/datastore/datastore_test_constructors.go +++ b/central/cluster/datastore/datastore_test_constructors.go @@ -25,6 +25,8 @@ import ( secretDataStore "github.com/stackrox/rox/central/secret/datastore" "github.com/stackrox/rox/central/sensor/service/connection" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" + virtualMachineDataStore "github.com/stackrox/rox/central/virtualmachine/datastore" + virtualMachineV2DataStore "github.com/stackrox/rox/central/virtualmachine/v2/datastore" "github.com/stackrox/rox/pkg/postgres" ) @@ -75,5 +77,7 @@ func GetTestPostgresDataStore(t testing.TB, pool postgres.DB) (DataStore, error) alertStore, iiStore, namespaceStore, deploymentStore, nodeStore, podStore, secretStore, netFlowStore, netEntityStore, serviceAccountStore, k8sRoleStore, k8sRoleBindingStore, sensorCnxMgr, nil, - clusterRanker, networkBaselineManager, compliancePruner, clusterInitStore) + clusterRanker, networkBaselineManager, compliancePruner, clusterInitStore, + virtualMachineDataStore.GetTestPostgresDataStore(t, pool), + virtualMachineV2DataStore.GetTestPostgresDataStore(t, pool)) } diff --git a/central/cluster/datastore/singleton.go b/central/cluster/datastore/singleton.go index 80961a103f9e4..17f7b0408f68e 100644 --- a/central/cluster/datastore/singleton.go +++ b/central/cluster/datastore/singleton.go @@ -23,6 +23,8 @@ import ( secretDataStore "github.com/stackrox/rox/central/secret/datastore" "github.com/stackrox/rox/central/sensor/service/connection" serviceAccountDataStore "github.com/stackrox/rox/central/serviceaccount/datastore" + virtualMachineDataStore "github.com/stackrox/rox/central/virtualmachine/datastore" + virtualMachineV2DataStore "github.com/stackrox/rox/central/virtualmachine/v2/datastore" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/pkg/utils" ) @@ -60,7 +62,9 @@ func initialize() { ranking.ClusterRanker(), networkBaselineManager.Singleton(), compliancePruning.Singleton(), - clusterInitStoreSingleton.Singleton()) + clusterInitStoreSingleton.Singleton(), + virtualMachineDataStore.Singleton(), + virtualMachineV2DataStore.Singleton()) utils.CrashOnError(err) } diff --git a/central/graphql/resolvers/test_setup_utils.go b/central/graphql/resolvers/test_setup_utils.go index 0709b99e18a4c..d34ad58c09ae7 100644 --- a/central/graphql/resolvers/test_setup_utils.go +++ b/central/graphql/resolvers/test_setup_utils.go @@ -256,7 +256,8 @@ func CreateTestClusterDatastore(t testing.TB, testDB *pgtest.TestPostgres, ctrl connMgr.EXPECT().GetConnection(gomock.Any()).AnyTimes() datastore, err := clusterDataStore.New(storage, healthStorage, clusterCVEDS, nil, nil, namespaceDS, nil, nodeDataStore, nil, nil, - netFlows, netEntities, nil, nil, nil, connMgr, nil, ranking.ClusterRanker(), nil, nil, nil) + netFlows, netEntities, nil, nil, nil, connMgr, nil, ranking.ClusterRanker(), nil, nil, nil, + nil, nil) assert.NoError(t, err, "failed to create cluster datastore") return datastore } diff --git a/central/pruning/pruning_test.go b/central/pruning/pruning_test.go index cb67672d1ec9d..6cd525a70115c 100644 --- a/central/pruning/pruning_test.go +++ b/central/pruning/pruning_test.go @@ -421,7 +421,9 @@ func (s *PruningTestSuite) generateClusterDataStructures() (configDatastore.Data ranking.NewRanker(), networkBaselineMgr, compliancePruner, - clusterInitStore) + clusterInitStore, + nil, + nil) require.NoError(s.T(), err) return mockConfigDatastore, deployments, clusterDataStore diff --git a/central/virtualmachine/datastore/datastore_test_constructors.go b/central/virtualmachine/datastore/datastore_test_constructors.go new file mode 100644 index 0000000000000..903e13a949d3f --- /dev/null +++ b/central/virtualmachine/datastore/datastore_test_constructors.go @@ -0,0 +1,13 @@ +package datastore + +import ( + "testing" + + pgStore "github.com/stackrox/rox/central/virtualmachine/datastore/internal/store/postgres" + "github.com/stackrox/rox/pkg/postgres" +) + +// GetTestPostgresDataStore provides a datastore connected to postgres for testing purposes. +func GetTestPostgresDataStore(_ testing.TB, pool postgres.DB) DataStore { + return newDatastoreImpl(pgStore.New(pool)) +} diff --git a/central/virtualmachine/v2/datastore/datastore_test_constructors.go b/central/virtualmachine/v2/datastore/datastore_test_constructors.go new file mode 100644 index 0000000000000..5aef488adee23 --- /dev/null +++ b/central/virtualmachine/v2/datastore/datastore_test_constructors.go @@ -0,0 +1,14 @@ +package datastore + +import ( + "testing" + + pgStore "github.com/stackrox/rox/central/virtualmachine/v2/datastore/store/postgres" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/postgres" +) + +// GetTestPostgresDataStore provides a datastore connected to postgres for testing purposes. +func GetTestPostgresDataStore(_ testing.TB, pool postgres.DB) DataStore { + return newDatastoreImpl(pgStore.New(pool, concurrency.NewKeyFence())) +} From 1c53c020f6f6246e44ab8341745fdc48cdaef3ff Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:36:02 +0200 Subject: [PATCH 260/638] ROX-36616, ROX-36627: Fix virtctl fallback and retry scan waits (#22643) --- tests/e2e/vm-scanning-lib.sh | 59 +++++++-- tests/vmhelpers/central_v2.go | 18 ++- tests/vmhelpers/central_v2_test.go | 200 ++++++++++++++++++++++++----- tests/vmhelpers/wait.go | 37 +++++- tests/vmhelpers/wait_test.go | 32 +++++ 5 files changed, 295 insertions(+), 51 deletions(-) diff --git a/tests/e2e/vm-scanning-lib.sh b/tests/e2e/vm-scanning-lib.sh index 37d75054a3283..58d771a00eb62 100755 --- a/tests/e2e/vm-scanning-lib.sh +++ b/tests/e2e/vm-scanning-lib.sh @@ -170,18 +170,61 @@ _download_and_install_virtctl() { return 1 } -# Installs virtctl from the KubeVirt GitHub release matching the cluster's observed version. +# True when $1 is a kubevirt/kubevirt GitHub release tag (v1.6.0 or 1.6.0). +# Image digests (sha256:...) and two-part versions (v1.6) are not release tags. +_is_kubevirt_github_release_tag() { + [[ "$1" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] +} + +_normalize_kubevirt_github_tag() { + local v="$1" + if [[ "$v" == v* ]]; then + printf '%s\n' "$v" + else + printf 'v%s\n' "$v" + fi +} + +# Prints a kubevirt/kubevirt GitHub release tag to stdout (logs go to stderr). +# CNV digest-pins operand images, so observedKubeVirtVersion is often sha256:...; +# those values are skipped. If the CR has no tag, use GitHub's latest release. +_kubevirt_github_release_tag() { + local v + while IFS= read -r v; do + [[ -n "$v" ]] || continue + if _is_kubevirt_github_release_tag "$v"; then + _normalize_kubevirt_github_tag "$v" + return 0 + fi + warn "Ignoring KubeVirt version ${v}: not a GitHub release tag" >&2 + done </dev/null || true) +$(oc get kubevirt -n openshift-cnv -o jsonpath='{.items[0].status.targetKubeVirtVersion}' 2>/dev/null || true) +$(oc get deploy virt-operator -n openshift-cnv -o jsonpath='{range .spec.template.spec.containers[*].env[?(@.name=="KUBEVIRT_VERSION")]}{.value}{"\n"}{end}' 2>/dev/null || true) +EOF + + local latest_url tag + latest_url="$(curl -fsSL --connect-timeout 30 --max-time 60 --retry 5 --retry-delay 5 -o /dev/null -w '%{url_effective}' \ + https://github.com/kubevirt/kubevirt/releases/latest)" || return 1 + tag="${latest_url##*/}" + if ! _is_kubevirt_github_release_tag "$tag"; then + warn "GitHub latest release URL did not end in a tag: ${latest_url}" >&2 + return 1 + fi + tag="$(_normalize_kubevirt_github_tag "$tag")" + info "No cluster KubeVirt GitHub tag; using latest release ${tag}" >&2 + printf '%s\n' "$tag" + return 0 +} + +# Installs virtctl from a kubevirt/kubevirt GitHub release. # ConsoleCLIDownload can stay on HTML while CNV's CSV is Installing or Failed. _install_virtctl_from_kubevirt_release() { local version dest url bin - version="$(oc get kubevirt -n openshift-cnv -o jsonpath='{.items[0].status.observedKubeVirtVersion}' 2>/dev/null || true)" - if [[ -z "$version" ]]; then - version="$(oc get kubevirt -n openshift-cnv -o jsonpath='{.items[0].status.targetKubeVirtVersion}' 2>/dev/null || true)" - fi - if [[ -z "$version" ]]; then - warn "No observedKubeVirtVersion on kubevirt CRs; cannot download virtctl from GitHub" + version="$(_kubevirt_github_release_tag)" || { + warn "Could not resolve a kubevirt/kubevirt GitHub release tag for virtctl" return 1 - fi + } dest="$(_virtctl_install_dir)" _virtctl_add_install_dir_to_path "$dest" diff --git a/tests/vmhelpers/central_v2.go b/tests/vmhelpers/central_v2.go index a464951f2535e..18caf6c158f7b 100644 --- a/tests/vmhelpers/central_v2.go +++ b/tests/vmhelpers/central_v2.go @@ -182,8 +182,8 @@ func ListAllVMComponents(ctx context.Context, client v2.VirtualMachineV2ServiceC } total = resp.GetTotalCount() all = append(all, resp.GetComponents()...) - if int32(len(all)) >= total || len(resp.GetComponents()) == 0 { - return all, total, nil + if v2ListExhausted(len(all), len(resp.GetComponents()), total) { + return all, max(total, int32(len(all))), nil } } return nil, 0, fmt.Errorf("ListVMComponents: exceeded %d pages for vm %s (collected %d, total_count=%d)", v2ListMaxPages, vmID, len(all), total) @@ -210,13 +210,19 @@ func ListAllVMCVEsByVM(ctx context.Context, client v2.VirtualMachineV2ServiceCli } total = resp.GetTotalCount() all = append(all, resp.GetCves()...) - if int32(len(all)) >= total || len(resp.GetCves()) == 0 { - return all, total, nil + if v2ListExhausted(len(all), len(resp.GetCves()), total) { + return all, max(total, int32(len(all))), nil } } return nil, 0, fmt.Errorf("ListVMCVEsByVM: exceeded %d pages for vm %s (collected %d, total_count=%d)", v2ListMaxPages, vmID, len(all), total) } +// v2ListExhausted is true on a short page, or when a positive total_count has +// already been collected. A zero total_count with a full page is not exhausted. +func v2ListExhausted(collected, pageLen int, total int32) bool { + return pageLen < int(v2ListPageSize) || (total > 0 && int32(collected) >= total) +} + // VulnCountBySeverityTotal sums the per-severity total fields. func VulnCountBySeverityTotal(c *v2.VulnCountBySeverity) int32 { if c == nil { @@ -286,9 +292,9 @@ func WaitForV2ScanMissingComponent( if err != nil { return false, "", err } - if filtered.GetTotalCount() > 0 { + if n := max(int(filtered.GetTotalCount()), len(filtered.GetComponents())); n > 0 { return false, fmt.Sprintf("scan_time advances=%d but package %q still present (matches=%d)", - advances, packageName, filtered.GetTotalCount()), nil + advances, packageName, n), nil } comps, total, err := ListAllVMComponents(ctx, client, id) diff --git a/tests/vmhelpers/central_v2_test.go b/tests/vmhelpers/central_v2_test.go index 396b43438644b..51a983322a1a1 100644 --- a/tests/vmhelpers/central_v2_test.go +++ b/tests/vmhelpers/central_v2_test.go @@ -11,6 +11,8 @@ import ( v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stretchr/testify/require" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -145,52 +147,154 @@ func TestWaitForV2VMRunningInCentral(t *testing.T) { } func TestListAllVMCVEsByVM_Pages(t *testing.T) { - ctx := t.Context() - client := &stubV2Client{ - listVMCVEsByVMFn: func(_ context.Context, req *v2.ListVMCVEsByVMRequest) (*v2.ListVMCVEsByVMResponse, error) { - switch req.GetQuery().GetPagination().GetOffset() { - case 0: + page0 := make([]*v2.VMCVERow, v2ListPageSize) + for i := range page0 { + page0[i] = &v2.VMCVERow{Cve: "CVE-full"} + } + tests := map[string]struct { + resp func(offset int32) *v2.ListVMCVEsByVMResponse + wantLen int + wantTotal int32 + wantCalls int + wantLast string + }{ + "short first page": { + resp: func(offset int32) *v2.ListVMCVEsByVMResponse { + if offset != 0 { + return &v2.ListVMCVEsByVMResponse{TotalCount: 2} + } return &v2.ListVMCVEsByVMResponse{ TotalCount: 2, Cves: []*v2.VMCVERow{{Cve: "CVE-1"}, {Cve: "CVE-2"}}, - }, nil - default: - return &v2.ListVMCVEsByVMResponse{TotalCount: 2}, nil - } + } + }, + wantLen: 2, + wantTotal: 2, + wantCalls: 1, + wantLast: "CVE-2", + }, + "zero total_count still pages a full first page": { + resp: func(offset int32) *v2.ListVMCVEsByVMResponse { + switch offset { + case 0: + return &v2.ListVMCVEsByVMResponse{TotalCount: 0, Cves: page0} + case v2ListPageSize: + return &v2.ListVMCVEsByVMResponse{ + TotalCount: 0, + Cves: []*v2.VMCVERow{{Cve: "CVE-last"}}, + } + default: + return &v2.ListVMCVEsByVMResponse{TotalCount: 0} + } + }, + wantLen: v2ListPageSize + 1, + wantTotal: int32(v2ListPageSize + 1), + wantCalls: 2, + wantLast: "CVE-last", }, } - cves, total, err := ListAllVMCVEsByVM(ctx, client, "vid") - require.NoError(t, err) - require.Equal(t, int32(2), total) - require.Equal(t, []string{"CVE-1", "CVE-2"}, []string{cves[0].GetCve(), cves[1].GetCve()}) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + calls := 0 + client := &stubV2Client{ + listVMCVEsByVMFn: func(_ context.Context, req *v2.ListVMCVEsByVMRequest) (*v2.ListVMCVEsByVMResponse, error) { + calls++ + return tc.resp(req.GetQuery().GetPagination().GetOffset()), nil + }, + } + cves, total, err := ListAllVMCVEsByVM(t.Context(), client, "vid") + require.NoError(t, err) + require.Equal(t, tc.wantTotal, total) + require.Len(t, cves, tc.wantLen) + require.Equal(t, tc.wantCalls, calls) + require.Equal(t, tc.wantLast, cves[len(cves)-1].GetCve()) + }) + } } func TestListAllVMComponents_Pages(t *testing.T) { - ctx := t.Context() page0 := make([]*v2.VMComponentRow, v2ListPageSize) for i := range page0 { page0[i] = &v2.VMComponentRow{Name: "pkg"} } - client := &stubV2Client{ - listVMComponentsFn: func(_ context.Context, req *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { - switch req.GetQuery().GetPagination().GetOffset() { - case 0: - return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1), Components: page0}, nil - case v2ListPageSize: - return &v2.ListVMComponentsResponse{ - TotalCount: int32(v2ListPageSize + 1), - Components: []*v2.VMComponentRow{{Name: "last"}}, - }, nil - default: - return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1)}, nil - } + tests := map[string]struct { + resp func(offset int32) *v2.ListVMComponentsResponse + wantLen int + wantTotal int32 + wantCalls int + wantLast string + }{ + "accurate total across two pages": { + resp: func(offset int32) *v2.ListVMComponentsResponse { + switch offset { + case 0: + return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1), Components: page0} + case v2ListPageSize: + return &v2.ListVMComponentsResponse{ + TotalCount: int32(v2ListPageSize + 1), + Components: []*v2.VMComponentRow{{Name: "last"}}, + } + default: + return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize + 1)} + } + }, + wantLen: v2ListPageSize + 1, + wantTotal: int32(v2ListPageSize + 1), + wantCalls: 2, + wantLast: "last", + }, + "zero total_count still pages a full first page": { + resp: func(offset int32) *v2.ListVMComponentsResponse { + switch offset { + case 0: + return &v2.ListVMComponentsResponse{TotalCount: 0, Components: page0} + case v2ListPageSize: + return &v2.ListVMComponentsResponse{ + TotalCount: 0, + Components: []*v2.VMComponentRow{{Name: "last"}}, + } + default: + return &v2.ListVMComponentsResponse{TotalCount: 0} + } + }, + wantLen: v2ListPageSize + 1, + wantTotal: int32(v2ListPageSize + 1), + wantCalls: 2, + wantLast: "last", + }, + "full page matching total does not fetch next": { + resp: func(offset int32) *v2.ListVMComponentsResponse { + if offset != 0 { + return &v2.ListVMComponentsResponse{ + TotalCount: int32(v2ListPageSize), + Components: page0, + } + } + return &v2.ListVMComponentsResponse{TotalCount: int32(v2ListPageSize), Components: page0} + }, + wantLen: v2ListPageSize, + wantTotal: int32(v2ListPageSize), + wantCalls: 1, + wantLast: "pkg", }, } - comps, total, err := ListAllVMComponents(ctx, client, "vid") - require.NoError(t, err) - require.Equal(t, int32(v2ListPageSize+1), total) - require.Len(t, comps, v2ListPageSize+1) - require.Equal(t, "last", comps[len(comps)-1].GetName()) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + calls := 0 + client := &stubV2Client{ + listVMComponentsFn: func(_ context.Context, req *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { + calls++ + return tc.resp(req.GetQuery().GetPagination().GetOffset()), nil + }, + } + comps, total, err := ListAllVMComponents(t.Context(), client, "vid") + require.NoError(t, err) + require.Equal(t, tc.wantTotal, total) + require.Len(t, comps, tc.wantLen) + require.Equal(t, tc.wantCalls, calls) + require.Equal(t, tc.wantLast, comps[len(comps)-1].GetName()) + }) + } } func TestVulnCountBySeverityTotal(t *testing.T) { @@ -286,3 +390,35 @@ func TestWaitForV2ScanReady(t *testing.T) { require.Equal(t, "vid", vm.GetId()) require.GreaterOrEqual(t, calls, 2) } + +func TestWaitForV2ScanReady_RetriesTransientListError(t *testing.T) { + ctx := t.Context() + opts := WaitOptions{Timeout: 400 * time.Millisecond, PollInterval: 5 * time.Millisecond} + var calls int + client := &stubV2Client{ + getVMFn: func(_ context.Context, req *v2.GetVMRequest) (*v2.VMDetail, error) { + return &v2.VMDetail{ + Id: req.GetId(), + GuestOs: "rhel", + LatestScan: &v2.VMScanInfo{ + ScanTime: timestamppb.Now(), + ScanOs: "rhel", + }, + }, nil + }, + listVMComponentsFn: func(context.Context, *v2.ListVMComponentsRequest) (*v2.ListVMComponentsResponse, error) { + calls++ + if calls == 1 { + return nil, status.Error(codes.Unavailable, "central busy") + } + return &v2.ListVMComponentsResponse{ + TotalCount: 1, + Components: []*v2.VMComponentRow{{Name: "bash", ScanStatus: v2.ScanStatus_SCANNED}}, + }, nil + }, + } + vm, err := WaitForV2ScanReady(ctx, client, opts, "vid") + require.NoError(t, err) + require.Equal(t, "vid", vm.GetId()) + require.GreaterOrEqual(t, calls, 2) +} diff --git a/tests/vmhelpers/wait.go b/tests/vmhelpers/wait.go index 820b1fa32f2fe..2d5739418f3cb 100644 --- a/tests/vmhelpers/wait.go +++ b/tests/vmhelpers/wait.go @@ -93,10 +93,31 @@ func validateWaitOptions(desc string, opts WaitOptions) error { return nil } +// isTransientPollError reports gRPC failures that should not abort a Central wait. +// Unavailable/DeadlineExceeded/ResourceExhausted/Aborted are typical of a busy +// Central or a short network blip; Unimplemented and InvalidArgument are not. +func isTransientPollError(err error) bool { + if err == nil || IsAuthenticationExpired(err) { + return false + } + st, ok := status.FromError(err) + if !ok { + return false + } + switch st.Code() { + case codes.Unavailable, codes.DeadlineExceeded, codes.ResourceExhausted, codes.Aborted: + return true + default: + return false + } +} + // pollUntil is the poll loop for Central gRPC scan-lifecycle waits. // Use this instead of k8s wait.PollUntilContextCancel when you need: // - auth-expiry fail-fast: aborts immediately on expired kubeconfig tokens // instead of burning the full timeout, +// - retry of transient gRPC codes until Timeout (v2 scan waits issue more +// RPCs per poll than the legacy GetVirtualMachine path), // - structured diagnostics: each poll returns a detail string so timeout // errors report exactly which stage was stuck. // @@ -121,15 +142,21 @@ func pollUntil(ctx context.Context, opts WaitOptions, desc string, poll func(ctx if IsAuthenticationExpired(err) { return fmt.Errorf("vmhelpers: %s: %w: %v", desc, ErrAuthenticationExpired, err) } - return fmt.Errorf("vmhelpers: %s: %w", desc, err) - } - if done { + if !isTransientPollError(err) { + return fmt.Errorf("vmhelpers: %s: %w", desc, err) + } + if lastDetail == "" { + lastDetail = err.Error() + } + if opts.Logf != nil { + opts.Logf("poll %s: waiting (transient: %v)", desc, err) + } + } else if done { if opts.Logf != nil && detail != "" { opts.Logf("poll %s: done (%s)", desc, detail) } return nil - } - if opts.Logf != nil && detail != "" { + } else if opts.Logf != nil && detail != "" { opts.Logf("poll %s: waiting (%s)", desc, detail) } if time.Now().After(deadline) { diff --git a/tests/vmhelpers/wait_test.go b/tests/vmhelpers/wait_test.go index 2870bda9e0b68..eb3f22338914c 100644 --- a/tests/vmhelpers/wait_test.go +++ b/tests/vmhelpers/wait_test.go @@ -41,6 +41,38 @@ func TestPollUntil_TimesOutWithDetail(t *testing.T) { require.Contains(t, err.Error(), "still waiting") } +func TestPollUntil_RetriesTransientThenSucceeds(t *testing.T) { + ctx := t.Context() + var polls int + err := pollUntil(ctx, WaitOptions{ + Timeout: 400 * time.Millisecond, + PollInterval: 5 * time.Millisecond, + }, "transient-test", func(context.Context) (bool, string, error) { + polls++ + if polls == 1 { + return false, "", status.Error(codes.Unavailable, "central busy") + } + return true, "ok", nil + }) + require.NoError(t, err) + require.Equal(t, 2, polls) +} + +func TestPollUntil_UnimplementedIsTerminal(t *testing.T) { + ctx := t.Context() + var polls int + err := pollUntil(ctx, WaitOptions{ + Timeout: 400 * time.Millisecond, + PollInterval: 5 * time.Millisecond, + }, "unimplemented-test", func(context.Context) (bool, string, error) { + polls++ + return false, "", status.Error(codes.Unimplemented, "not registered") + }) + require.Error(t, err) + require.Equal(t, 1, polls) + require.Contains(t, err.Error(), "Unimplemented") +} + func TestPollUntil_RejectsInvalidOptions(t *testing.T) { ctx := context.Background() From 6a737b0934d314489ea1bf461836b0f9b91c96bb Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:36:49 +0200 Subject: [PATCH 261/638] ROX-35685: Add metric & Sensor mapping updater e2e coverage (#22644) --- sensor/common/scannerdefinitions/metrics.go | 20 ++++++++++++ sensor/common/scannerdefinitions/repo2cpe.go | 8 +++++ .../scannerdefinitions/repo2cpe_test.go | 31 +++++++++++++------ tests/vm_scanning_test.go | 30 ++++++++++++++++-- tests/vmhelpers/central.go | 4 +-- tests/vmhelpers/central_v2.go | 12 +++---- tests/vmhelpers/cluster_preflight.go | 2 +- tests/vmhelpers/compliance.go | 7 +++-- tests/vmhelpers/roxagent_quadlet.go | 24 ++++++++------ tests/vmhelpers/roxagent_quadlet_test.go | 30 ++++++++++++++---- tests/vmhelpers/virtctl.go | 2 +- tests/vmhelpers/vm.go | 2 +- tests/vmhelpers/vm_scan_config.go | 6 ++-- tests/vmhelpers/vm_scan_config_test.go | 2 ++ 14 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 sensor/common/scannerdefinitions/metrics.go diff --git a/sensor/common/scannerdefinitions/metrics.go b/sensor/common/scannerdefinitions/metrics.go new file mode 100644 index 0000000000000..10fa1388cf04a --- /dev/null +++ b/sensor/common/scannerdefinitions/metrics.go @@ -0,0 +1,20 @@ +package scannerdefinitions + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/stackrox/rox/pkg/metrics" +) + +// repoCPEMappingHashChanges counts Sensor replacing its cached mapping when +// Central's file hashes differently, including the first populate. It is not +// a per-VM VSOCK sync (see vsock_pull_sync_total). +var repoCPEMappingHashChanges = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "repo_cpe_mapping_hash_changes_total", + Help: "Times Sensor replaced its cached repo-to-CPE mapping because the content hash from Central (and its upstream source) has changed. This answers the question 'How often does the repo-to-CPE mapping get updated in general?'", +}) + +func init() { + metrics.EmplaceCollector(repoCPEMappingHashChanges) +} diff --git a/sensor/common/scannerdefinitions/repo2cpe.go b/sensor/common/scannerdefinitions/repo2cpe.go index 825c3e37fd41b..6007936e0f493 100644 --- a/sensor/common/scannerdefinitions/repo2cpe.go +++ b/sensor/common/scannerdefinitions/repo2cpe.go @@ -251,14 +251,22 @@ func (r *Repo2CPE) recordRepo2CPEUnchanged(resp *http.Response) { // hashes the same as what's cached, treating a same-hash 200 like a 304. func (r *Repo2CPE) recordRepo2CPESuccess(body []byte, resp *http.Response) { hash := cpemapping.HashMapping(body) + var oldHash string + var changed bool concurrency.WithLock(&r.cacheMu, func() { r.cache.lastSuccess = time.Now() + oldHash = r.cache.hash if hash != r.cache.hash { r.cache.mapping = body r.cache.hash = hash + changed = true } r.replaceRepo2CPEValidatorsNoLock(resp) }) + if changed { + repoCPEMappingHashChanges.Inc() + log.Infof("Repo-to-CPE mapping content hash changed: old=%q new=%q", oldHash, hash) + } log.Debugf("Fetched repo-to-CPE mapping from central, hash=%s", hash) } diff --git a/sensor/common/scannerdefinitions/repo2cpe_test.go b/sensor/common/scannerdefinitions/repo2cpe_test.go index 458d47f34577f..2776484802a99 100644 --- a/sensor/common/scannerdefinitions/repo2cpe_test.go +++ b/sensor/common/scannerdefinitions/repo2cpe_test.go @@ -14,6 +14,7 @@ import ( "testing/synctest" "time" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stackrox/rox/pkg/httputil" "github.com/stackrox/rox/pkg/virtualmachine/cpemapping" "github.com/stackrox/rox/sensor/common" @@ -74,6 +75,7 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { wantHash string wantEtag string wantLastModified string + wantHashChange bool }{ "first fetch succeeds and populates an empty cache": { serverHandler: func(w http.ResponseWriter, r *http.Request) { @@ -81,10 +83,11 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { w.Header().Set(etagHeader, `"v1"`) _, _ = w.Write([]byte(mappingV1)) }, - wantOK: true, - wantMapping: mappingV1, - wantHash: hashV1, - wantEtag: `"v1"`, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + wantEtag: `"v1"`, + wantHashChange: true, }, "304 with matching validators keeps the cached mapping": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, etag: `"v1"`, lastModified: "old", lastSuccess: seededSuccess}, @@ -103,9 +106,10 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { serverHandler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(mappingV2)) }, - wantOK: true, - wantMapping: mappingV2, - wantHash: hashV2, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + wantHashChange: true, }, "200 with a same-hash body is treated as unchanged": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, @@ -121,9 +125,10 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { serverHandler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(mappingV2)) }, - wantOK: true, - wantMapping: mappingV2, - wantHash: hashV2, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + wantHashChange: true, }, "an unexpected status leaves the cache untouched": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, @@ -174,9 +179,15 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { r.centralClient = newTestCentralClient(t, tt.serverHandler) } + before := testutil.ToFloat64(repoCPEMappingHashChanges) ok := r.attemptRepo2CPERefresh(t.Context()) assert.Equal(t, tt.wantOK, ok) + wantCount := before + if tt.wantHashChange { + wantCount++ + } + assert.Equal(t, wantCount, testutil.ToFloat64(repoCPEMappingHashChanges)) assert.Equal(t, tt.wantMapping, string(r.cache.mapping)) assert.Equal(t, tt.wantHash, r.cache.hash) assert.Equal(t, tt.wantEtag, r.cache.etag) diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index 161584f0c4959..a2aeef87a59ec 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -3,11 +3,17 @@ package tests import ( + "context" + "fmt" + "regexp" + "strings" "testing" "time" v2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/namespaces" pkgVM "github.com/stackrox/rox/pkg/virtualmachine" + "github.com/stackrox/rox/tests/logmatchers" "github.com/stackrox/rox/tests/vmhelpers" "github.com/stretchr/testify/require" ) @@ -33,8 +39,8 @@ func (s *VMScanningSuite) TestScanPipeline() { roxagentOK := false t.Run("EnsureRoxagentServing", func(t *testing.T) { - t.Logf("ensuring Quadlet roxagent.service is active (image=%s rescan=%s repo-cpe-url=%s)", - s.cfg.RoxagentImage, vmhelpers.E2ERescanInterval, s.cfg.Repo2CPEURL) + t.Logf("ensuring Quadlet roxagent.service is active (image=%s rescan=%s)", + s.cfg.RoxagentImage, vmhelpers.E2ERescanInterval) err := s.ensureRoxagentServing(s.ctx, vm) require.NoError(t, err) roxagentOK = true @@ -44,6 +50,13 @@ func (s *VMScanningSuite) TestScanPipeline() { return } + t.Run("WaitForSensorPushedMapping", func(t *testing.T) { + if strings.TrimSpace(s.cfg.Repo2CPEURL) != "" { + t.Skip("ROXAGENT_REPO2CPE_URL set: agent is URL-managed, Sensor will not push mapping") + } + s.waitForSensorPushedMapping(vm) + }) + t.Run("WaitForScan", func(t *testing.T) { var err error snapshot, err = s.waitForScan(s.ctx, vm) @@ -240,6 +253,19 @@ func (s *VMScanningSuite) TestScanPipeline() { } } +// waitForSensorPushedMapping waits until Sensor logs a successful repo-to-CPE +// mapping push for vm. Setup installs without --repo-cpe-url, so a scan cannot +// complete until this push happens. +func (s *VMScanningSuite) waitForSensorPushedMapping(vm *VMHandle) { + waitCtx, cancel := context.WithTimeout(s.ctx, s.cfg.ScanTimeout) + defer cancel() + re := regexp.MustCompile(regexp.QuoteMeta( + fmt.Sprintf(`VMScraper: synced repo-to-CPE mapping to "%s/%s"`, vm.Namespace, vm.Name))) + s.waitUntilLog(waitCtx, namespaces.StackRox, sensorPodLabels, sensorContainer, + "contain Sensor-pushed repo-to-CPE mapping sync", + logmatchers.ContainsLineMatching(re)) +} + func (s *VMScanningSuite) requireProbePackagePresent(t *testing.T, snapshot *centralScanSnapshot, pkg string) int { t.Helper() if s.enhancedVMModel { diff --git a/tests/vmhelpers/central.go b/tests/vmhelpers/central.go index 8b4234ea1af5b..cfe33bec43826 100644 --- a/tests/vmhelpers/central.go +++ b/tests/vmhelpers/central.go @@ -40,7 +40,7 @@ func waitForVMCondition(ctx context.Context, client v2.VirtualMachineServiceClie err := pollUntil(ctx, opts, desc, func(ctx context.Context) (bool, string, error) { cur, err := client.GetVirtualMachine(ctx, &v2.GetVirtualMachineRequest{Id: id}) if err != nil { - return false, "", err + return false, "", fmt.Errorf("get virtual machine %s: %w", id, err) } done, detail := check(cur) if done { @@ -154,7 +154,7 @@ func ListVMByNamespaceName(ctx context.Context, client v2.VirtualMachineServiceC }, }) if err != nil { - return nil, err + return nil, fmt.Errorf("list virtual machines: %w", err) } if vms := resp.GetVirtualMachines(); len(vms) > 0 { return vms[0], nil diff --git a/tests/vmhelpers/central_v2.go b/tests/vmhelpers/central_v2.go index 18caf6c158f7b..92bf0cd2cba4b 100644 --- a/tests/vmhelpers/central_v2.go +++ b/tests/vmhelpers/central_v2.go @@ -27,7 +27,7 @@ func ListV2VMByNamespaceName(ctx context.Context, client v2.VirtualMachineV2Serv }, }) if err != nil { - return nil, err + return nil, fmt.Errorf("list vms: %w", err) } if vms := resp.GetVms(); len(vms) > 0 { return vms[0], nil @@ -62,7 +62,7 @@ func waitForV2VMCondition(ctx context.Context, client v2.VirtualMachineV2Service err := pollUntil(ctx, opts, desc, func(ctx context.Context) (bool, string, error) { cur, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) if err != nil { - return false, "", err + return false, "", fmt.Errorf("get vm %s: %w", id, err) } done, detail := check(cur) if done { @@ -112,7 +112,7 @@ func WaitForV2ScanReady(ctx context.Context, client v2.VirtualMachineV2ServiceCl err := pollUntil(ctx, opts, fmt.Sprintf("V2 scan ready (id=%q)", id), func(ctx context.Context) (bool, string, error) { vm, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) if err != nil { - return false, "", err + return false, "", fmt.Errorf("get vm %s: %w", id, err) } scan := vm.GetLatestScan() if scan == nil { @@ -178,7 +178,7 @@ func ListAllVMComponents(ctx context.Context, client v2.VirtualMachineV2ServiceC }, }) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("list vm components %s: %w", vmID, err) } total = resp.GetTotalCount() all = append(all, resp.GetComponents()...) @@ -206,7 +206,7 @@ func ListAllVMCVEsByVM(ctx context.Context, client v2.VirtualMachineV2ServiceCli }, }) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("list vm cves %s: %w", vmID, err) } total = resp.GetTotalCount() all = append(all, resp.GetCves()...) @@ -263,7 +263,7 @@ func WaitForV2ScanMissingComponent( func(ctx context.Context) (bool, string, error) { vm, err := client.GetVM(ctx, &v2.GetVMRequest{Id: id}) if err != nil { - return false, "", err + return false, "", fmt.Errorf("get vm %s: %w", id, err) } scan := vm.GetLatestScan() if scan == nil { diff --git a/tests/vmhelpers/cluster_preflight.go b/tests/vmhelpers/cluster_preflight.go index 759e569caee95..01b89f959ac4d 100644 --- a/tests/vmhelpers/cluster_preflight.go +++ b/tests/vmhelpers/cluster_preflight.go @@ -94,7 +94,7 @@ func nodeKVMCapacityString(node coreV1.Node) string { func InspectClusterKVMReadiness(ctx context.Context, k8s kubernetes.Interface) (ClusterKVMPreflightResult, error) { nodeList, err := k8s.CoreV1().Nodes().List(ctx, metaV1.ListOptions{}) if err != nil { - return ClusterKVMPreflightResult{}, err + return ClusterKVMPreflightResult{}, fmt.Errorf("list nodes: %w", err) } result := ClusterKVMPreflightResult{ diff --git a/tests/vmhelpers/compliance.go b/tests/vmhelpers/compliance.go index cc8a0846b85fd..4e4771628b933 100644 --- a/tests/vmhelpers/compliance.go +++ b/tests/vmhelpers/compliance.go @@ -67,14 +67,17 @@ func EnsureComplianceMetricsEnv( err = retry.RetryOnConflict(retry.DefaultRetry, func() error { current, getErr := k8sClient.AppsV1().DaemonSets(ns).Get(ctx, dsName, metaV1.GetOptions{}) if getErr != nil { - return getErr + return fmt.Errorf("get daemonset %s/%s: %w", ns, dsName, getErr) } needsUpdate, setErr := SetContainerEnv(current, containerName, envName, envValue) if setErr != nil || !needsUpdate { return setErr } _, updateErr := k8sClient.AppsV1().DaemonSets(ns).Update(ctx, current, metaV1.UpdateOptions{}) - return updateErr + if updateErr != nil { + return fmt.Errorf("update daemonset %s/%s: %w", ns, dsName, updateErr) + } + return nil }) if err != nil { return fmt.Errorf("updating DaemonSet %s/%s: %w", ns, dsName, err) diff --git a/tests/vmhelpers/roxagent_quadlet.go b/tests/vmhelpers/roxagent_quadlet.go index cece26b627b31..80e99db9f001e 100644 --- a/tests/vmhelpers/roxagent_quadlet.go +++ b/tests/vmhelpers/roxagent_quadlet.go @@ -217,25 +217,22 @@ func stageQuadletInstall(image, repo2cpeURL, podmanAuthPath string) (string, err } if err := os.WriteFile(filepath.Join(dir, quadletContainerFileName), overlayed, 0o644); err != nil { _ = os.RemoveAll(dir) - return "", err + return "", fmt.Errorf("write %s: %w", quadletContainerFileName, err) } if err := os.WriteFile(filepath.Join(dir, quadletInstallScriptName), installSrc, 0o755); err != nil { _ = os.RemoveAll(dir) - return "", err + return "", fmt.Errorf("write %s: %w", quadletInstallScriptName, err) } return dir, nil } // overlayQuadletContainer rewrites Image= and Exec= for the cluster image and -// a 5m rescan. A non-empty authFile sets REGISTRY_AUTH_FILE on [Service]: -// RHEL 8 Quadlet rejects AuthFile= as an unsupported [Container] key. +// a 5m rescan. A non-empty authFile sets REGISTRY_AUTH_FILE on [Service] +// because RHEL 8 Quadlet rejects AuthFile=. func overlayQuadletContainer(src []byte, image, repo2cpeURL, authFile string) ([]byte, error) { if strings.TrimSpace(image) == "" { return nil, errors.New("overlayQuadletContainer: image is empty") } - if strings.TrimSpace(repo2cpeURL) == "" { - return nil, errors.New("overlayQuadletContainer: repo2cpeURL is empty") - } sawImage, sawExec := false, false srcLines := strings.Split(string(src), "\n") @@ -253,8 +250,7 @@ func overlayQuadletContainer(src []byte, image, repo2cpeURL, authFile string) ([ out = append(out, "Environment=REGISTRY_AUTH_FILE="+authFile) } case strings.HasPrefix(line, "Exec="): - out = append(out, "Exec=serve --host-path /host --rescan-interval "+ - E2ERescanInterval.String()+" --repo-cpe-url "+repo2cpeURL) + out = append(out, overlayExecLine(repo2cpeURL)) sawExec = true default: out = append(out, line) @@ -268,3 +264,13 @@ func overlayQuadletContainer(src []byte, image, repo2cpeURL, authFile string) ([ } return []byte(strings.Join(out, "\n")), nil } + +// overlayExecLine is the Quadlet Exec= line. --repo-cpe-url is omitted when +// empty so systemd cannot pass a blank argument to serve. +func overlayExecLine(repo2cpeURL string) string { + line := "Exec=serve --host-path /host --rescan-interval " + E2ERescanInterval.String() + if u := strings.TrimSpace(repo2cpeURL); u != "" { + line += " --repo-cpe-url " + u + } + return line +} diff --git a/tests/vmhelpers/roxagent_quadlet_test.go b/tests/vmhelpers/roxagent_quadlet_test.go index 206c9fa5e5062..e64262c40afc3 100644 --- a/tests/vmhelpers/roxagent_quadlet_test.go +++ b/tests/vmhelpers/roxagent_quadlet_test.go @@ -31,11 +31,34 @@ func TestOverlayQuadletContainer(t *testing.T) { require.NotContains(t, out, "Image=quay.io/stackrox-io/main:latest") require.NotContains(t, out, "AuthFile=") require.NotContains(t, out, "REGISTRY_AUTH_FILE") - require.Contains(t, out, "Exec=serve --host-path /host --rescan-interval "+E2ERescanInterval.String()+" --repo-cpe-url "+url) + require.Contains(t, out, overlayExecLine(url)) require.Contains(t, out, "Notify=true") require.Contains(t, out, "TimeoutStartSec=600") } +func TestOverlayQuadletContainer_OmitsRepoCPEURLWhenEmpty(t *testing.T) { + t.Parallel() + + src, err := os.ReadFile(filepath.Join(repoRoot(), "compliance", "virtualmachines", "roxagent", "quadlet", "roxagent.container")) + require.NoError(t, err) + + const image = "quay.io/rhacs-eng/main:e2e-test" + for name, url := range map[string]string{ + "empty": "", + "whitespace": " \t", + } { + t.Run(name, func(t *testing.T) { + t.Parallel() + got, err := overlayQuadletContainer(src, image, url, "") + require.NoError(t, err) + out := string(got) + require.Contains(t, out, overlayExecLine("")) + require.NotContains(t, out, "--repo-cpe-url") + require.NotContains(t, out, "--repo-cpe-url ") + }) + } +} + func TestOverlayQuadletContainer_RegistryAuthFile(t *testing.T) { t.Parallel() @@ -67,11 +90,6 @@ func TestOverlayQuadletContainer_Errors(t *testing.T) { repo2cpeURL: "https://example.test/mapping.json", errSubstring: "image is empty", }, - "should reject empty repo2cpe URL": { - src: src, - image: "example:latest", - errSubstring: "repo2cpeURL is empty", - }, "should reject missing Image=": { src: []byte("[Container]\nExec=serve\n"), image: "example:latest", diff --git a/tests/vmhelpers/virtctl.go b/tests/vmhelpers/virtctl.go index d4cac7ed1016e..5d0feaa89bbbf 100644 --- a/tests/vmhelpers/virtctl.go +++ b/tests/vmhelpers/virtctl.go @@ -96,7 +96,7 @@ func (v Virtctl) run(ctx context.Context, args []string) (stdout string, stderr stdoutStr := outBuf.String() stderrStr := errBuf.String() v.Logf("remote command could not start: %s (result=%v)", summary, err) - return stdoutStr, stderrStr, err + return stdoutStr, stderrStr, fmt.Errorf("start virtctl: %w", err) } err = waitForCommandWithContext(ctx, cmd) diff --git a/tests/vmhelpers/vm.go b/tests/vmhelpers/vm.go index 4277f02073bf6..ed233f8b57d81 100644 --- a/tests/vmhelpers/vm.go +++ b/tests/vmhelpers/vm.go @@ -384,7 +384,7 @@ func virtualMachineStatusDetail(ctx context.Context, client dynamic.Interface, n if apierrors.IsNotFound(err) { return "virtual machine object not found", false, nil } - return "", false, err + return "", false, fmt.Errorf("get virtual machine %s/%s: %w", namespace, name, err) } vm, err := vmFromUnstructured(obj) if err != nil { diff --git a/tests/vmhelpers/vm_scan_config.go b/tests/vmhelpers/vm_scan_config.go index 2655f6681646d..1c2b544be6fdd 100644 --- a/tests/vmhelpers/vm_scan_config.go +++ b/tests/vmhelpers/vm_scan_config.go @@ -28,15 +28,15 @@ const ( defaultScanPollInterval = 10 * time.Second defaultDeleteTimeout = 5 * time.Minute defaultGuestUser = "cloud-user" - - defaultRepo2CPEURL = "https://security.access.redhat.com/data/metrics/repository-to-cpe.json" ) var ( vmScanNamespacePrefix = env.RegisterSetting("VM_SCAN_NAMESPACE_PREFIX", env.WithDefault("vm-scan-e2e")) vmScanSkipCleanup = env.RegisterBooleanSetting("VM_SCAN_SKIP_CLEANUP", false) - repo2CPEURL = env.RegisterSetting("ROXAGENT_REPO2CPE_URL", env.WithDefault(defaultRepo2CPEURL)) + // repo2CPEURL is empty so Quadlet omits --repo-cpe-url and the agent + // stays Sensor-managed. Set ROXAGENT_REPO2CPE_URL to force URL-managed. + repo2CPEURL = env.RegisterSetting("ROXAGENT_REPO2CPE_URL") ) // VMSpec describes a VM to provision: container-disk image and guest SSH user. diff --git a/tests/vmhelpers/vm_scan_config_test.go b/tests/vmhelpers/vm_scan_config_test.go index 32cbee8d8c83e..ede5eebbc8e54 100644 --- a/tests/vmhelpers/vm_scan_config_test.go +++ b/tests/vmhelpers/vm_scan_config_test.go @@ -35,10 +35,12 @@ func TestLoadVMScanConfig_Defaults(t *testing.T) { t.Setenv("ROXAGENT_IMAGE", "") t.Setenv("MAIN_IMAGE", "quay.io/example/main:test") t.Setenv("VM_SCAN_NAMESPACE_PREFIX", "") + t.Setenv("ROXAGENT_REPO2CPE_URL", "") cfg, err := LoadVMScanConfig() require.NoError(t, err) require.Equal(t, []string{"registry.example.com/rhel9:latest", "registry.example.com/rhel10:latest"}, cfg.Images) require.Empty(t, cfg.GuestUsers, "no padding; VMSpecs() defaults per-image") + require.Empty(t, cfg.Repo2CPEURL, "empty URL selects Sensor-managed mapping updater") require.Equal(t, "vm-scan-e2e", cfg.NamespacePrefix) require.Equal(t, 20*time.Minute, cfg.ScanTimeout) require.Equal(t, 10*time.Second, cfg.ScanPollInterval) From c75761f1b006953ab1b4f346bb2da5e7fbeb56db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:48:04 +0200 Subject: [PATCH 262/638] chore(deps): bump gacts/run-and-post-run from 1.4.4 to 1.4.5 in /.github/actions/cache-go-dependencies (#22732) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/cache-go-dependencies/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/cache-go-dependencies/action.yaml b/.github/actions/cache-go-dependencies/action.yaml index 0d70c4ee4ee3e..43900bbb10a3f 100644 --- a/.github/actions/cache-go-dependencies/action.yaml +++ b/.github/actions/cache-go-dependencies/action.yaml @@ -67,7 +67,7 @@ runs: # Registered AFTER cache steps so the post-step runs BEFORE cache save # (GHA runs post-steps in reverse registration order). - name: Trim stale GOCACHE entries (post-step) - uses: gacts/run-and-post-run@598d7a875d5620e0457490555b5e18e46082aa47 # v1.4.0 + uses: gacts/run-and-post-run@fc6503862aba998e2f2123225ad63c09a92ed46e # v1.4.0 with: post: > set +e; From 4ea68b9d39d02558c3b8a2c94e42875c0eafca2e Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:48:25 +0200 Subject: [PATCH 263/638] fix(test): skip CentralWorker E2E when FF is disabled (#22741) --- qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy b/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy index 6937a0289a6ff..42b305f5c9ca3 100644 --- a/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy +++ b/qa-tests-backend/src/test/groovy/CentralWorkerTest.groovy @@ -4,11 +4,16 @@ import io.stackrox.proto.api.v2.ReportServiceOuterClass import services.CollectionsService import services.ReportV2Service +import util.Env +import spock.lang.IgnoreIf import spock.lang.Tag import spock.lang.Shared @Tag("CentralWorker") +// CentralWorkerTest requires a central-worker pod, which is off unless +// ROX_CENTRAL_WORKER_ENABLED is true at deploy time. +@IgnoreIf({ Env.get("ROX_CENTRAL_WORKER_ENABLED", "false") != "true" }) class CentralWorkerTest extends BaseSpecification { @Shared From e638f0be9df0532025418b57b4ec1a0d3295ad11 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:54:16 +0200 Subject: [PATCH 264/638] ROX-36887: Scan standalone VMIs first seen before Running (#22746) --- .../dispatcher/virtualmachineinstances.go | 12 ++- .../virtualmachineinstances_test.go | 86 +++++++++++++------ 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go index 4bdddbafc6ee0..e1a9bccc1fa50 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances.go @@ -80,10 +80,16 @@ func (d *VirtualMachineInstanceDispatcher) ProcessEvent( BootOrder: extractBootOrder(disks), CDRomDisks: extractCDRomDisks(disks), } - // If the instance is NOT handled by a VirtualMachine - // Process the instance as a VirtualMachine + // Ownerless VMIs are the authoritative instance: UpdateStateOrCreate writes + // Running so a later boot is visible to ListRunning. if !handled { - return processVirtualMachine(vm, action, d.clusterID, d.store) + if action == central.ResourceAction_REMOVE_RESOURCE { + d.store.Remove(vm.ID) + } else { + d.store.UpdateStateOrCreate(vm) + } + attachStoredAgentFacts(d.store, vm) + return component.NewEvent(createEvent(action, d.clusterID, vm)) } // This is an instance that is handled by a VirtualMachine diff --git a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go index 0903d10d571b4..5359ce202e5e5 100644 --- a/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go +++ b/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/virtualmachineinstances_test.go @@ -13,6 +13,7 @@ import ( vmInfo "github.com/stackrox/rox/sensor/common/virtualmachine" "github.com/stackrox/rox/sensor/kubernetes/eventpipeline/component" "github.com/stackrox/rox/sensor/kubernetes/listener/resources/virtualmachine/dispatcher/mocks" + vmstore "github.com/stackrox/rox/sensor/kubernetes/listener/resources/virtualmachine/store" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" "google.golang.org/protobuf/proto" @@ -235,7 +236,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { action: central.ResourceAction_CREATE_RESOURCE, obj: toUnstructured(newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, ownerUID, "Not-VirtualMachine", nil, v1.Scheduled)), expectFn: func() { - s.store.EXPECT().AddOrUpdate(gomock.Eq( + s.store.EXPECT().UpdateStateOrCreate(gomock.Eq( &vmInfo.Info{ ID: vmiUID, Name: vmiName, @@ -243,14 +244,8 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { VSOCKCID: nil, Running: false, }), - ).Times(1).Return( - &vmInfo.Info{ - ID: vmiUID, - Name: vmiName, - Namespace: vmiNamespace, - VSOCKCID: nil, - Running: false, - }) + ).Times(1) + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(vmiUID))).Times(1).Return(nil) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ Id: vmiUID, @@ -271,7 +266,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { action: central.ResourceAction_UPDATE_RESOURCE, obj: toUnstructured(newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, ownerUID, "Not-VirtualMachine", nil, v1.Scheduled)), expectFn: func() { - s.store.EXPECT().AddOrUpdate(gomock.Eq( + s.store.EXPECT().UpdateStateOrCreate(gomock.Eq( &vmInfo.Info{ ID: vmiUID, Name: vmiName, @@ -279,14 +274,8 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { VSOCKCID: nil, Running: false, }), - ).Times(1).Return( - &vmInfo.Info{ - ID: vmiUID, - Name: vmiName, - Namespace: vmiNamespace, - VSOCKCID: nil, - Running: false, - }) + ).Times(1) + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(vmiUID))).Times(1).Return(nil) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ Id: vmiUID, @@ -308,6 +297,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { obj: toUnstructured(newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, ownerUID, "Not-VirtualMachine", nil, v1.Scheduled)), expectFn: func() { s.store.EXPECT().Remove(gomock.Eq(vmInfo.VMID(vmiUID))).Times(1) + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(vmiUID))).Times(1).Return(nil) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ Id: vmiUID, @@ -328,7 +318,7 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { action: central.ResourceAction_SYNC_RESOURCE, obj: toUnstructured(newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, ownerUID, "Not-VirtualMachine", nil, v1.Scheduled)), expectFn: func() { - s.store.EXPECT().AddOrUpdate(gomock.Eq( + s.store.EXPECT().UpdateStateOrCreate(gomock.Eq( &vmInfo.Info{ ID: vmiUID, Name: vmiName, @@ -336,14 +326,8 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { VSOCKCID: nil, Running: false, }), - ).Times(1).Return( - &vmInfo.Info{ - ID: vmiUID, - Name: vmiName, - Namespace: vmiNamespace, - VSOCKCID: nil, - Running: false, - }) + ).Times(1) + s.store.EXPECT().Get(gomock.Eq(vmInfo.VMID(vmiUID))).Times(1).Return(nil) }, expectedMsg: component.NewEvent(¢ral.SensorEvent{ Id: vmiUID, @@ -452,6 +436,54 @@ func (s *virtualMachineInstanceSuite) Test_VirtualMachineInstanceEvents() { } } +// Test_OwnerlessVMIScheduledThenRunningIsScrapable covers an ownerless VMI first +// seen before Running: later Running must reach the store and ListRunning. +func (s *virtualMachineInstanceSuite) Test_OwnerlessVMIScheduledThenRunningIsScrapable() { + cases := map[string]struct { + buildVMI func(phase v1.VirtualMachineInstancePhase) *v1.VirtualMachineInstance + }{ + "should mark ownerless VMI running after Scheduled then Running": { + buildVMI: func(phase v1.VirtualMachineInstancePhase) *v1.VirtualMachineInstance { + vmi := newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, "", "", nil, phase) + vmi.OwnerReferences = nil + return vmi + }, + }, + "should mark ReplicaSet-owned VMI running after Scheduled then Running": { + buildVMI: func(phase v1.VirtualMachineInstancePhase) *v1.VirtualMachineInstance { + return newVirtualMachineInstanceWithOwnerKind(vmiUID, vmiName, vmiNamespace, ownerUID, "VirtualMachineInstanceReplicaSet", nil, phase) + }, + }, + } + for name, tc := range cases { + s.Run(name, func() { + realStore := vmstore.NewVirtualMachineStore() + d := NewVirtualMachineInstanceDispatcher(clusterID, realStore) + + scheduled := d.ProcessEvent(toUnstructured(tc.buildVMI(v1.Scheduled)), nil, central.ResourceAction_CREATE_RESOURCE) + s.Require().NotNil(scheduled) + stored := realStore.Get(vmInfo.VMID(vmiUID)) + s.Require().NotNil(stored) + s.False(stored.Running) + s.Empty(realStore.ListRunning()) + + running := d.ProcessEvent(toUnstructured(tc.buildVMI(v1.Running)), nil, central.ResourceAction_UPDATE_RESOURCE) + s.Require().NotNil(running) + s.Require().Len(running.ForwardMessages, 1) + vmEvent := running.ForwardMessages[0].GetVirtualMachine() + s.Require().NotNil(vmEvent) + s.Equal(virtualMachineV1.VirtualMachine_RUNNING, vmEvent.GetState()) + + stored = realStore.Get(vmInfo.VMID(vmiUID)) + s.Require().NotNil(stored) + s.True(stored.Running) + runningList := realStore.ListRunning() + s.Require().Len(runningList, 1) + s.Equal(vmInfo.VMID(vmiUID), runningList[0].ID) + }) + } +} + func newVirtualMachineInstanceWithOwnerKind(uid, name, namespace, owner, kind string, vsock *uint32, phase v1.VirtualMachineInstancePhase) *v1.VirtualMachineInstance { return &v1.VirtualMachineInstance{ ObjectMeta: metav1.ObjectMeta{ From f9324109b9406e7aaea4dab4ab59321cf3ff5884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:21:21 +0200 Subject: [PATCH 265/638] chore(deps): bump github.com/sigstore/rekor from 1.5.3 to 1.5.4 (#22417) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 47 +++++++++-------- go.sum | 156 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 99 insertions(+), 104 deletions(-) diff --git a/go.mod b/go.mod index 9c7f6cb0b99ec..c18d2906450ff 100644 --- a/go.mod +++ b/go.mod @@ -120,7 +120,7 @@ require ( github.com/segmentio/analytics-go/v3 v3.3.0 github.com/sergi/go-diff v1.4.0 github.com/sigstore/cosign/v3 v3.1.3 - github.com/sigstore/rekor v1.5.3 + github.com/sigstore/rekor v1.5.4 github.com/sigstore/sigstore v1.10.9 github.com/sigstore/sigstore-go v1.2.2 github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 @@ -156,7 +156,7 @@ require ( golang.org/x/tools v0.49.0 golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.292.0 - google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94 + google.golang.org/genproto v0.0.0-20260622175928-b703f567277d google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 google.golang.org/grpc v1.83.0 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 @@ -191,7 +191,7 @@ require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.22.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/iam v1.11.0 // indirect + cloud.google.com/go/iam v1.13.0 // indirect cloud.google.com/go/longrunning v1.2.0 // indirect cloud.google.com/go/monitoring v1.29.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect @@ -208,7 +208,7 @@ require ( github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/RaduBerinde/axisds v0.1.0 // indirect github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect @@ -231,7 +231,6 @@ require ( github.com/beevik/etree v1.7.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect - github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -275,7 +274,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/getsentry/sentry-go v0.34.0 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect - github.com/go-chi/chi/v5 v5.3.0 // indirect + github.com/go-chi/chi/v5 v5.3.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect @@ -284,28 +283,29 @@ require ( github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.25.2 // indirect + github.com/go-openapi/analysis v0.26.0 // indirect github.com/go-openapi/errors v0.22.8 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.6 // indirect - github.com/go-openapi/loads v0.24.0 // indirect - github.com/go-openapi/runtime v0.32.4 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/loads v0.25.1 // indirect + github.com/go-openapi/runtime v0.32.5 // indirect github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect - github.com/go-openapi/spec v0.22.6 // indirect - github.com/go-openapi/strfmt v0.26.4 // indirect + github.com/go-openapi/spec v0.22.9 // indirect + github.com/go-openapi/strfmt v0.27.0 // indirect github.com/go-openapi/swag v0.26.1 // indirect github.com/go-openapi/swag/cmdutils v0.26.1 // indirect - github.com/go-openapi/swag/conv v0.27.0 // indirect - github.com/go-openapi/swag/fileutils v0.26.1 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect + github.com/go-openapi/swag/fileutils v0.28.0 // indirect github.com/go-openapi/swag/jsonname v0.26.1 // indirect - github.com/go-openapi/swag/jsonutils v0.26.1 // indirect - github.com/go-openapi/swag/loading v0.26.1 // indirect - github.com/go-openapi/swag/mangling v0.26.1 // indirect + github.com/go-openapi/swag/jsonutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/mangling v0.28.0 // indirect github.com/go-openapi/swag/netutils v0.26.1 // indirect - github.com/go-openapi/swag/stringutils v0.26.1 // indirect - github.com/go-openapi/swag/typeutils v0.27.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.1 // indirect - github.com/go-openapi/validate v0.26.0 // indirect + github.com/go-openapi/swag/pools v0.28.0 // indirect + github.com/go-openapi/swag/stringutils v0.28.0 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/go-openapi/validate v0.26.3 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect @@ -404,7 +404,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect - github.com/sassoftware/relic v7.2.1+incompatible // indirect + github.com/sassoftware/relic/v8 v8.2.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect github.com/segmentio/backo-go v1.0.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect @@ -440,7 +440,6 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - github.com/zalando/go-keyring v0.2.6 // indirect github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98 // indirect github.com/zmap/zlint/v3 v3.6.6 // indirect go.etcd.io/bbolt v1.4.3 // indirect diff --git a/go.sum b/go.sum index fca5070e86e69..db099f2579a92 100644 --- a/go.sum +++ b/go.sum @@ -52,10 +52,10 @@ cloud.google.com/go/containeranalysis v0.19.0/go.mod h1:Zq0XHzUIa0oTa7H6aSR8HWqe cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= -cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= -cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE= -cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= +cloud.google.com/go/iam v1.13.0 h1:ufT3FPT5rFFXu6UtLkNoxaOaV5EuA1dsSkmemCSTo6U= +cloud.google.com/go/iam v1.13.0/go.mod h1:gHXdDEiPDvqd1q1KwBDGQlgZY/BwY760zU2LhOZS5w0= +cloud.google.com/go/kms v1.33.0 h1:pG0X78m212b2pv9N4fdMoUO69LuZGQ9kSvn8sHBOFAo= +cloud.google.com/go/kms v1.33.0/go.mod h1:CSGvW6GnMQbY+1nOHcIzhMtHSbExXlOmCKjWtYVjcpA= cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k= cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM= @@ -163,8 +163,8 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI= github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaduBerinde/axisds v0.1.0 h1:YItk/RmU5nvlsv/awo2Fjx97Mfpt4JfgtEVAGPrLdz8= @@ -241,8 +241,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 h1:BBEElKh4 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35/go.mod h1:zaZk983w//8beSruBVec/mr4CmDwgZitW/qzGhAAX0g= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 h1:EUIwBoN+q7UmhAejxgD27APiRjh1vwCFo53gSqdT0BM= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36/go.mod h1:6u00gmlTGR6W0b2k9NBrld7MnOEmf1Spqx0VVt6AqyE= -github.com/aws/aws-sdk-go-v2/service/kms v1.53.4 h1:PEgVSsWtR8NNxsDxFL2Ywisi7R+1EFQARGsT4q3mWwI= -github.com/aws/aws-sdk-go-v2/service/kms v1.53.4/go.mod h1:3EeKyDGPGSCEphG2OolwNGNF45RvQIfm27AYYpfEWrw= +github.com/aws/aws-sdk-go-v2/service/kms v1.55.0 h1:uB8ymkVosyourmGXCZHyWhJ4wuKA4xq3ii2dVMPtBZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.55.0/go.mod h1:rK4RITSY/qJw3qVJ7p19fceOWuvrisqqOChFkX05n5I= github.com/aws/aws-sdk-go-v2/service/s3 v1.107.0 h1:OkYV+1171za+ab9otU1tGxMXhx6uZvwVEtVddjLuYTg= github.com/aws/aws-sdk-go-v2/service/s3 v1.107.0/go.mod h1:5FTZoQxhmLEiCAtYVk6V+t0iS/B5yGZVLZ3Wq5FDJZI= github.com/aws/aws-sdk-go-v2/service/securityhub v1.76.0 h1:J/olEWGa5NIIZiejnuG4u+ahM5liNIKa07sEP5OPbqs= @@ -271,8 +271,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= @@ -486,8 +484,8 @@ github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01 github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= +github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -525,30 +523,30 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= -github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= +github.com/go-openapi/analysis v0.26.0 h1:1xECln1iMMmQnTjgcknC1vi1hA4KISt6IHpSwnqcuwI= +github.com/go-openapi/analysis v0.26.0/go.mod h1:40gERFi/2dyXA1FaqRRLxkv1IlC6X+GPDNd1xrYAjZE= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= -github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= -github.com/go-openapi/loads v0.24.0 h1:4LLorXRPTzIN9V6ngMUZbAscsBOUBk3Oa8cClu/bFrQ= -github.com/go-openapi/loads v0.24.0/go.mod h1:xQMgX+hw5xRAhGrcDXxeMw78IFqUpIzhleu3HqPhyF4= -github.com/go-openapi/runtime v0.32.4 h1:8ElGj/3goG0itt0nBPP6Cm57ehcYyuHoI3O20nxgvkw= -github.com/go-openapi/runtime v0.32.4/go.mod h1:Bz6keOZw1NX4T6f+m42OoT1MBPDt6Re13dbccHyGH/4= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/loads v0.25.1 h1:toKQdIDLxlqfKLLGUUmUsiTd5/X0Chzvde9EGYQP/Ac= +github.com/go-openapi/loads v0.25.1/go.mod h1:33Hen4tsKXHL45TyYojvfD5fZUFN4O1y4r/XhsRW2zc= +github.com/go-openapi/runtime v0.32.5 h1:+aDmMj6xB4P8isUwEOERtgHW3JNI/i93KvTWNa4LRik= +github.com/go-openapi/runtime v0.32.5/go.mod h1:eNPUCk8LAYV803GHJP3xDSNllJQfv+pBlXpFfWqpPO8= github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= -github.com/go-openapi/spec v0.22.6 h1:Tyy1pLaNCM8GBCFLoGYLonjJi6zykqyLCjXLc19ZPic= -github.com/go-openapi/spec v0.22.6/go.mod h1:HZvTHat+iH0PALQRWhrqIHtU/PEqxqd89fu0MxGlMeM= -github.com/go-openapi/strfmt v0.26.4 h1:yI6IAEfcWow459BD5UzFY430KUwXZwBHrYusPFkhWlc= -github.com/go-openapi/strfmt v0.26.4/go.mod h1:hNJi6nb5ETD6i7A1yRo03M9S6ZoTPPoWff1iUexmfUc= +github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= +github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= +github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= +github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -558,41 +556,43 @@ github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGo github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= github.com/go-openapi/swag/cmdutils v0.26.1 h1:f2iE1ijYaJ3nuu5PaEMx3zpEhzhZFgivCJObWEObLIQ= github.com/go-openapi/swag/cmdutils v0.26.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.27.0 h1:EKOH4feXrvdo8DbSsXSAqRT8fz1epEnS5O2IfXUOzE8= -github.com/go-openapi/swag/conv v0.27.0/go.mod h1:pfiv0uKQTbaGApk8Zs/lZV3uSjmSpa2FO1y183YngN8= -github.com/go-openapi/swag/fileutils v0.26.1 h1:K1XCM2CGhfNsc6YDt6v7Q5+1e59rftYWdcu/isZhvFw= -github.com/go-openapi/swag/fileutils v0.26.1/go.mod h1:mYUgxQAKX4ShS3qvvySx+/9yrlUnDhjiD1CalaQl8lQ= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= +github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= +github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= -github.com/go-openapi/swag/jsonutils v0.26.1 h1:2hdBfFkHg+7Wrz2VsCbeyR6hzkRDs7AztnMR2u84yOY= -github.com/go-openapi/swag/jsonutils v0.26.1/go.mod h1:U+RMJH3wa+6BRiphuRtIyI8fW9HPFqFQ4sHk2oRx0UQ= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1 h1:1CD7NiLLb/TXl3tOnFYU4b+mNfb5rtgHkaA+q7RMYYQ= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1/go.mod h1:ZWafc8nMdYzTE3uYY6W86f0n46+IF0g4uUyRhJw/kXc= -github.com/go-openapi/swag/loading v0.26.1 h1:E9K4wqXeROlhjFQ13K9zMz6ojFGXIggGe+ad1odrK9w= -github.com/go-openapi/swag/loading v0.26.1/go.mod h1:3qvRIlWzWdq1HvmldwmuJ2ohpcAryN6xVt2OTKd0/7E= -github.com/go-openapi/swag/mangling v0.26.1 h1:gpYI4WuPKFJJVjV5cDLGlDVJhFIxYjQc7yN5eEb4CqM= -github.com/go-openapi/swag/mangling v0.26.1/go.mod h1:POETDH01hqAdASXfw7ISEd9bCOE6xBHOt8NHmGZRmYM= +github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= +github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= github.com/go-openapi/swag/netutils v0.26.1 h1:BNctoc39WTAUMxyAs355fExOPzMZtPbZ0ZZ1Am2FR5M= github.com/go-openapi/swag/netutils v0.26.1/go.mod h1:y02vByhZhQPAVwOX+0KipXFZ/hUbk6G/Enhf5rGaOkQ= -github.com/go-openapi/swag/stringutils v0.26.1 h1:f88uYyTso7TnHrKM/bUBsQ5e2wKf37cpgo6pvbzd9yU= -github.com/go-openapi/swag/stringutils v0.26.1/go.mod h1:Sc6d3bU8fgk5AyZR8/8jEQ+Is/Ald+TD/IIggPN8UJk= -github.com/go-openapi/swag/typeutils v0.27.0 h1:aCf4MSGo8NLwZP8Q6t32DWLJSvl/WwNqgmEG+xJ6v2o= -github.com/go-openapi/swag/typeutils v0.27.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.26.1 h1:0TSLK+lXs9vfIhAWzBeI/lOzEnIoot6WTCO1aAeWFTk= -github.com/go-openapi/swag/yamlutils v0.26.1/go.mod h1:7W5b7PRX9MxwL7TjeG7H8HkyBGRsIDRObhyMWFgBI2M= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.26.0 h1:dxWzQ3F+vb1SajqUxHjwb5T4mTpSHmdrtv5Bi7+ZNhw= -github.com/go-openapi/validate v0.26.0/go.mod h1:b4o00uq7fJeJA+wWhVFCJpKTctzeFwzZImGGmHsl2JA= +github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc= +github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= -github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -763,8 +763,6 @@ github.com/grafana/pyroscope-go/godeltaprof v0.1.11 h1:el5LYpXissAiCKZ5/6yjlr6mh github.com/grafana/pyroscope-go/godeltaprof v0.1.11/go.mod h1:jl1V8M4cWsXciROCPIDDG7CtjSjT/ECbp6eLVuMxYRI= github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 h1:nz9OX9q1iUJZwrPA2Ge8xNlB3inmq+pafyf6qmBLBew= github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= @@ -827,8 +825,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= -github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4g3h6A= +github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= github.com/heimdalr/dag v1.5.1 h1:Oug6YdjFpQN1Itrc/deoIDqtWUk4FO96ulod23/TOr8= github.com/heimdalr/dag v1.5.1/go.mod h1:lthekrHl01dddmzqyBQ1YZbi7XcVGGzjFo0jIky5knc= github.com/helm/helm-mapkubeapis v0.6.1 h1:GDEEe8ol1MiSs4a9TD8LzBb+ftkC+V7M67gkxx7UmAE= @@ -864,8 +862,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= -github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= -github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= +github.com/jellydator/ttlcache/v3 v3.4.1 h1:bOdXmXiycyK6E6Qjyuj5vl+/vU3SCOoDs8a86NbHjAQ= +github.com/jellydator/ttlcache/v3 v3.4.1/go.mod h1:j7LO12PNghFg5+0v9budMAT4rDK4JY969jb9vOdOBBk= github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -1188,8 +1186,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 h1:1/BDligzCa40GTllkDnY3Y5DTH github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3/go.mod h1:3dZmcLn3Qw6FLlWASn1g4y+YO9ycEFUOM+bhBmzLVKQ= github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgYaOmx1l+lYJyVdrRUOs= github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g= -github.com/redis/go-redis/v9 v9.20.1 h1:sfCU6A8P3dXbKyWes02uxA2baehGux9dZHfEKtsTB1w= -github.com/redis/go-redis/v9 v9.20.1/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= +github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 h1:ukjThsA2ou7AmovpwtMVkNQSuoN/v5U16+JomTz3c7o= github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319/go.mod h1:rhSvwcijY9wfmrBYrfCvapX8/xOTV46NAUjBRgUyJqc= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -1216,10 +1214,8 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= -github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= -github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= -github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= +github.com/sassoftware/relic/v8 v8.2.0 h1:9/L4S4I6an/JsPNhmcTpqfiOIsLb/iJaePZD1xR+ulE= +github.com/sassoftware/relic/v8 v8.2.0/go.mod h1:pZy7hLT9WCOKPonV8G/fplvtBLOZd6/kWtKqeHR6nKc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= @@ -1238,22 +1234,22 @@ github.com/sigstore/cosign/v3 v3.1.3 h1:001JQRI/PJ/5T+g/kJ1KTvKFbb322+fomc+pHDZ/ github.com/sigstore/cosign/v3 v3.1.3/go.mod h1:DmjtYkWDMdbG26X+QSOPB6QQGkLjRjQCIxxNs6wV6bA= github.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U= github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= -github.com/sigstore/rekor v1.5.3 h1:0Tyolw3zreRgm7PUW8dccFLXGBThi08278jI8EXNSr4= -github.com/sigstore/rekor v1.5.3/go.mod h1:h3GK5dDqCcWJJZUJwdpKGSSmEV2GEjPUjJy3WTjBwzA= +github.com/sigstore/rekor v1.5.4 h1:A9zITTrkRwO+8lfq7L/gBR7rDHHcBQJRI+a0JQo4xDM= +github.com/sigstore/rekor v1.5.4/go.mod h1:0bcUHhZPlG+RVW1dI/7sdBp0MiVWVzk9h0tgOH/o0Yc= github.com/sigstore/rekor-tiles/v2 v2.3.0 h1:HhMgH61UP0t899V8Fjt7pz1YdgOBptbaQdnCF+79cdc= github.com/sigstore/rekor-tiles/v2 v2.3.0/go.mod h1:DEFiKSyQ4nF75QRVNdOPaIH3cmvMkO2B6xDZjNYngPc= github.com/sigstore/sigstore v1.10.9 h1:7Dcpt+ibnltHQZ8XhaU0dFmhHaf/T491eJfA9WDex4Y= github.com/sigstore/sigstore v1.10.9/go.mod h1:LYW9+qH7bK8wZmLm6lPxIC5lkHtkJDCgkqjChzTAIBs= github.com/sigstore/sigstore-go v1.2.2 h1:xAJ8hxaoecC0HKBYVbrwUjkeAI+GJYu6vLqbxDlD2Q0= github.com/sigstore/sigstore-go v1.2.2/go.mod h1:MIFwBxAHJD+/lKgZzt9n/4Zhq/3T2+EuGX8iGrIsZgU= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8/go.mod h1:YiTpAsxoWXhF9KlLOVWCh7BckN5cYO8X01WufDq1ido= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 h1:MxpAIMZVzn0Tpbarc9ax1I498oQBp7oYSMgoMSsOmKI= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8/go.mod h1:bnAUEkFNam6STvkVZhptVwWzWR5pS24CEtQ+lhxu7S0= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 h1:1DGe4/clcdOnkz5MINEczWlmEvjUtZd+AjPPT/cBhQ8= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8/go.mod h1:6IDFhpgxtzqbnzrFkyegbj7RfWwKeRrb3/+xAD1Wp+Y= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.9 h1:2NWAU/utJRwzy1OrQurlY3wlDSynyH1rNJS5GgFchrg= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.9/go.mod h1:2shgYiPHTzeb5NNEu6okco+LOojMS5n9p/5Wdp0jhGI= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.9 h1:zcg/EG6JW/8RSx9T2g5j6zqxplaE4dymmhiFflDE6ks= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.9/go.mod h1:e7QngtHib0McuP1C/+Dt9f/7a8ic8FXvPB75OQzWgU4= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.9 h1:DG8R4orWDs3l9trp70fLRTgazedEDoAIo/cHEN7Qo0k= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.9/go.mod h1:d6oIwQ0YiYHMcFO4xqFcAx5njXwIf0dm5KfdytOar8A= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.9 h1:V+spyeSHypvLHSOvPMT+V72dxDlS0LDkDd6lwCsUOXM= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.9/go.mod h1:o7u5bQNLqpDQFaST29dpo6P6ihMV+wicwqfMmMW6H3c= github.com/sigstore/timestamp-authority/v2 v2.1.2 h1:7DDhnknLL4w8VwomyvW2W8qblOS9LDR8oihna+jc7Ls= github.com/sigstore/timestamp-authority/v2 v2.1.2/go.mod h1:o6rAVZceFyejClIj/uStRNIemP16bVMZtbMmhk6pr0U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1371,8 +1367,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tink-crypto/tink-go-awskms/v3 v3.0.0 h1:XSohRhCkXAVI0iaCnWB/GS05TEmpnKurQmzaY1jzt3Y= github.com/tink-crypto/tink-go-awskms/v3 v3.0.0/go.mod h1:+7MXsShLzVbSQ6dI0Pe4JuZM52jD1jQ1itAygd/MDsA= -github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= -github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.4.0 h1:xpI5pnEQ0erFCS3JmcEl7Blluo8ZieEPNOURqy9uD70= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.4.0/go.mod h1:wDDAhjfd1t4TjeJCSsmFK7CARPUs/ITi16ZZ8kmAaFQ= github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0 h1:eXuNqgrcYelxU1MVikOJDP3wTS5lvihM4ntoAbAMfvs= github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0/go.mod h1:3RhcxAqek6xUlRFmJifvU4CYLZN60KMQdIKqpZAZJG0= github.com/tink-crypto/tink-go/v2 v2.7.0 h1:k7QnUXJ1cRDpvoy/5l1FimZqMAArRff8vjUqzi5N04o= @@ -1509,8 +1505,8 @@ go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUS go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.step.sm/crypto v0.81.0 h1:e+ouzpNt3Xm4dp7HGXhgYB5y4iFik3vh3phHKWmvugU= -go.step.sm/crypto v0.81.0/go.mod h1:fsTizqQeASjTXnbv9O00XtRlIuXRkCdoRiJNyXGQujc= +go.step.sm/crypto v0.87.0 h1:+u0pDz1OV9M/SswA6KtcQ3GSrYMMelbL//0W/ceJVYI= +go.step.sm/crypto v0.87.0/go.mod h1:gBr1mpMiKs5804/Yt03uAoOBiw3DFxARZnGUKEUO82M= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -2039,8 +2035,8 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94 h1:YJjbgu+dkp5kUJLfpMyCLfBIWZb/FcJyuLeo1gVBOuo= -google.golang.org/genproto v0.0.0-20260519071638-aa98bba5eb94/go.mod h1:RRHjglSYABVCWpQ7USCpdfhcd9t4PkajvVwyynZizTc= +google.golang.org/genproto v0.0.0-20260622175928-b703f567277d h1:CP5omUq8AJTiWMrPKM1WRLJ7zZeXd9OPcQD3TbBNAyY= +google.golang.org/genproto v0.0.0-20260622175928-b703f567277d/go.mod h1:DrwuGJgFSEVNpv3S5Q5VxhRTvdnjauw9GtvwVOEARfA= google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 h1:4namukbyF7JY83aWHQwi9J5ugNTnDReLJ9ZcpqOpRB4= google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= @@ -2253,5 +2249,5 @@ sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= +software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= From 6e9ae5c20901c71a691827956d770c2b0e5e06cb Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:29:14 -0500 Subject: [PATCH 266/638] ROX-36102: Generate node report (#22707) --- .../Configurations/NodeConfigurationsList.tsx | 174 ++++++++++++++++-- .../ViewVulnReport/ViewVulnReportPage.tsx | 12 +- .../VulnReports/ConfigReportsTab.tsx | 14 +- .../VulnerablityReporting/api/useRunReport.ts | 10 +- .../api/useWatchLastSnapshotForReports.ts | 44 ++--- .../src/services/NodeReportsService.ts | 68 +++++++ 6 files changed, 281 insertions(+), 41 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx index d844f7bec8705..226798eef0499 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/NodeConfigurationsList.tsx @@ -1,6 +1,9 @@ import { useCallback, useState } from 'react'; -import { Link, generatePath } from 'react-router-dom-v5-compat'; +import { Link, generatePath, useNavigate } from 'react-router-dom-v5-compat'; import { + Alert, + AlertActionCloseButton, + AlertGroup, Button, Content, DropdownItem, @@ -14,11 +17,17 @@ import { ToolbarItem, } from '@patternfly/react-core'; import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import type { IAction } from '@patternfly/react-table'; import HelpIconTh from 'Components/HelpIconTh'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; import PageTitle from 'Components/PageTitle'; +import JobStatusPopoverContent from 'Components/ReportJob/JobStatusPopoverContent'; +import MyLastJobStatus from 'Components/ReportJob/MyLastJobStatus'; import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import useAuthStatus from 'hooks/useAuthStatus'; +import useToasts from 'hooks/patternfly/useToasts'; +import type { Toast } from 'hooks/patternfly/useToasts'; import usePermissions from 'hooks/usePermissions'; import useRestQuery from 'hooks/useRestQuery'; import useTableSelection from 'hooks/useTableSelection'; @@ -31,12 +40,17 @@ import { } from 'routePaths'; import { deleteNodeReportConfiguration, + fetchMyNodeReportHistory, fetchNodeReportConfigurations, fetchNodeReportConfigurationsCount, + nodeReportDownloadURL, + runNodeReport, } from 'services/NodeReportsService'; import { getTableUIState } from 'utils/getTableUIState'; import DeleteReportsModal from '../../components/DeleteReportsModal'; +import useRunReport from '../../../VulnerablityReporting/api/useRunReport'; +import { useWatchLastSnapshotForReports } from '../../../VulnerablityReporting/api/useWatchLastSnapshotForReports'; import useDeleteModal from '../../../VulnerablityReporting/hooks/useDeleteModal'; const CreateReportsButton = () => { @@ -55,6 +69,10 @@ const sortOptions = { }; function NodeConfigurationsList() { + const navigate = useNavigate(); + const { currentUser } = useAuthStatus(); + const { toasts, addToast, removeToast } = useToasts(); + const { hasReadWriteAccess, hasReadAccess } = usePermissions(); const hasDeleteAccessForReport = hasReadWriteAccess('WorkflowAdministration'); const hasWriteAccessForReport = @@ -98,6 +116,26 @@ function NodeConfigurationsList() { fetchReportConfigurationsCountCallback ); + const { reportSnapshots, isLoading: isLoadingReportSnapshots } = useWatchLastSnapshotForReports( + reportConfigurations ?? null, + fetchMyNodeReportHistory + ); + + const { isRunning, runError, runReport } = useRunReport({ + runReportRequest: runNodeReport, + onCompleted: ({ reportNotificationMethod }) => { + if (reportNotificationMethod === 'EMAIL') { + addToast('The report has been sent to the configured email notifier', 'success'); + } else if (reportNotificationMethod === 'DOWNLOAD') { + addToast( + 'The report generation has started and will be available for download once complete', + 'success' + ); + } + refetchList(); + }, + }); + // table should still load even if countData is not available const totalReports = countData?.count ?? 0; @@ -150,8 +188,30 @@ function NodeConfigurationsList() { return ( <> + + {toasts.map(({ key, variant, title, children }: Toast) => ( + removeToast(key)} + actionClose={ + removeToast(key)} + /> + } + > + {children} + + ))} + + {runError && } @@ -227,7 +287,21 @@ function NodeConfigurationsList() { Resources
    - + + } + > + My last job status + {hasActionsColumn && @@ -251,6 +325,82 @@ function NodeConfigurationsList() { reportId: report.id, } ); + const snapshot = reportSnapshots[report.id]; + const isReportStatusPending = + snapshot?.reportStatus.runState === 'PREPARING' || + snapshot?.reportStatus.runState === 'WAITING'; + + const writeActions: IAction[] = hasWriteAccessForReport + ? [ + { + title: 'Edit report', + onClick: (event) => { + event.preventDefault(); + navigate(`${reportURL}?action=edit`); + }, + isDisabled: isReportStatusPending, + }, + { isSeparator: true }, + { + title: 'Send report', + description: + report.notifiers.length === 0 + ? 'No delivery destinations set' + : '', + onClick: (event) => { + event.preventDefault(); + runReport(report.id, 'EMAIL'); + }, + isDisabled: + isReportStatusPending || + report.notifiers.length === 0, + }, + { + title: 'Generate download', + onClick: (event) => { + event.preventDefault(); + runReport(report.id, 'DOWNLOAD'); + }, + isDisabled: isReportStatusPending, + }, + { + title: 'Clone report', + onClick: (event) => { + event.preventDefault(); + navigate(`${reportURL}?action=clone`); + }, + }, + ] + : []; + const deleteActions: IAction[] = hasDeleteAccessForReport + ? [ + { + title: ( + + Delete report + + ), + onClick: (event) => { + event.preventDefault(); + openDeleteModal([report.id]); + }, + isDisabled: isReportStatusPending, + }, + ] + : []; + const rowActions: IAction[] = [ + ...writeActions, + ...(writeActions.length > 0 && deleteActions.length > 0 + ? [{ isSeparator: true } as IAction] + : []), + ...deleteActions, + ]; return ( @@ -271,19 +421,19 @@ function NodeConfigurationsList() { - + {hasActionsColumn && ( )} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx index 46c54cc6b4aa4..9fe794c6b4e55 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/ViewVulnReport/ViewVulnReportPage.tsx @@ -32,7 +32,11 @@ import NotFoundMessage from 'Components/NotFoundMessage/NotFoundMessage'; import usePermissions from 'hooks/usePermissions'; import useToasts from 'hooks/patternfly/useToasts'; import type { Toast } from 'hooks/patternfly/useToasts'; -import { deleteReportConfiguration } from 'services/ReportsService'; +import { + deleteReportConfiguration, + fetchReportHistory, + runReportRequest, +} from 'services/ReportsService'; import type { ReportConfiguration } from 'services/ReportsService.types'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; @@ -72,7 +76,10 @@ function ViewVulnReportPage() { hasReadAccess('Integration'); // for notifiers const { reportConfiguration, isLoading, error: fetchError } = useFetchReport(reportId); - const { reportSnapshots } = useWatchLastSnapshotForReports(reportConfiguration); + const { reportSnapshots } = useWatchLastSnapshotForReports( + reportConfiguration, + fetchReportHistory + ); const reportSnapshot = reportSnapshots[reportId]; const { @@ -92,6 +99,7 @@ function ViewVulnReportPage() { const { toasts, addToast, removeToast } = useToasts(); const { isRunning, runError, runReport } = useRunReport({ + runReportRequest, onCompleted: ({ reportNotificationMethod }) => { if (reportNotificationMethod === 'EMAIL') { addToast('The report has been sent to the configured email notifier', 'success'); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx index 9f8fd1935508b..f426756ce8783 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/VulnReports/ConfigReportsTab.tsx @@ -45,7 +45,12 @@ import HelpIconTh from 'Components/HelpIconTh'; import JobStatusPopoverContent from 'Components/ReportJob/JobStatusPopoverContent'; import MyLastJobStatus from 'Components/ReportJob/MyLastJobStatus'; import useAuthStatus from 'hooks/useAuthStatus'; -import { deleteReportConfiguration, reportDownloadURL } from 'services/ReportsService'; +import { + deleteReportConfiguration, + fetchReportHistory, + reportDownloadURL, + runReportRequest, +} from 'services/ReportsService'; import type { ImageVulnerabilityReportConfiguration, ImageVulnerabilityReportResourceScope, @@ -115,9 +120,12 @@ function ConfigReportsTab() { perPage, sortOption, }); - const { reportSnapshots, isLoading: isLoadingReportSnapshots } = - useWatchLastSnapshotForReports(reportConfigurations); + const { reportSnapshots, isLoading: isLoadingReportSnapshots } = useWatchLastSnapshotForReports( + reportConfigurations, + fetchReportHistory + ); const { isRunning, runError, runReport } = useRunReport({ + runReportRequest, onCompleted: ({ reportNotificationMethod }) => { if (reportNotificationMethod === 'EMAIL') { addToast('The report has been sent to the configured email notifier', 'success'); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useRunReport.ts b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useRunReport.ts index ae141823900e7..9c53548a10e66 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useRunReport.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useRunReport.ts @@ -4,12 +4,16 @@ import useAnalytics, { VULNERABILITY_REPORT_DOWNLOAD_GENERATED, VULNERABILITY_REPORT_SENT_MANUALLY, } from 'hooks/useAnalytics'; -import { runReportRequest } from 'services/ReportsService'; +import type { RunReportResponse } from 'services/ReportsService.types'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; import type { ReportNotificationMethod } from 'types/reportJob'; export type UseSaveReportProps = { onCompleted: (context: { reportNotificationMethod: ReportNotificationMethod }) => void; + runReportRequest: ( + reportConfigId: string, + reportNotificationMethod: ReportNotificationMethod + ) => Promise; }; type Result = { @@ -28,7 +32,7 @@ const defaultResult = { runError: null, }; -function useRunReport({ onCompleted }: UseSaveReportProps): SaveReportResult { +function useRunReport({ onCompleted, runReportRequest }: UseSaveReportProps): SaveReportResult { const { analyticsTrack } = useAnalytics(); const [result, setResult] = useState(defaultResult); @@ -63,7 +67,7 @@ function useRunReport({ onCompleted }: UseSaveReportProps): SaveReportResult { }); }); }, - [analyticsTrack, onCompleted] + [analyticsTrack, onCompleted, runReportRequest] ); return { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useWatchLastSnapshotForReports.ts b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useWatchLastSnapshotForReports.ts index 0cb2c988fac2f..adb9dc77dd247 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useWatchLastSnapshotForReports.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/VulnerablityReporting/api/useWatchLastSnapshotForReports.ts @@ -1,29 +1,17 @@ import { useCallback } from 'react'; -import { fetchReportHistory } from 'services/ReportsService'; -import type { ReportConfiguration, ReportSnapshot } from 'services/ReportsService.types'; +import type { FetchReportHistoryServiceParams } from 'services/ReportsService'; +import type { ConfiguredReportSnapshot, ReportSnapshot } from 'services/ReportsService.types'; import useInterval from 'hooks/useInterval'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; import useRestQuery from 'hooks/useRestQuery'; -// TODO investigate whether this hook is for generic or specific report type. +// Only the config id is needed, so accept a minimal shape to support any report type. +type ReportConfigurationRef = { id: string }; -async function fetchLastReportJobForConfiguration( - reportConfigurationId: string -): Promise { - const reportSnapshot = await fetchReportHistory({ - id: reportConfigurationId, - query: '', - page: 1, - perPage: 1, - showMyHistory: true, - sortOption: { - field: 'Report Completion Time', - reversed: true, - }, - }); - return reportSnapshot[0] ?? null; -} +type FetchReportHistory = ( + params: FetchReportHistoryServiceParams +) => Promise; type ReportSnapshotLookup = Partial>; @@ -38,7 +26,8 @@ export type FetchLastSnapshotReturn = Result & { }; export function useWatchLastSnapshotForReports( - reportConfigurations: ReportConfiguration | ReportConfiguration[] | null + reportConfigurations: ReportConfigurationRef | ReportConfigurationRef[] | null, + fetchReportHistory: FetchReportHistory ): FetchLastSnapshotReturn { const fetchSnapshotsCallback = useCallback(() => { if (!reportConfigurations) { @@ -46,6 +35,19 @@ export function useWatchLastSnapshotForReports( return Promise.resolve(result); } + const fetchLastReportJobForConfiguration = (id: string) => + fetchReportHistory({ + id, + query: '', + page: 1, + perPage: 1, + showMyHistory: true, + sortOption: { + field: 'Report Completion Time', + reversed: true, + }, + }).then((snapshots) => snapshots[0] ?? null); + const promise: Promise = new Promise((resolve, reject) => { const configurations = Array.isArray(reportConfigurations) ? reportConfigurations @@ -65,7 +67,7 @@ export function useWatchLastSnapshotForReports( }); return promise; - }, [reportConfigurations]); + }, [reportConfigurations, fetchReportHistory]); const { data, isLoading, error, refetch } = useRestQuery(fetchSnapshotsCallback); useInterval(refetch, 10000); diff --git a/ui/apps/platform/src/services/NodeReportsService.ts b/ui/apps/platform/src/services/NodeReportsService.ts index 336c72af8d32f..6f4cbfbffd434 100644 --- a/ui/apps/platform/src/services/NodeReportsService.ts +++ b/ui/apps/platform/src/services/NodeReportsService.ts @@ -1,25 +1,34 @@ import queryString from 'qs'; +import type { ReportNotificationMethod } from 'types/reportJob'; import type { SearchFilter, SearchQueryOptions } from 'types/search'; import { buildNestedRawQueryParams, getListQueryParams, + getPaginationParams, getRequestQueryStringForSearchFilter, } from 'utils/searchUtils'; import { makeCancellableAxiosRequest } from './cancellationUtils'; import type { CancellableRequest } from './cancellationUtils'; import axios from './instance'; +import type { FetchReportHistoryServiceParams } from './ReportsService'; +import { isConfiguredReportSnapshot } from './ReportsService.types'; import type { + ConfiguredReportSnapshot, NodeViewBasedReportSnapshot, NodeVulnerabilityReportConfiguration, + ReportHistoryResponse, ReportRequestViewBased, + RunReportResponse, RunReportResponseViewBased, } from './ReportsService.types'; import type { Empty } from './types'; // https://github.com/stackrox/stackrox/blob/master/proto/api/v2/node_report_service.proto +export const nodeReportDownloadURL = '/api/reports/node/jobs/download'; + // Configuration of scheduled reports // PostNodeReportConfiguration @@ -95,10 +104,69 @@ export function deleteNodeReportConfiguration(reportId: string): Promise // Configuration-based jobs // RunNodeReport +export function runNodeReport( + reportConfigId: string, + reportNotificationMethod: ReportNotificationMethod +): Promise { + return axios + .post('/v2/reports/node/run', { + reportConfigId, + reportNotificationMethod, + }) + .then((response) => response.data); +} + +type FetchNodeReportHistoryServiceParams = Omit; // GetNodeReportHistory +export function fetchNodeReportHistory({ + id, + query, + page, + perPage, + sortOption, +}: FetchNodeReportHistoryServiceParams): Promise { + const params = queryString.stringify( + { + reportParamQuery: { + query, + pagination: getPaginationParams({ page, perPage, sortOption }), + }, + }, + { arrayFormat: 'repeat', allowDots: true } + ); + return axios + .get(`/v2/reports/node/configurations/${id}/history?${params}`) + .then((response) => { + const snapshots = response.data?.reportSnapshots ?? []; + return snapshots.filter(isConfiguredReportSnapshot); + }); +} // GetMyNodeReportHistory +export function fetchMyNodeReportHistory({ + id, + query, + page, + perPage, + sortOption, +}: FetchNodeReportHistoryServiceParams): Promise { + const params = queryString.stringify( + { + reportParamQuery: { + query, + pagination: getPaginationParams({ page, perPage, sortOption }), + }, + }, + { arrayFormat: 'repeat', allowDots: true } + ); + return axios + .get(`/v2/reports/node/configurations/${id}/my-history?${params}`) + .then((response) => { + const snapshots = response.data?.reportSnapshots ?? []; + return snapshots.filter(isConfiguredReportSnapshot); + }); +} // Job management From a4db432b855e98e1cf751649804d2db5e126befe Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:36:15 +0200 Subject: [PATCH 267/638] ROX-33973: UI support for the new AuthProviders with ACM roles (#22348) --- .../AuthProviders/AuthProviderForm.tsx | 84 +++++++++++++++---- .../AuthProviders/ConfigurationFormFields.tsx | 52 +++++++++++- .../AuthProviders/authProviders.utils.ts | 18 +++- .../platform/src/constants/accessControl.ts | 2 + .../src/services/AuthService/AuthService.ts | 10 ++- 5 files changed, 145 insertions(+), 21 deletions(-) diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx index 0a8f6e1c46578..58a1e051e6a98 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx @@ -93,7 +93,9 @@ function testModeSupported(provider) { provider.type === 'auth0' || provider.type === 'oidc' || provider.type === 'saml' || - provider.type === 'openshift' + provider.type === 'openshift' || + provider.type === 'openshift-with-acm-roles' || + provider.type === 'oidc-with-acm-roles' ); } @@ -166,7 +168,7 @@ function AuthProviderForm({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .when('type', { - is: 'oidc', + is: (type) => type === 'oidc' || type === 'oidc-with-acm-roles', then: (configSchema) => configSchema.shape({ client_id: yup.string().required('A client ID is required.'), @@ -235,6 +237,14 @@ function AuthProviderForm({ configSchema.shape({ audience: yup.string().required('An audience is required.'), }), + }) + .when('type', { + is: 'openshift-with-acm-roles', + then: (configSchema) => + configSchema.shape({ + client_name: yup.string().required('A client name is required.'), + client_secret: yup.string().required('A client secret is required.'), + }), }), }); @@ -523,7 +533,8 @@ function AuthProviderForm({

    - {selectedAuthProvider.type === 'oidc' && ( + {(selectedAuthProvider.type === 'oidc' || + selectedAuthProvider.type === 'oidc-with-acm-roles') && ( )} - {selectedAuthProvider.type === 'oidc' && ( + {(selectedAuthProvider.type === 'oidc' || + selectedAuthProvider.type === 'oidc-with-acm-roles') && ( )} - - - + {selectedAuthProvider.type !== 'openshift-with-acm-roles' && + selectedAuthProvider.type !== 'oidc-with-acm-roles' && ( + + + + )} + {(selectedAuthProvider.type === 'openshift-with-acm-roles' || + selectedAuthProvider.type === 'oidc-with-acm-roles') && ( +
    + +

    + This requires Red Hat Advanced Cluster Management deployed on + the cluster where the central entity of Red Hat Advanced Cluster + Security is deployed. +

    +

    + Additionally, Red Hat Advanced Cluster Management must have + 'fine-grained role-based access control' activated. +

    +

    + For further information on the role configuration, please refer + to the Red Hat Advanced Cluster Management documentation + (version >= 2.16), area 'Secure clusters', section + 'Securing clusters', subsections 'Fine-grained + role-based access control for virtual machines', + 'Enabling fine-grained role-based access control for + virtualization', and the subsections related to + 'MulticlusterRoleAssignment'. +

    +

    + The OpenShift ClusterRoles created to configure the Red Hat + Advanced Cluster Security access control need to have the label + 'clusterview.open-cluster-management.io/discoverable' + present and set to 'true'. +

    +
    +
    + )} diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/ConfigurationFormFields.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/ConfigurationFormFields.tsx index 7bb46917bd468..41994aa452472 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/ConfigurationFormFields.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/ConfigurationFormFields.tsx @@ -176,7 +176,7 @@ function ConfigurationFormFields({ )} - {type === 'oidc' && ( + {(type === 'oidc' || type === 'oidc-with-acm-roles') && ( <> @@ -738,6 +738,56 @@ function ConfigurationFormFields({ )} + {type === 'openshift-with-acm-roles' && ( + <> + + + + + + + + The name of the client registered in ACM for role + resolution + + + + + + + + + + + + + + The client secret for authenticating with ACM + + + + + + + + )} ); } diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts index 603dd7cae4583..bce738d2ff708 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts @@ -19,7 +19,7 @@ export function transformInitialValues( // TODO-ivan: eventually logic for different auth provider type should live // with the form component that renders form for the corresponding auth provider // type, probably makes sense to refactor after moving away from redux-form - if (initialValues.type === 'oidc') { + if (initialValues.type === 'oidc' || initialValues.type === 'oidc-with-acm-roles') { const alteredConfig = { ...initialValues.config }; // backend doesn't return the exact value for the client secret for the security reasons, @@ -45,6 +45,18 @@ export function transformInitialValues( config: alteredConfig, }; } + if (initialValues.type === 'openshift-with-acm-roles') { + const alteredConfig = { ...initialValues.config }; + + // backend doesn't return the exact value for the client secret for security reasons + // clean-up obfuscated value if any as we don't need to show it + alteredConfig.client_secret = ''; + + return { + ...initialValues, + config: alteredConfig, + }; + } if (initialValues.type === 'saml') { const alteredConfig = { ...initialValues.config }; // unless static config values are present, assume dynamic configuration is selected @@ -61,7 +73,7 @@ function populateDefaultValues(authProvider: AuthProvider): AuthProvider { const newInitialValues: DisplayedAuthProvider = { ...authProvider }; newInitialValues.uiEndpoint = window.location.host; newInitialValues.enabled = true; - if (authProvider.type === 'oidc') { + if (authProvider.type === 'oidc' || authProvider.type === 'oidc-with-acm-roles') { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore newInitialValues.config = { @@ -116,7 +128,7 @@ export function transformValuesBeforeSaving( | Group[] | undefined > { - if (values.type === 'oidc') { + if (values.type === 'oidc' || values.type === 'oidc-with-acm-roles') { const alteredConfig = { ...(values.config as AuthProviderConfig) }; // if client secret is stored on the backend and user didn't enter any value, diff --git a/ui/apps/platform/src/constants/accessControl.ts b/ui/apps/platform/src/constants/accessControl.ts index 167886222f398..a3d3fcda998f0 100644 --- a/ui/apps/platform/src/constants/accessControl.ts +++ b/ui/apps/platform/src/constants/accessControl.ts @@ -14,6 +14,8 @@ export const authProviderLabels = { userpki: 'User Certificates', iap: 'Google IAP', openshift: 'OpenShift Auth', + 'openshift-with-acm-roles': 'OpenShift Auth with ACM Roles', + 'oidc-with-acm-roles': 'OpenID Connect with ACM Roles', }; export const oidcCallbackModes = [ diff --git a/ui/apps/platform/src/services/AuthService/AuthService.ts b/ui/apps/platform/src/services/AuthService/AuthService.ts index 33f3f5c2e1114..14aa760c44d68 100644 --- a/ui/apps/platform/src/services/AuthService/AuthService.ts +++ b/ui/apps/platform/src/services/AuthService/AuthService.ts @@ -46,7 +46,15 @@ export class AuthHttpError extends Error { isInvalidAuth = (): boolean => this.code === 401; } -export type AuthProviderType = 'auth0' | 'oidc' | 'saml' | 'userpki' | 'iap' | 'openshift'; +export type AuthProviderType = + | 'auth0' + | 'oidc' + | 'saml' + | 'userpki' + | 'iap' + | 'openshift' + | 'openshift-with-acm-roles' + | 'oidc-with-acm-roles'; export type AuthProviderConfig = Record< string, From 8fdcf6edd904cbd3de2a0da3873539bcf7b20f25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:37:21 +0200 Subject: [PATCH 268/638] chore(deps): bump stackrox/actions/release/scan-image-vulnerabilities from 1.0.39 to 1.0.45 (#22361) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scan-image-vulnerabilities.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scan-image-vulnerabilities.yaml b/.github/workflows/scan-image-vulnerabilities.yaml index d973a73cdfa7f..9f17c2138f0e7 100644 --- a/.github/workflows/scan-image-vulnerabilities.yaml +++ b/.github/workflows/scan-image-vulnerabilities.yaml @@ -83,7 +83,7 @@ jobs: echo "tag=${tag}" >> "${GITHUB_OUTPUT}" - name: Scan image vulnerabilities - uses: stackrox/actions/release/scan-image-vulnerabilities@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/release/scan-image-vulnerabilities@v1 + uses: stackrox/actions/release/scan-image-vulnerabilities@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/release/scan-image-vulnerabilities@v1 id: scan-image with: image: rhacs-eng/${{ matrix.image }} @@ -141,7 +141,7 @@ jobs: echo "tag=${tag}" >> "${GITHUB_OUTPUT}" - name: Scan image vulnerabilities - uses: stackrox/actions/release/scan-image-vulnerabilities@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/release/scan-image-vulnerabilities@v1 + uses: stackrox/actions/release/scan-image-vulnerabilities@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/release/scan-image-vulnerabilities@v1 id: scan-image with: image: rhacs-eng/${{ matrix.image }} From a387985ee79d70b7b24998e41a305f139fde14c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:38:02 +0200 Subject: [PATCH 269/638] chore(deps): bump gacts/run-and-post-run from 1.4.4 to 1.4.5 in /.github/actions/job-preamble (#22731) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/job-preamble/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/job-preamble/action.yaml b/.github/actions/job-preamble/action.yaml index 9080746c11019..8bb2eb47b1154 100644 --- a/.github/actions/job-preamble/action.yaml +++ b/.github/actions/job-preamble/action.yaml @@ -157,7 +157,7 @@ runs: echo "container_runtime=$([[ -f /.dockerenv ]] && echo container || echo None)" >> "$GITHUB_OUTPUT" - name: Record job info - uses: gacts/run-and-post-run@598d7a875d5620e0457490555b5e18e46082aa47 # v1.4.0 + uses: gacts/run-and-post-run@fc6503862aba998e2f2123225ad63c09a92ed46e # v1.4.0 id: record_job_info env: GCP_SERVICE_ACCOUNT_STACKROX_CI: ${{ inputs.gcp-account }} From 7cb64f36b6e210c81274c33eb0c62f89bc1fc784 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Wed, 9 Sep 2026 20:31:48 +0200 Subject: [PATCH 270/638] chore(release): Add 4.10.8 to scanner updater configuration (#22686) Co-authored-by: bradr5 --- scanner/updater/version/RELEASE_VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index e7beb020a049a..3e970658b5816 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -14,6 +14,7 @@ 4.10.5 4.10.6 4.10.7 +4.10.8 4.11.0 4.11.1 4.11.2 From 57bad3f7d7588252deabee5942dd1b6daccfbb08 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Wed, 9 Sep 2026 20:31:58 +0200 Subject: [PATCH 271/638] chore(release): Add 4.11.4 to scanner updater configuration (#22685) Co-authored-by: bradr5 --- scanner/updater/version/RELEASE_VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index 3e970658b5816..f2688bca0a8fd 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -19,6 +19,7 @@ 4.11.1 4.11.2 4.11.3 +4.11.4 4.4.0 4.4.1 4.4.2 From b606b2b6dac60c0d3e6bf9e231afe3485a7a5944 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:00:37 +0200 Subject: [PATCH 272/638] fix(ci): Match scanner-v4-matcher restart log names (#22700) --- .../fixtures/check-restart-logs-output.txt | 2 +- tests/e2e/bats/junit_report_pod_restarts.bats | 21 +++++++++++++++++-- tests/e2e/lib.sh | 5 +++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/e2e/bats/fixtures/check-restart-logs-output.txt b/tests/e2e/bats/fixtures/check-restart-logs-output.txt index a9d54d0dbc2b3..8149952e93bdd 100644 --- a/tests/e2e/bats/fixtures/check-restart-logs-output.txt +++ b/tests/e2e/bats/fixtures/check-restart-logs-output.txt @@ -3,6 +3,6 @@ collector-11111-node-inventory-previous.log copied to Artifacts Example for DaemonSet container collector-22222-node-inventory-previous.log copied to Artifacts Example for deployment container -scanner-v4-111111111-11111-matcher-previous.log copied to Artifacts +scanner-v4-matcher-111111111-11111-matcher-previous.log copied to Artifacts Example for unknown pod unknown-pod-111111111-11111-container-previous.log copied to Artifacts diff --git a/tests/e2e/bats/junit_report_pod_restarts.bats b/tests/e2e/bats/junit_report_pod_restarts.bats index 26e38df35634e..1a0022e0b7643 100644 --- a/tests/e2e/bats/junit_report_pod_restarts.bats +++ b/tests/e2e/bats/junit_report_pod_restarts.bats @@ -2,6 +2,23 @@ load "../../../scripts/test_helpers.bats" +# Real previous-log names from collect-service-logs.sh: --previous.log. +# Matcher/indexer pods are scanner-v4-matcher-* / scanner-v4-indexer-*, not scanner-v4-*. +@test "POD_CONTAINERS_MAP matches scanner-v4-matcher and indexer previous logs" { + source "${BATS_TEST_DIRNAME}/../lib.sh" + + local matcher_re indexer_re + matcher_re="${POD_CONTAINERS_MAP["pod: scanner-v4-matcher - container: matcher"]}" + indexer_re="${POD_CONTAINERS_MAP["pod: scanner-v4-indexer - container: indexer"]}" + + [[ -n "${matcher_re}" ]] + [[ -n "${indexer_re}" ]] + [[ "scanner-v4-matcher-b49db6cbf-kg6p8-matcher-previous.log" =~ ${matcher_re} ]] + [[ "scanner-v4-indexer-65d57884b9-c8dt2-indexer-previous.log" =~ ${indexer_re} ]] + # Combined scanner-v4-- names must not match (those pods do not exist). + [[ ! "scanner-v4-111111111-11111-matcher-previous.log" =~ ${matcher_re} ]] +} + @test "junit_report_pod_restarts - clean" { source "${BATS_TEST_DIRNAME}/../lib.sh" @@ -37,7 +54,7 @@ load "../../../scripts/test_helpers.bats" POD_CONTAINERS_MAP=() # Deployment failure - POD_CONTAINERS_MAP["pod: scanner-v4 - container: matcher"]="scanner-v4-[A-Za-z0-9]+-[A-Za-z0-9]+-matcher-previous.log" + POD_CONTAINERS_MAP["pod: scanner-v4-matcher - container: matcher"]="scanner-v4-matcher-[A-Za-z0-9]+-[A-Za-z0-9]+-matcher-previous.log" # DaemonSet failure (with two pods failed) POD_CONTAINERS_MAP["pod: collector - container: node-inventory"]="collector-[A-Za-z0-9]+-node-inventory-previous.log" # No failure @@ -49,7 +66,7 @@ load "../../../scripts/test_helpers.bats" # 1 without failure and 3 failed (4 in output, but 1 de-duplicated) [ "${#lines[@]}" -eq 4 ] assert_line 'Success: Check unexpected pod restarts pod: sensor - container: sensor' - assert_line 'Fail: Check unexpected pod restarts pod: scanner-v4 - container: matcher' + assert_line 'Fail: Check unexpected pod restarts pod: scanner-v4-matcher - container: matcher' assert_line 'Fail: Check unexpected pod restarts pod: collector - container: node-inventory' assert_line 'Fail: Check unexpected pod restarts unknown' } diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index c2fb2ae67f61c..7a4ee7e231f69 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -42,8 +42,9 @@ POD_CONTAINERS_MAP["pod: config-controller - container: manager"]="config-contro POD_CONTAINERS_MAP["pod: scanner - container: scanner"]="scanner-[A-Za-z0-9]+-[A-Za-z0-9]+-scanner-previous.log" POD_CONTAINERS_MAP["pod: scanner-db - container: init-db"]="scanner-db-[A-Za-z0-9]+-[A-Za-z0-9]+-init-db-previous.log" POD_CONTAINERS_MAP["pod: scanner-db - container: db"]="scanner-db-[A-Za-z0-9]+-[A-Za-z0-9]+-db-previous.log" -POD_CONTAINERS_MAP["pod: scanner-v4 - container: matcher"]="scanner-v4-[A-Za-z0-9]+-[A-Za-z0-9]+-matcher-previous.log" -POD_CONTAINERS_MAP["pod: scanner-v4 - container: indexer"]="scanner-v4-[A-Za-z0-9]+-[A-Za-z0-9]+-indexer-previous.log" +# Matcher and indexer are separate deployments: scanner-v4-matcher-*, scanner-v4-indexer-*. +POD_CONTAINERS_MAP["pod: scanner-v4-matcher - container: matcher"]="scanner-v4-matcher-[A-Za-z0-9]+-[A-Za-z0-9]+-matcher-previous.log" +POD_CONTAINERS_MAP["pod: scanner-v4-indexer - container: indexer"]="scanner-v4-indexer-[A-Za-z0-9]+-[A-Za-z0-9]+-indexer-previous.log" POD_CONTAINERS_MAP["pod: scanner-v4-db - container: init-db"]="scanner-v4-db-[A-Za-z0-9]+-[A-Za-z0-9]+-init-db-previous.log" POD_CONTAINERS_MAP["pod: scanner-v4-db - container: db"]="scanner-v4-db-[A-Za-z0-9]+-[A-Za-z0-9]+-db-previous.log" POD_CONTAINERS_MAP["pod: sensor - container: sensor"]="sensor-[A-Za-z0-9]+-[A-Za-z0-9]+-sensor-previous.log" From 6d4ca422a9ebfdff18828a12c65f581369bfb93b Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:16:27 -0400 Subject: [PATCH 273/638] ROX-34928: Add Known Ransomware Campaign search field (#22455) --- .../cluster/datastore/store/postgres/store.go | 5 +- central/cve/converter/utils/convert_utils.go | 23 ++++---- .../cve/converter/utils/convert_utils_v2.go | 27 ++++----- .../v2/datastore/store/postgres/store.go | 5 +- .../node/datastore/store/postgres/store.go | 5 +- central/graphql/resolvers/generated.go | 6 ++ .../imagev2/datastore/store/postgres/store.go | 2 + .../scheduler/v2/reportgenerator/csv_gen.go | 10 +++- .../v2/reportgenerator/report_gen_impl.go | 2 + .../scheduler/v2/reportgenerator/types.go | 43 +++++++------- .../cve/v2/datastore/store/postgres/store.go | 5 +- generated/api/v1/node_service.swagger.json | 3 + generated/storage/cve.pb.go | 25 +++++--- generated/storage/cve_vtproto.pb.go | 57 +++++++++++++++++++ pkg/nodes/converter/convert_utils_test.go | 1 + pkg/postgres/schema/cluster_cves.go | 27 ++++----- pkg/postgres/schema/image_cves_v2.go | 51 +++++++++-------- pkg/postgres/schema/node_cves.go | 31 +++++----- pkg/postgres/schema/virtual_machine_cvev2.go | 41 ++++++------- pkg/search/options.go | 39 ++++++------- proto/storage/cve.proto | 1 + proto/storage/proto.lock | 5 ++ 22 files changed, 265 insertions(+), 149 deletions(-) diff --git a/central/cve/cluster/datastore/store/postgres/store.go b/central/cve/cluster/datastore/store/postgres/store.go index 363da067cdea3..ef90c0d4b9e6b 100644 --- a/central/cve/cluster/datastore/store/postgres/store.go +++ b/central/cve/cluster/datastore/store/postgres/store.go @@ -106,6 +106,7 @@ func insertIntoClusterCves(batch *pgx.Batch, obj *storage.ClusterCVE) error { protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), @@ -115,7 +116,7 @@ func insertIntoClusterCves(batch *pgx.Batch, obj *storage.ClusterCVE) error { serialized, } - finalStr := "INSERT INTO cluster_cves (Id, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, Cvss, Severity, ImpactScore, Snoozed, SnoozeExpiry, Type, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Snoozed = EXCLUDED.Snoozed, SnoozeExpiry = EXCLUDED.SnoozeExpiry, Type = EXCLUDED.Type, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO cluster_cves (Id, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign, Cvss, Severity, ImpactScore, Snoozed, SnoozeExpiry, Type, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign = EXCLUDED.CveBaseInfo_KnownRansomwareCampaign, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Snoozed = EXCLUDED.Snoozed, SnoozeExpiry = EXCLUDED.SnoozeExpiry, Type = EXCLUDED.Type, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -128,6 +129,7 @@ var copyColsClusterCves = []string{ "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", "cvebaseinfo_cisakev", + "cvebaseinfo_knownransomwarecampaign", "cvss", "severity", "impactscore", @@ -174,6 +176,7 @@ func copyFromClusterCves(ctx context.Context, s pgSearch.Deleter, tx *postgres.T protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), diff --git a/central/cve/converter/utils/convert_utils.go b/central/cve/converter/utils/convert_utils.go index 64f0284b2631a..a910783666165 100644 --- a/central/cve/converter/utils/convert_utils.go +++ b/central/cve/converter/utils/convert_utils.go @@ -292,17 +292,18 @@ func EmbeddedVulnerabilityToImageCVE(os string, from *storage.EmbeddedVulnerabil Id: cve.ID(from.GetCve(), os), OperatingSystem: os, CveBaseInfo: &storage.CVEInfo{ - Cve: from.GetCve(), - Summary: from.GetSummary(), - Link: from.GetLink(), - PublishedOn: from.GetPublishedOn(), - CreatedAt: from.GetFirstSystemOccurrence(), - LastModified: from.GetLastModified(), - CvssV2: from.GetCvssV2(), - CvssV3: from.GetCvssV3(), - Epss: from.GetEpss(), - Exploit: from.GetExploit(), - CisaKev: from.GetCisaKev(), + Cve: from.GetCve(), + Summary: from.GetSummary(), + Link: from.GetLink(), + PublishedOn: from.GetPublishedOn(), + CreatedAt: from.GetFirstSystemOccurrence(), + LastModified: from.GetLastModified(), + CvssV2: from.GetCvssV2(), + CvssV3: from.GetCvssV3(), + Epss: from.GetEpss(), + Exploit: from.GetExploit(), + CisaKev: from.GetCisaKev(), + KnownRansomwareCampaign: from.GetExploit() != nil && from.GetExploit().GetKnownRansomwareCampaignUse() == "Known", }, Cvss: from.GetCvss(), Nvdcvss: nvdCvss, diff --git a/central/cve/converter/utils/convert_utils_v2.go b/central/cve/converter/utils/convert_utils_v2.go index 9a2b918bf11df..c02e2f9b3b4d2 100644 --- a/central/cve/converter/utils/convert_utils_v2.go +++ b/central/cve/converter/utils/convert_utils_v2.go @@ -87,19 +87,20 @@ func EmbeddedVulnerabilityToImageCVEV2(imageID string, componentID string, index Id: cveID, ComponentId: componentID, CveBaseInfo: &storage.CVEInfo{ - Cve: from.GetCve(), - Summary: from.GetSummary(), - Link: from.GetLink(), - PublishedOn: from.GetPublishedOn(), - CreatedAt: from.GetFirstSystemOccurrence(), - LastModified: from.GetLastModified(), - CvssV2: from.GetCvssV2(), - CvssV3: from.GetCvssV3(), - CvssMetrics: from.GetCvssMetrics(), - Epss: from.GetEpss(), - Exploit: from.GetExploit(), - CisaKev: from.GetExploit() != nil, - ScoreVersion: scoreVersion, + Cve: from.GetCve(), + Summary: from.GetSummary(), + Link: from.GetLink(), + PublishedOn: from.GetPublishedOn(), + CreatedAt: from.GetFirstSystemOccurrence(), + LastModified: from.GetLastModified(), + CvssV2: from.GetCvssV2(), + CvssV3: from.GetCvssV3(), + CvssMetrics: from.GetCvssMetrics(), + Epss: from.GetEpss(), + Exploit: from.GetExploit(), + CisaKev: from.GetExploit() != nil, + KnownRansomwareCampaign: from.GetExploit() != nil && from.GetExploit().GetKnownRansomwareCampaignUse() == "Known", + ScoreVersion: scoreVersion, }, Cvss: from.GetCvss(), Nvdcvss: nvdCvss, diff --git a/central/cve/image/v2/datastore/store/postgres/store.go b/central/cve/image/v2/datastore/store/postgres/store.go index c91451c625918..d76c93ecd28f4 100644 --- a/central/cve/image/v2/datastore/store/postgres/store.go +++ b/central/cve/image/v2/datastore/store/postgres/store.go @@ -108,6 +108,7 @@ func insertIntoImageCvesV2(batch *pgx.Batch, obj *storage.ImageCVEV2) error { protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), @@ -125,7 +126,7 @@ func insertIntoImageCvesV2(batch *pgx.Batch, obj *storage.ImageCVEV2) error { serialized, } - finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, Advisory_Name, Advisory_Link, ImageIdV2, FixAvailableTimestamp, Origin, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, ImageIdV2 = EXCLUDED.ImageIdV2, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, Origin = EXCLUDED.Origin, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO image_cves_v2 (Id, ImageId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign, Cvss, Severity, ImpactScore, Nvdcvss, FirstImageOccurrence, State, IsFixable, FixedBy, ComponentId, Advisory_Name, Advisory_Link, ImageIdV2, FixAvailableTimestamp, Origin, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, ImageId = EXCLUDED.ImageId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign = EXCLUDED.CveBaseInfo_KnownRansomwareCampaign, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, FirstImageOccurrence = EXCLUDED.FirstImageOccurrence, State = EXCLUDED.State, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, ComponentId = EXCLUDED.ComponentId, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, ImageIdV2 = EXCLUDED.ImageIdV2, FixAvailableTimestamp = EXCLUDED.FixAvailableTimestamp, Origin = EXCLUDED.Origin, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -139,6 +140,7 @@ var copyColsImageCvesV2 = []string{ "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", "cvebaseinfo_cisakev", + "cvebaseinfo_knownransomwarecampaign", "cvss", "severity", "impactscore", @@ -194,6 +196,7 @@ func copyFromImageCvesV2(ctx context.Context, s pgSearch.Deleter, tx *postgres.T protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), diff --git a/central/cve/node/datastore/store/postgres/store.go b/central/cve/node/datastore/store/postgres/store.go index 3eecc6e7d7e97..d60a5591f2700 100644 --- a/central/cve/node/datastore/store/postgres/store.go +++ b/central/cve/node/datastore/store/postgres/store.go @@ -106,6 +106,7 @@ func insertIntoNodeCves(batch *pgx.Batch, obj *storage.NodeCVE) error { protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetOperatingSystem(), obj.GetCvss(), obj.GetSeverity(), @@ -117,7 +118,7 @@ func insertIntoNodeCves(batch *pgx.Batch, obj *storage.NodeCVE) error { serialized, } - finalStr := "INSERT INTO node_cves (Id, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, OperatingSystem, Cvss, Severity, ImpactScore, Snoozed, SnoozeExpiry, Orphaned, OrphanedTime, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, OperatingSystem = EXCLUDED.OperatingSystem, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Snoozed = EXCLUDED.Snoozed, SnoozeExpiry = EXCLUDED.SnoozeExpiry, Orphaned = EXCLUDED.Orphaned, OrphanedTime = EXCLUDED.OrphanedTime, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO node_cves (Id, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign, OperatingSystem, Cvss, Severity, ImpactScore, Snoozed, SnoozeExpiry, Orphaned, OrphanedTime, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign = EXCLUDED.CveBaseInfo_KnownRansomwareCampaign, OperatingSystem = EXCLUDED.OperatingSystem, Cvss = EXCLUDED.Cvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Snoozed = EXCLUDED.Snoozed, SnoozeExpiry = EXCLUDED.SnoozeExpiry, Orphaned = EXCLUDED.Orphaned, OrphanedTime = EXCLUDED.OrphanedTime, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -130,6 +131,7 @@ var copyColsNodeCves = []string{ "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", "cvebaseinfo_cisakev", + "cvebaseinfo_knownransomwarecampaign", "operatingsystem", "cvss", "severity", @@ -178,6 +180,7 @@ func copyFromNodeCves(ctx context.Context, s pgSearch.Deleter, tx *postgres.Tx, protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetOperatingSystem(), obj.GetCvss(), obj.GetSeverity(), diff --git a/central/graphql/resolvers/generated.go b/central/graphql/resolvers/generated.go index ad8f17ae48311..38b997da3faf7 100644 --- a/central/graphql/resolvers/generated.go +++ b/central/graphql/resolvers/generated.go @@ -199,6 +199,7 @@ func registerGeneratedTypes(builder generator.SchemaBuilder) { "cvssV3: CVSSV3", "epss: EPSS", "exploit: Exploit", + "knownRansomwareCampaign: Boolean!", "lastModified: Time", "link: String!", "publishedOn: Time", @@ -3436,6 +3437,11 @@ func (resolver *cVEInfoResolver) Exploit(ctx context.Context) (*exploitResolver, return resolver.root.wrapExploit(value, true, nil) } +func (resolver *cVEInfoResolver) KnownRansomwareCampaign(ctx context.Context) bool { + value := resolver.data.GetKnownRansomwareCampaign() + return value +} + func (resolver *cVEInfoResolver) LastModified(ctx context.Context) (*graphql.Time, error) { value := resolver.data.GetLastModified() return protocompat.ConvertTimestampToGraphqlTimeOrError(value) diff --git a/central/imagev2/datastore/store/postgres/store.go b/central/imagev2/datastore/store/postgres/store.go index 87c0382422b44..80d94e4117421 100644 --- a/central/imagev2/datastore/store/postgres/store.go +++ b/central/imagev2/datastore/store/postgres/store.go @@ -321,6 +321,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", "cvebaseinfo_cisakev", + "cvebaseinfo_knownransomwarecampaign", "cvss", "severity", "impactscore", @@ -364,6 +365,7 @@ func copyFromImageComponentV2Cves(ctx context.Context, tx *postgres.Tx, iTimesta protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetCvss(), obj.GetSeverity(), obj.GetImpactScore(), diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 54f6c9fb6cf8e..8d9b6419ce731 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -110,7 +110,7 @@ func formatCol() []string { if features.KnownExploitedVulnerabilities.Enabled() { csvHeaderCols = append(csvHeaderCols[:0:0], csvHeader...) epssIdx := slices.Index(csvHeaderCols, "EPSS Probability Percentage") - csvHeaderCols = slices.Insert(csvHeaderCols, epssIdx+1, "CISA KEV") + csvHeaderCols = slices.Insert(csvHeaderCols, epssIdx+1, "CISA KEV", "Known Ransomware Campaign") } return csvHeaderCols } @@ -157,7 +157,13 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt } else { cisaKev = "Not Available" } - row = append(row, cisaKev) + var knownRansomware string + if r.GetKnownRansomwareCampaign() != nil { + knownRansomware = strconv.FormatBool(*r.GetKnownRansomwareCampaign()) + } else { + knownRansomware = "Not Available" + } + row = append(row, cisaKev, knownRansomware) } row = append(row, r.GetDiscoveredAtImage(), diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index 47f6a4f54525b..ae8fc26443bfe 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -757,6 +757,7 @@ func getSelectsWatchedImages() []*v1.QuerySelect { search.NewQuerySelect(search.FirstImageOccurrenceTimestamp).Proto(), search.NewQuerySelect(search.EPSSProbablity).Proto(), search.NewQuerySelect(search.CisaKev).Proto(), + search.NewQuerySelect(search.KnownRansomwareCampaign).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), search.NewQuerySelect(search.CVEOrigin).Proto(), @@ -782,6 +783,7 @@ func getSelectsDeployedImages() []*v1.QuerySelect { search.NewQuerySelect(search.DeploymentName).Proto(), search.NewQuerySelect(search.EPSSProbablity).Proto(), search.NewQuerySelect(search.CisaKev).Proto(), + search.NewQuerySelect(search.KnownRansomwareCampaign).Proto(), search.NewQuerySelect(search.AdvisoryName).Proto(), search.NewQuerySelect(search.AdvisoryLink).Proto(), search.NewQuerySelect(search.CVEOrigin).Proto(), diff --git a/central/reports/scheduler/v2/reportgenerator/types.go b/central/reports/scheduler/v2/reportgenerator/types.go index ff2450ffbe716..1b3b53035cef1 100644 --- a/central/reports/scheduler/v2/reportgenerator/types.go +++ b/central/reports/scheduler/v2/reportgenerator/types.go @@ -31,25 +31,26 @@ type reportEmailSubjectFormat struct { // ImageCVEQueryResponse contains the fields of report query response type ImageCVEQueryResponse struct { - Cluster *string `db:"cluster"` - Namespace *string `db:"namespace"` - Deployment *string `db:"deployment"` - Image *string `db:"image"` - Component *string `db:"component"` - ComponentVersion *string `db:"component_version"` - CVEID *string `db:"cve_id"` - CVE *string `db:"cve"` - Fixable *bool `db:"fixable"` - FixedByVersion *string `db:"fixed_by"` - Severity *storage.VulnerabilitySeverity `db:"severity"` - CVSS *float64 `db:"cvss"` - NVDCVSS *float64 `db:"nvd_cvss"` - EPSSProbability *float64 `db:"epss_probability"` - CisaKev *bool `db:"cisa_kev"` - DiscoveredAtImage *time.Time `db:"first_image_occurrence_timestamp"` - AdvisoryName *string `db:"advisory_name"` - AdvisoryLink *string `db:"advisory_link"` - Origin *storage.VulnOrigin `db:"cve_origin"` + Cluster *string `db:"cluster"` + Namespace *string `db:"namespace"` + Deployment *string `db:"deployment"` + Image *string `db:"image"` + Component *string `db:"component"` + ComponentVersion *string `db:"component_version"` + CVEID *string `db:"cve_id"` + CVE *string `db:"cve"` + Fixable *bool `db:"fixable"` + FixedByVersion *string `db:"fixed_by"` + Severity *storage.VulnerabilitySeverity `db:"severity"` + CVSS *float64 `db:"cvss"` + NVDCVSS *float64 `db:"nvd_cvss"` + EPSSProbability *float64 `db:"epss_probability"` + CisaKev *bool `db:"cisa_kev"` + KnownRansomwareCampaign *bool `db:"known_ransomware_campaign"` + DiscoveredAtImage *time.Time `db:"first_image_occurrence_timestamp"` + AdvisoryName *string `db:"advisory_name"` + AdvisoryLink *string `db:"advisory_link"` + Origin *storage.VulnOrigin `db:"cve_origin"` Link string } @@ -160,6 +161,10 @@ func (res *ImageCVEQueryResponse) GetCisaKev() *bool { return res.CisaKev } +func (res *ImageCVEQueryResponse) GetKnownRansomwareCampaign() *bool { + return res.KnownRansomwareCampaign +} + func (res *ImageCVEQueryResponse) GetAdvisoryName() string { if res.AdvisoryName == nil { return "Not Available" diff --git a/central/virtualmachine/cve/v2/datastore/store/postgres/store.go b/central/virtualmachine/cve/v2/datastore/store/postgres/store.go index 8c9c4877e2b2f..9a817e4e199b1 100644 --- a/central/virtualmachine/cve/v2/datastore/store/postgres/store.go +++ b/central/virtualmachine/cve/v2/datastore/store/postgres/store.go @@ -109,6 +109,7 @@ func insertIntoVirtualMachineCvev2(batch *pgx.Batch, obj *storage.VirtualMachine protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetPreferredCvss(), obj.GetSeverity(), obj.GetImpactScore(), @@ -121,7 +122,7 @@ func insertIntoVirtualMachineCvev2(batch *pgx.Batch, obj *storage.VirtualMachine serialized, } - finalStr := "INSERT INTO virtual_machine_cvev2 (Id, VmV2Id, VmComponentId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, PreferredCvss, Severity, ImpactScore, Nvdcvss, IsFixable, FixedBy, EpssProbability, Advisory_Name, Advisory_Link, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, VmV2Id = EXCLUDED.VmV2Id, VmComponentId = EXCLUDED.VmComponentId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, PreferredCvss = EXCLUDED.PreferredCvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, EpssProbability = EXCLUDED.EpssProbability, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, serialized = EXCLUDED.serialized" + finalStr := "INSERT INTO virtual_machine_cvev2 (Id, VmV2Id, VmComponentId, CveBaseInfo_Cve, CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign, PreferredCvss, Severity, ImpactScore, Nvdcvss, IsFixable, FixedBy, EpssProbability, Advisory_Name, Advisory_Link, serialized) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) ON CONFLICT(Id) DO UPDATE SET Id = EXCLUDED.Id, VmV2Id = EXCLUDED.VmV2Id, VmComponentId = EXCLUDED.VmComponentId, CveBaseInfo_Cve = EXCLUDED.CveBaseInfo_Cve, CveBaseInfo_PublishedOn = EXCLUDED.CveBaseInfo_PublishedOn, CveBaseInfo_CreatedAt = EXCLUDED.CveBaseInfo_CreatedAt, CveBaseInfo_Epss_EpssProbability = EXCLUDED.CveBaseInfo_Epss_EpssProbability, CveBaseInfo_CisaKev = EXCLUDED.CveBaseInfo_CisaKev, CveBaseInfo_KnownRansomwareCampaign = EXCLUDED.CveBaseInfo_KnownRansomwareCampaign, PreferredCvss = EXCLUDED.PreferredCvss, Severity = EXCLUDED.Severity, ImpactScore = EXCLUDED.ImpactScore, Nvdcvss = EXCLUDED.Nvdcvss, IsFixable = EXCLUDED.IsFixable, FixedBy = EXCLUDED.FixedBy, EpssProbability = EXCLUDED.EpssProbability, Advisory_Name = EXCLUDED.Advisory_Name, Advisory_Link = EXCLUDED.Advisory_Link, serialized = EXCLUDED.serialized" batch.Queue(finalStr, values...) return nil @@ -136,6 +137,7 @@ var copyColsVirtualMachineCvev2 = []string{ "cvebaseinfo_createdat", "cvebaseinfo_epss_epssprobability", "cvebaseinfo_cisakev", + "cvebaseinfo_knownransomwarecampaign", "preferredcvss", "severity", "impactscore", @@ -187,6 +189,7 @@ func copyFromVirtualMachineCvev2(ctx context.Context, s pgSearch.Deleter, tx *po protocompat.NilOrTime(obj.GetCveBaseInfo().GetCreatedAt()), obj.GetCveBaseInfo().GetEpss().GetEpssProbability(), obj.GetCveBaseInfo().GetCisaKev(), + obj.GetCveBaseInfo().GetKnownRansomwareCampaign(), obj.GetPreferredCvss(), obj.GetSeverity(), obj.GetImpactScore(), diff --git a/generated/api/v1/node_service.swagger.json b/generated/api/v1/node_service.swagger.json index a89070cc04e43..a50f3bc54ff87 100644 --- a/generated/api/v1/node_service.swagger.json +++ b/generated/api/v1/node_service.swagger.json @@ -307,6 +307,9 @@ }, "cisaKev": { "type": "boolean" + }, + "knownRansomwareCampaign": { + "type": "boolean" } } }, diff --git a/generated/storage/cve.pb.go b/generated/storage/cve.pb.go index b7a233c92d855..a3d5625cef0b2 100644 --- a/generated/storage/cve.pb.go +++ b/generated/storage/cve.pb.go @@ -1390,12 +1390,13 @@ type CVEInfo struct { CvssV3 *CVSSV3 `protobuf:"bytes,9,opt,name=cvss_v3,json=cvssV3,proto3" json:"cvss_v3,omitempty"` References []*CVEInfo_Reference `protobuf:"bytes,10,rep,name=references,proto3" json:"references,omitempty"` // cvss_metrics stores list of cvss scores from different sources like nvd, Redhat etc - CvssMetrics []*CVSSScore `protobuf:"bytes,11,rep,name=cvss_metrics,json=cvssMetrics,proto3" json:"cvss_metrics,omitempty"` - Epss *EPSS `protobuf:"bytes,12,opt,name=epss,proto3" json:"epss,omitempty"` - Exploit *Exploit `protobuf:"bytes,13,opt,name=exploit,proto3" json:"exploit,omitempty"` - CisaKev bool `protobuf:"varint,14,opt,name=cisa_kev,json=cisaKev,proto3" json:"cisa_kev,omitempty" search:"CISA KEV"` // @gotags: search:"CISA KEV" - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + CvssMetrics []*CVSSScore `protobuf:"bytes,11,rep,name=cvss_metrics,json=cvssMetrics,proto3" json:"cvss_metrics,omitempty"` + Epss *EPSS `protobuf:"bytes,12,opt,name=epss,proto3" json:"epss,omitempty"` + Exploit *Exploit `protobuf:"bytes,13,opt,name=exploit,proto3" json:"exploit,omitempty"` + CisaKev bool `protobuf:"varint,14,opt,name=cisa_kev,json=cisaKev,proto3" json:"cisa_kev,omitempty" search:"CISA KEV"` // @gotags: search:"CISA KEV" + KnownRansomwareCampaign bool `protobuf:"varint,15,opt,name=known_ransomware_campaign,json=knownRansomwareCampaign,proto3" json:"known_ransomware_campaign,omitempty" search:"Known Ransomware Campaign"` // @gotags: search:"Known Ransomware Campaign" + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CVEInfo) Reset() { @@ -1526,6 +1527,13 @@ func (x *CVEInfo) GetCisaKev() bool { return false } +func (x *CVEInfo) GetKnownRansomwareCampaign() bool { + if x != nil { + return x.KnownRansomwareCampaign + } + return false +} + type Advisory struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty" search:"Advisory Name"` // @gotags: search:"Advisory Name" @@ -2843,7 +2851,7 @@ const file_storage_cve_proto_rawDesc = "" + "\fScoreVersion\x12\x06\n" + "\x02V2\x10\x00\x12\x06\n" + "\x02V3\x10\x01\x12\v\n" + - "\aUNKNOWN\x10\x02J\x04\b\x16\x10\x17J\x04\b\x15\x10\x16\"\xd9\x05\n" + + "\aUNKNOWN\x10\x02J\x04\b\x16\x10\x17J\x04\b\x15\x10\x16\"\x95\x06\n" + "\aCVEInfo\x12\x10\n" + "\x03cve\x18\x01 \x01(\tR\x03cve\x12\x18\n" + "\asummary\x18\x02 \x01(\tR\asummary\x12\x12\n" + @@ -2862,7 +2870,8 @@ const file_storage_cve_proto_rawDesc = "" + "\fcvss_metrics\x18\v \x03(\v2\x12.storage.CVSSScoreR\vcvssMetrics\x12!\n" + "\x04epss\x18\f \x01(\v2\r.storage.EPSSR\x04epss\x12*\n" + "\aexploit\x18\r \x01(\v2\x10.storage.ExploitR\aexploit\x12\x19\n" + - "\bcisa_kev\x18\x0e \x01(\bR\acisaKev\x1a1\n" + + "\bcisa_kev\x18\x0e \x01(\bR\acisaKev\x12:\n" + + "\x19known_ransomware_campaign\x18\x0f \x01(\bR\x17knownRansomwareCampaign\x1a1\n" + "\tReference\x12\x10\n" + "\x03URI\x18\x01 \x01(\tR\x03URI\x12\x12\n" + "\x04tags\x18\x02 \x03(\tR\x04tags\"+\n" + diff --git a/generated/storage/cve_vtproto.pb.go b/generated/storage/cve_vtproto.pb.go index 8c05e40d486f1..c0392a9ed578d 100644 --- a/generated/storage/cve_vtproto.pb.go +++ b/generated/storage/cve_vtproto.pb.go @@ -196,6 +196,7 @@ func (m *CVEInfo) CloneVT() *CVEInfo { r.Epss = m.Epss.CloneVT() r.Exploit = m.Exploit.CloneVT() r.CisaKev = m.CisaKev + r.KnownRansomwareCampaign = m.KnownRansomwareCampaign if rhs := m.References; rhs != nil { tmpContainer := make([]*CVEInfo_Reference, len(rhs)) for k, v := range rhs { @@ -813,6 +814,9 @@ func (this *CVEInfo) EqualVT(that *CVEInfo) bool { if this.CisaKev != that.CisaKev { return false } + if this.KnownRansomwareCampaign != that.KnownRansomwareCampaign { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1851,6 +1855,16 @@ func (m *CVEInfo) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.KnownRansomwareCampaign { + i-- + if m.KnownRansomwareCampaign { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x78 + } if m.CisaKev { i-- if m.CisaKev { @@ -3203,6 +3217,9 @@ func (m *CVEInfo) SizeVT() (n int) { if m.CisaKev { n += 2 } + if m.KnownRansomwareCampaign { + n += 2 + } n += len(m.unknownFields) return n } @@ -5520,6 +5537,26 @@ func (m *CVEInfo) UnmarshalVT(dAtA []byte) error { } } m.CisaKev = bool(v != 0) + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KnownRansomwareCampaign", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.KnownRansomwareCampaign = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -10003,6 +10040,26 @@ func (m *CVEInfo) UnmarshalVTUnsafe(dAtA []byte) error { } } m.CisaKev = bool(v != 0) + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KnownRansomwareCampaign", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.KnownRansomwareCampaign = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pkg/nodes/converter/convert_utils_test.go b/pkg/nodes/converter/convert_utils_test.go index 1136a85dad5ec..2dd9b18d72c86 100644 --- a/pkg/nodes/converter/convert_utils_test.go +++ b/pkg/nodes/converter/convert_utils_test.go @@ -23,6 +23,7 @@ func TestNodeVulnConv(t *testing.T) { nodeVuln.CveBaseInfo.Epss = nil nodeVuln.CveBaseInfo.Exploit = nil nodeVuln.CveBaseInfo.CisaKev = false + nodeVuln.CveBaseInfo.KnownRansomwareCampaign = false embedvuln := EmbeddedVulnerabilityToNodeVulnerability(vuln) protoassert.Equal(t, nodeVuln, embedvuln) } diff --git a/pkg/postgres/schema/cluster_cves.go b/pkg/postgres/schema/cluster_cves.go index e29ab8335b394..daeda1489767a 100644 --- a/pkg/postgres/schema/cluster_cves.go +++ b/pkg/postgres/schema/cluster_cves.go @@ -52,17 +52,18 @@ const ( // ClusterCves holds the Gorm model for Postgres table `cluster_cves`. type ClusterCves struct { - ID string `gorm:"column:id;type:varchar;primaryKey"` - CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` - CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` - CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` - CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` - CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` - Cvss float32 `gorm:"column:cvss;type:numeric"` - Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` - ImpactScore float32 `gorm:"column:impactscore;type:numeric"` - Snoozed bool `gorm:"column:snoozed;type:bool"` - SnoozeExpiry *time.Time `gorm:"column:snoozeexpiry;type:timestamp"` - Type storage.CVE_CVEType `gorm:"column:type;type:integer"` - Serialized []byte `gorm:"column:serialized;type:bytea"` + ID string `gorm:"column:id;type:varchar;primaryKey"` + CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` + CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` + CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` + CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` + CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` + CveBaseInfoKnownRansomwareCampaign bool `gorm:"column:cvebaseinfo_knownransomwarecampaign;type:bool"` + Cvss float32 `gorm:"column:cvss;type:numeric"` + Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` + ImpactScore float32 `gorm:"column:impactscore;type:numeric"` + Snoozed bool `gorm:"column:snoozed;type:bool"` + SnoozeExpiry *time.Time `gorm:"column:snoozeexpiry;type:timestamp"` + Type storage.CVE_CVEType `gorm:"column:type;type:integer"` + Serialized []byte `gorm:"column:serialized;type:bytea"` } diff --git a/pkg/postgres/schema/image_cves_v2.go b/pkg/postgres/schema/image_cves_v2.go index 25b19004731be..b9ae2d99564a4 100644 --- a/pkg/postgres/schema/image_cves_v2.go +++ b/pkg/postgres/schema/image_cves_v2.go @@ -71,29 +71,30 @@ const ( // ImageCvesV2 holds the Gorm model for Postgres table `image_cves_v2`. type ImageCvesV2 struct { - ID string `gorm:"column:id;type:varchar;primaryKey"` - ImageID string `gorm:"column:imageid;type:varchar"` - CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` - CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` - CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` - CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` - CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` - Cvss float32 `gorm:"column:cvss;type:numeric"` - Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` - ImpactScore float32 `gorm:"column:impactscore;type:numeric"` - Nvdcvss float32 `gorm:"column:nvdcvss;type:numeric"` - FirstImageOccurrence *time.Time `gorm:"column:firstimageoccurrence;type:timestamp"` - State storage.VulnerabilityState `gorm:"column:state;type:integer"` - IsFixable bool `gorm:"column:isfixable;type:bool"` - FixedBy string `gorm:"column:fixedby;type:varchar"` - ComponentID string `gorm:"column:componentid;type:varchar"` - AdvisoryName string `gorm:"column:advisory_name;type:varchar"` - AdvisoryLink string `gorm:"column:advisory_link;type:varchar"` - ImageIDV2 string `gorm:"column:imageidv2;type:varchar"` - FixAvailableTimestamp *time.Time `gorm:"column:fixavailabletimestamp;type:timestamp"` - Origin storage.VulnOrigin `gorm:"column:origin;type:integer"` - Serialized []byte `gorm:"column:serialized;type:bytea"` - ImagesRef Images `gorm:"foreignKey:imageid;references:id;belongsTo;constraint:OnDelete:CASCADE"` - ImageComponentV2Ref ImageComponentV2 `gorm:"foreignKey:componentid;references:id;belongsTo;constraint:OnDelete:CASCADE"` - ImagesV2Ref ImagesV2 `gorm:"foreignKey:imageidv2;references:id;belongsTo;constraint:OnDelete:CASCADE"` + ID string `gorm:"column:id;type:varchar;primaryKey"` + ImageID string `gorm:"column:imageid;type:varchar"` + CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` + CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` + CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` + CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` + CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` + CveBaseInfoKnownRansomwareCampaign bool `gorm:"column:cvebaseinfo_knownransomwarecampaign;type:bool"` + Cvss float32 `gorm:"column:cvss;type:numeric"` + Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` + ImpactScore float32 `gorm:"column:impactscore;type:numeric"` + Nvdcvss float32 `gorm:"column:nvdcvss;type:numeric"` + FirstImageOccurrence *time.Time `gorm:"column:firstimageoccurrence;type:timestamp"` + State storage.VulnerabilityState `gorm:"column:state;type:integer"` + IsFixable bool `gorm:"column:isfixable;type:bool"` + FixedBy string `gorm:"column:fixedby;type:varchar"` + ComponentID string `gorm:"column:componentid;type:varchar"` + AdvisoryName string `gorm:"column:advisory_name;type:varchar"` + AdvisoryLink string `gorm:"column:advisory_link;type:varchar"` + ImageIDV2 string `gorm:"column:imageidv2;type:varchar"` + FixAvailableTimestamp *time.Time `gorm:"column:fixavailabletimestamp;type:timestamp"` + Origin storage.VulnOrigin `gorm:"column:origin;type:integer"` + Serialized []byte `gorm:"column:serialized;type:bytea"` + ImagesRef Images `gorm:"foreignKey:imageid;references:id;belongsTo;constraint:OnDelete:CASCADE"` + ImageComponentV2Ref ImageComponentV2 `gorm:"foreignKey:componentid;references:id;belongsTo;constraint:OnDelete:CASCADE"` + ImagesV2Ref ImagesV2 `gorm:"foreignKey:imageidv2;references:id;belongsTo;constraint:OnDelete:CASCADE"` } diff --git a/pkg/postgres/schema/node_cves.go b/pkg/postgres/schema/node_cves.go index f3e716f52758c..e47421853ae26 100644 --- a/pkg/postgres/schema/node_cves.go +++ b/pkg/postgres/schema/node_cves.go @@ -55,19 +55,20 @@ const ( // NodeCves holds the Gorm model for Postgres table `node_cves`. type NodeCves struct { - ID string `gorm:"column:id;type:varchar;primaryKey"` - CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` - CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` - CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` - CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` - CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` - OperatingSystem string `gorm:"column:operatingsystem;type:varchar"` - Cvss float32 `gorm:"column:cvss;type:numeric"` - Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` - ImpactScore float32 `gorm:"column:impactscore;type:numeric"` - Snoozed bool `gorm:"column:snoozed;type:bool"` - SnoozeExpiry *time.Time `gorm:"column:snoozeexpiry;type:timestamp"` - Orphaned bool `gorm:"column:orphaned;type:bool"` - OrphanedTime *time.Time `gorm:"column:orphanedtime;type:timestamp"` - Serialized []byte `gorm:"column:serialized;type:bytea"` + ID string `gorm:"column:id;type:varchar;primaryKey"` + CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` + CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` + CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` + CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` + CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` + CveBaseInfoKnownRansomwareCampaign bool `gorm:"column:cvebaseinfo_knownransomwarecampaign;type:bool"` + OperatingSystem string `gorm:"column:operatingsystem;type:varchar"` + Cvss float32 `gorm:"column:cvss;type:numeric"` + Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` + ImpactScore float32 `gorm:"column:impactscore;type:numeric"` + Snoozed bool `gorm:"column:snoozed;type:bool"` + SnoozeExpiry *time.Time `gorm:"column:snoozeexpiry;type:timestamp"` + Orphaned bool `gorm:"column:orphaned;type:bool"` + OrphanedTime *time.Time `gorm:"column:orphanedtime;type:timestamp"` + Serialized []byte `gorm:"column:serialized;type:bytea"` } diff --git a/pkg/postgres/schema/virtual_machine_cvev2.go b/pkg/postgres/schema/virtual_machine_cvev2.go index 1f7a689e20858..8119584863c06 100644 --- a/pkg/postgres/schema/virtual_machine_cvev2.go +++ b/pkg/postgres/schema/virtual_machine_cvev2.go @@ -68,24 +68,25 @@ const ( // VirtualMachineCvev2 holds the Gorm model for Postgres table `virtual_machine_cvev2`. type VirtualMachineCvev2 struct { - ID string `gorm:"column:id;type:uuid;primaryKey"` - VmV2ID string `gorm:"column:vmv2id;type:uuid"` - VmComponentID string `gorm:"column:vmcomponentid;type:uuid"` - CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` - CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` - CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` - CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` - CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` - PreferredCvss float32 `gorm:"column:preferredcvss;type:numeric"` - Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` - ImpactScore float32 `gorm:"column:impactscore;type:numeric"` - Nvdcvss float32 `gorm:"column:nvdcvss;type:numeric"` - IsFixable bool `gorm:"column:isfixable;type:bool"` - FixedBy string `gorm:"column:fixedby;type:varchar"` - EpssProbability float32 `gorm:"column:epssprobability;type:numeric"` - AdvisoryName string `gorm:"column:advisory_name;type:varchar"` - AdvisoryLink string `gorm:"column:advisory_link;type:varchar"` - Serialized []byte `gorm:"column:serialized;type:bytea"` - VirtualMachineV2Ref VirtualMachineV2 `gorm:"foreignKey:vmv2id;references:id;belongsTo;constraint:OnDelete:CASCADE"` - VirtualMachineComponentV2Ref VirtualMachineComponentV2 `gorm:"foreignKey:vmcomponentid;references:id;belongsTo;constraint:OnDelete:CASCADE"` + ID string `gorm:"column:id;type:uuid;primaryKey"` + VmV2ID string `gorm:"column:vmv2id;type:uuid"` + VmComponentID string `gorm:"column:vmcomponentid;type:uuid"` + CveBaseInfoCve string `gorm:"column:cvebaseinfo_cve;type:varchar"` + CveBaseInfoPublishedOn *time.Time `gorm:"column:cvebaseinfo_publishedon;type:timestamp"` + CveBaseInfoCreatedAt *time.Time `gorm:"column:cvebaseinfo_createdat;type:timestamp"` + CveBaseInfoEpssEpssProbability float32 `gorm:"column:cvebaseinfo_epss_epssprobability;type:numeric"` + CveBaseInfoCisaKev bool `gorm:"column:cvebaseinfo_cisakev;type:bool"` + CveBaseInfoKnownRansomwareCampaign bool `gorm:"column:cvebaseinfo_knownransomwarecampaign;type:bool"` + PreferredCvss float32 `gorm:"column:preferredcvss;type:numeric"` + Severity storage.VulnerabilitySeverity `gorm:"column:severity;type:integer"` + ImpactScore float32 `gorm:"column:impactscore;type:numeric"` + Nvdcvss float32 `gorm:"column:nvdcvss;type:numeric"` + IsFixable bool `gorm:"column:isfixable;type:bool"` + FixedBy string `gorm:"column:fixedby;type:varchar"` + EpssProbability float32 `gorm:"column:epssprobability;type:numeric"` + AdvisoryName string `gorm:"column:advisory_name;type:varchar"` + AdvisoryLink string `gorm:"column:advisory_link;type:varchar"` + Serialized []byte `gorm:"column:serialized;type:bytea"` + VirtualMachineV2Ref VirtualMachineV2 `gorm:"foreignKey:vmv2id;references:id;belongsTo;constraint:OnDelete:CASCADE"` + VirtualMachineComponentV2Ref VirtualMachineComponentV2 `gorm:"foreignKey:vmcomponentid;references:id;belongsTo;constraint:OnDelete:CASCADE"` } diff --git a/pkg/search/options.go b/pkg/search/options.go index 18a556e8e5431..6c417f6cff858 100644 --- a/pkg/search/options.go +++ b/pkg/search/options.go @@ -55,25 +55,26 @@ var ( Severity = newFieldLabel("Severity") Disabled = newFieldLabel("Disabled") - CVEID = newFieldLabel("CVE ID") - CVE = newFieldLabel("CVE") - CVEType = newFieldLabel("CVE Type") - CVEPublishedOn = newFieldLabel("CVE Published On") - CVEFixAvailable = newFieldLabel("CVE Fix Available Timestamp") - CVECreatedTime = newFieldLabel("CVE Created Time") - CVEOrigin = newFieldLabel("CVE Origin") - CVESuppressed = newFieldLabel("CVE Snoozed") - CVESuppressExpiry = newFieldLabel("CVE Snooze Expiry") - CVSS = newFieldLabel("CVSS") - NVDCVSS = newFieldLabel("NVD CVSS") - ImpactScore = newFieldLabel("Impact Score") - VulnerabilityState = newFieldLabel("Vulnerability State") - CVEOrphaned = newFieldLabel("CVE Orphaned") - CVEOrphanedTime = newFieldLabel("CVE Orphaned Time") - EPSSProbablity = newFieldLabel("EPSS Probability") - AdvisoryName = newFieldLabel("Advisory Name") - AdvisoryLink = newFieldLabel("Advisory Link") - CisaKev = newFieldLabel("CISA KEV") + CVEID = newFieldLabel("CVE ID") + CVE = newFieldLabel("CVE") + CVEType = newFieldLabel("CVE Type") + CVEPublishedOn = newFieldLabel("CVE Published On") + CVEFixAvailable = newFieldLabel("CVE Fix Available Timestamp") + CVECreatedTime = newFieldLabel("CVE Created Time") + CVEOrigin = newFieldLabel("CVE Origin") + CVESuppressed = newFieldLabel("CVE Snoozed") + CVESuppressExpiry = newFieldLabel("CVE Snooze Expiry") + CVSS = newFieldLabel("CVSS") + NVDCVSS = newFieldLabel("NVD CVSS") + ImpactScore = newFieldLabel("Impact Score") + VulnerabilityState = newFieldLabel("Vulnerability State") + CVEOrphaned = newFieldLabel("CVE Orphaned") + CVEOrphanedTime = newFieldLabel("CVE Orphaned Time") + EPSSProbablity = newFieldLabel("EPSS Probability") + AdvisoryName = newFieldLabel("Advisory Name") + AdvisoryLink = newFieldLabel("Advisory Link") + CisaKev = newFieldLabel("CISA KEV") + KnownRansomwareCampaign = newFieldLabel("Known Ransomware Campaign") CVEInfo = newFieldLabel("CVE Info") diff --git a/proto/storage/cve.proto b/proto/storage/cve.proto index 8336cf92e8403..87afedeeb280c 100644 --- a/proto/storage/cve.proto +++ b/proto/storage/cve.proto @@ -164,6 +164,7 @@ message CVEInfo { EPSS epss = 12; Exploit exploit = 13; bool cisa_kev = 14; // @gotags: search:"CISA KEV" + bool known_ransomware_campaign = 15; // @gotags: search:"Known Ransomware Campaign" } message Advisory { diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index 979013d5d1c0b..ada9094986dcf 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -6682,6 +6682,11 @@ "id": 14, "name": "cisa_kev", "type": "bool" + }, + { + "id": 15, + "name": "known_ransomware_campaign", + "type": "bool" } ], "messages": [ From c5ed87e306c755571723d4ce8e1e7ee599b8d24b Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:18:12 +0200 Subject: [PATCH 274/638] chore: bump kubevirt.io/client-go to v1.10.0-alpha.0 (#22740) --- go.mod | 6 +----- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c18d2906450ff..5c356e6d3823e 100644 --- a/go.mod +++ b/go.mod @@ -178,7 +178,7 @@ require ( k8s.io/kubelet v0.32.13 k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 kubevirt.io/api v1.9.0 - kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426 + kubevirt.io/client-go v1.10.0-alpha.0 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/controller-tools v0.21.0 sigs.k8s.io/e2e-framework v0.7.0 @@ -540,10 +540,6 @@ replace ( gopkg.in/yaml.v3 => github.com/stackrox/yaml/v3 v3.0.0 ) -// No tagged kubevirt.io/client-go includes kubevirt#18315, required for -// kubevirt client-go against k8s.io/client-go v0.36. Pinned to -// kubevirt/client-go@f38626e. - // kubevirt.io/client-go requires the nonexistent k8s.io/kube-openapi@v0.31.0 tag. replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 diff --git a/go.sum b/go.sum index db099f2579a92..b9a582cb58c0f 100644 --- a/go.sum +++ b/go.sum @@ -2180,8 +2180,8 @@ k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbe k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= kubevirt.io/api v1.9.0 h1:FWqx9yGk/GHUP5o4IthGXZ8FeAlgVNv2It48wD86UUo= kubevirt.io/api v1.9.0/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY= -kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426 h1:W7o8PnS4K+fLbaDSB8QhK2J18pcnRDs4QlfrxNspMng= -kubevirt.io/client-go v1.9.0-beta.0.0.20260825101120-f38626e05426/go.mod h1:bXmbwFVCJsUnFGg1lgVSB2Jc180N7IzlQRHXJxlrG6U= +kubevirt.io/client-go v1.10.0-alpha.0 h1:4fEp/8Aa6OUDRo5RZT5Uehw3K6Bp/tQPA2hIJhFCQfw= +kubevirt.io/client-go v1.10.0-alpha.0/go.mod h1:bXmbwFVCJsUnFGg1lgVSB2Jc180N7IzlQRHXJxlrG6U= kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc= kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= From e79bfc80efc31007dfa241924267ae2d0a553d08 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:11:09 +0200 Subject: [PATCH 275/638] ROX-36890: Stop VM index pulls after a feature-disabled ACK (#22743) --- .../virtualmachine/vmscraper/scraper.go | 32 ++++- .../virtualmachine/vmscraper/scraper_test.go | 111 ++++++++++++++++++ 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 04797f0149d4e..46e6ba51f2611 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -121,7 +121,10 @@ type VMScraper struct { // loggedSkip is set after the first skip log for a missing-capability stretch // so a 10s ticker does not repeat it until the capability returns. loggedSkip atomic.Bool - now func() time.Time + // indexReportsDisabled is set from a feature-disabled ACK and cleared on + // CentralReachable so a later connection can scrape again. + indexReportsDisabled atomic.Bool + now func() time.Time // randFloat64 returns a unit sample in [0, 1] for schedule offsets; tests inject a fixed source. randFloat64 func() float64 lastSpreadWarnNumVMs int @@ -198,6 +201,8 @@ func (s *VMScraper) Notify(e common.SensorComponentEvent) { switch e { case common.SensorComponentEventCentralReachable: s.centralReady.Signal() + s.indexReportsDisabled.Store(false) + s.loggedSkip.Store(false) case common.SensorComponentEventOfflineMode: s.centralReady.Reset() } @@ -222,6 +227,10 @@ func (s *VMScraper) ProcessMessage(_ context.Context, msg *central.MsgToSensor) switch sensorAck.GetAction() { case central.SensorACK_ACK: + if sensorAck.GetReason() == centralsensor.SensorACKReasonFeatureDisabled { + s.handleFeatureDisabled() + break + } log.Debugf("VMScraper: received acknowledgement for resource_id=%q", sensorAck.GetResourceId()) case central.SensorACK_NACK: s.handleNACK(sensorAck.GetResourceId()) @@ -231,6 +240,18 @@ func (s *VMScraper) ProcessMessage(_ context.Context, msg *central.MsgToSensor) return nil } +// handleFeatureDisabled stops index pulls for this connection. CentralReachable +// clears the flag so a later Central can be tried without a Sensor restart. +func (s *VMScraper) handleFeatureDisabled() { + if s.indexReportsDisabled.Swap(true) { + return + } + log.Infof( + "VMScraper: Central ACKed a VM index report with reason %q; stopping index pulls for this connection to save resources", + centralsensor.SensorACKReasonFeatureDisabled, + ) +} + // handleNACK clears lastToken and applies retry backoff so the next tick // resends a full report without tight-looping on persistent NACKs. func (s *VMScraper) handleNACK(resourceID string) { @@ -306,9 +327,14 @@ func (s *VMScraper) run() { } func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { - if !centralcaps.Has(centralsensor.VirtualMachinesSupported) { + disabled := s.indexReportsDisabled.Load() + if disabled || !centralcaps.Has(centralsensor.VirtualMachinesSupported) { if s.loggedSkip.CompareAndSwap(false, true) { - log.Infof("VMScraper: skipping pulling index reports from VMs; Central does not advertise VirtualMachinesSupported") + if disabled { + log.Infof("VMScraper: skipping pulling index reports from VMs; Central ACKed feature disabled") + } else { + log.Infof("VMScraper: skipping pulling index reports from VMs; Central does not advertise VirtualMachinesSupported") + } } return } diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index 7ac9687de85ce..c600fb23d5653 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -22,6 +22,7 @@ import ( "github.com/stackrox/rox/pkg/set" "github.com/stackrox/rox/pkg/sync" pkgVM "github.com/stackrox/rox/pkg/virtualmachine" + "github.com/stackrox/rox/sensor/common" "github.com/stackrox/rox/sensor/common/centralcaps" "github.com/stackrox/rox/sensor/common/message" "github.com/stackrox/rox/sensor/common/virtualmachine" @@ -848,6 +849,116 @@ func TestVMScraper_NACK(t *testing.T) { } } +// TestVMScraper_ACKFeatureDisabledStopsScraping covers a 4.11 Central that +// advertises VirtualMachinesSupported then ACKs index reports as feature-disabled. +func TestVMScraper_ACKFeatureDisabledStopsScraping(t *testing.T) { + cases := map[string]struct { + reason string + wantScrapes bool + }{ + "stops scraping after a feature-disabled ACK": { + reason: centralsensor.SensorACKReasonFeatureDisabled, + wantScrapes: false, + }, + "keeps scraping after an ACK with an empty reason": { + reason: "", + wantScrapes: true, + }, + "keeps scraping after an ACK with an unrelated reason": { + reason: centralsensor.SensorACKReasonRateLimited, + wantScrapes: true, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + vmA := makeVM("ns1", "vm-a", 100) + vmA.ID = "vm-a-id" + store := &mockStore{vms: []*virtualmachine.Info{vmA}} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.pollOnce(t.Context()) + require.Equal(t, 1, forwardedCount(s)) + require.True(t, centralcaps.Has(centralsensor.VirtualMachinesSupported)) + reconcilesAfterFirst := store.listRunningCalls + dialsAfterFirst := dialer.callIdx.Load() + + require.NoError(t, s.ProcessMessage(t.Context(), ¢ral.MsgToSensor{ + Msg: ¢ral.MsgToSensor_SensorAck{ + SensorAck: ¢ral.SensorACK{ + MessageType: central.SensorACK_VM_INDEX_REPORT, + Action: central.SensorACK_ACK, + ResourceId: "vm-a-id:100", + Reason: tc.reason, + }, + }, + })) + require.True(t, centralcaps.Has(centralsensor.VirtualMachinesSupported), + "hello capabilities are left unchanged") + assert.Equal(t, !tc.wantScrapes, s.indexReportsDisabled.Load()) + + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{unchangedResult()} + clock.Advance(5 * time.Minute) + s.pollOnce(t.Context()) + + if tc.wantScrapes { + require.Len(t, client.calls, 1) + assert.Equal(t, reconcilesAfterFirst+1, store.listRunningCalls) + assert.Equal(t, dialsAfterFirst+1, dialer.callIdx.Load()) + } else { + assert.Empty(t, client.calls) + assert.Equal(t, reconcilesAfterFirst, store.listRunningCalls) + assert.Equal(t, dialsAfterFirst, dialer.callIdx.Load()) + } + }) + } +} + +// TestVMScraper_ACKFeatureDisabledResumesOnCentralReachable covers reconnect +// after a feature-disabled ACK: the next Central is tried without a Sensor restart. +func TestVMScraper_ACKFeatureDisabledResumesOnCentralReachable(t *testing.T) { + vmA := makeVM("ns1", "vm-a", 100) + vmA.ID = "vm-a-id" + store := &mockStore{vms: []*virtualmachine.Info{vmA}} + dialer := &mockDialer{} + client := &mockProtocolClient{ + resultQueue: []*vsockclient.GetReportResult{makeReport("1")}, + } + + s, clock := newTestScraper(t, store, dialer, client) + s.pollOnce(t.Context()) + require.Equal(t, 1, forwardedCount(s)) + + require.NoError(t, s.ProcessMessage(t.Context(), ¢ral.MsgToSensor{ + Msg: ¢ral.MsgToSensor_SensorAck{ + SensorAck: ¢ral.SensorACK{ + MessageType: central.SensorACK_VM_INDEX_REPORT, + Action: central.SensorACK_ACK, + ResourceId: "vm-a-id:100", + Reason: centralsensor.SensorACKReasonFeatureDisabled, + }, + }, + })) + require.True(t, s.indexReportsDisabled.Load()) + + client.reset() + client.resultQueue = []*vsockclient.GetReportResult{unchangedResult()} + clock.Advance(5 * time.Minute) + s.pollOnce(t.Context()) + assert.Empty(t, client.calls) + + s.Notify(common.SensorComponentEventCentralReachable) + require.False(t, s.indexReportsDisabled.Load()) + + s.pollOnce(t.Context()) + require.Len(t, client.calls, 1) +} + // TestVMScraper_InFlightSendDoesNotOverwriteNACKReset covers NACK while // forwardReport is blocked: commit must not restore lastToken or clear NACK backoff. func TestVMScraper_InFlightSendDoesNotOverwriteNACKReset(t *testing.T) { From b2c4e1ad037d14aa80f04c8d6729b92e88b6b2f0 Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Wed, 9 Sep 2026 21:28:19 -0300 Subject: [PATCH 276/638] ROX-33377: add v4 vulnerability bundle stream (#22760) --- scanner/VULNERABILITY_VERSION | 2 +- scanner/updater/ci/source-config.yaml | 14 ++++++++++++++ .../updater/version/VULNERABILITY_BUNDLE_VERSION | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scanner/VULNERABILITY_VERSION b/scanner/VULNERABILITY_VERSION index 29ef827e8a45b..c694117fd4e76 100644 --- a/scanner/VULNERABILITY_VERSION +++ b/scanner/VULNERABILITY_VERSION @@ -1 +1 @@ -v3 +v4 diff --git a/scanner/updater/ci/source-config.yaml b/scanner/updater/ci/source-config.yaml index 4ddcc3c5d0533..26c66ad004d41 100644 --- a/scanner/updater/ci/source-config.yaml +++ b/scanner/updater/ci/source-config.yaml @@ -58,6 +58,20 @@ bundles: - stackrox-rhel-csaf - suse - ubuntu + v4: + - alpine + - aws + - debian + - epss + - manual + - nvd + - oracle + - osv + - photon + - rhel-vex + - stackrox-rhel-csaf + - suse + - ubuntu updaters: default: diff --git a/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION b/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION index 6e2ed9e2edd8b..8cf08d06206d0 100644 --- a/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION +++ b/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION @@ -8,3 +8,5 @@ dev heads/master v2 heads/release-4.9.x/scanner-updater-v2 # release-4.11 at 2026-08-06, after 4.11.3-rc.0. v3 heads/2e8d7a35b1200eb29d5a75cad2a443719cf265e1 +# master until the release branch is available. +v4 heads/master From d15e3d055d88fcbbd38c3a94aba5d3d01e6b8d21 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 03:41:38 +0000 Subject: [PATCH 277/638] Update Konflux references (#22767) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 14 +++++++------- .tekton/checks.yaml | 4 ++-- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/main-pipeline.yaml | 14 +++++++------- .tekton/operator-bundle-pipeline.yaml | 14 +++++++------- .tekton/operator-index-build.yaml | 6 +++--- .tekton/retag-pipeline.yaml | 2 +- .tekton/scanner-v4-pipeline.yaml | 14 +++++++------- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index af38e41601ed7..a4b5f9f050ee4 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:c6c414a3b5dcd827720f3443a128a02d27a0a5071ea3e4dc0063d578f1f73a74 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -251,7 +251,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe - name: kind value: task resolver: bundles @@ -310,7 +310,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:9afb0e30b1b5b49a5a934a09ce3b55baf892da99a784387ff72ec92bd2c374c2 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:6ead03ca82a1b970dc60eaadae86adf374e8e70f8d951902e1fac7411dbe0550 - name: kind value: task resolver: bundles @@ -350,7 +350,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:bbad5bfd67d1263320a0c4d3f062a18ae611e081b009fd96117c2a00a6ce8dd9 - name: kind value: task resolver: bundles @@ -582,7 +582,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:3e59d6303ca031c27fab079f7c743fbdecc53d007b8d86e32227de5164f06e31 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index dd48051f11391..9c735261a3103 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -146,7 +146,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:d44fb2d0e1bb5eb916777bf129b9d6b5e8c08a14f6c4505dc67bce22660b3d9f + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:dd0c817c0e3ded8ff52b6c227681ea542e17211bf85de38101b9b11029d16bac - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 929939192dcbd..87a2b58c25709 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -201,7 +201,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 3f42185fecfd6..cd6171a64e826 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -169,7 +169,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:c6c414a3b5dcd827720f3443a128a02d27a0a5071ea3e4dc0063d578f1f73a74 - name: kind value: task resolver: bundles @@ -193,7 +193,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe - name: kind value: task resolver: bundles @@ -331,7 +331,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:9afb0e30b1b5b49a5a934a09ce3b55baf892da99a784387ff72ec92bd2c374c2 - name: kind value: task resolver: bundles @@ -351,7 +351,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:6ead03ca82a1b970dc60eaadae86adf374e8e70f8d951902e1fac7411dbe0550 - name: kind value: task resolver: bundles @@ -372,7 +372,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:bbad5bfd67d1263320a0c4d3f062a18ae611e081b009fd96117c2a00a6ce8dd9 - name: kind value: task resolver: bundles @@ -604,7 +604,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:3e59d6303ca031c27fab079f7c743fbdecc53d007b8d86e32227de5164f06e31 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index c900ed07f08c3..fd81b86744b60 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -265,7 +265,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:c6c414a3b5dcd827720f3443a128a02d27a0a5071ea3e4dc0063d578f1f73a74 - name: kind value: task resolver: bundles @@ -289,7 +289,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -348,7 +348,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe - name: kind value: task resolver: bundles @@ -517,7 +517,7 @@ spec: - name: name value: buildah-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:d44fb2d0e1bb5eb916777bf129b9d6b5e8c08a14f6c4505dc67bce22660b3d9f + value: quay.io/konflux-ci/tekton-catalog/task-buildah-oci-ta:0.12.1@sha256:dd0c817c0e3ded8ff52b6c227681ea542e17211bf85de38101b9b11029d16bac - name: kind value: task resolver: bundles @@ -536,7 +536,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:ccd3665345d86c6799bc7e2e6ad86b277d9f3a5c40b513e6f2a0af8ad92e7dba + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:b1c71d83be8e6d0e44f9b6e58e1ad491396d4c287be9291802c9be69d9441f2f - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:bbad5bfd67d1263320a0c4d3f062a18ae611e081b009fd96117c2a00a6ce8dd9 - name: kind value: task resolver: bundles @@ -782,7 +782,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:3e59d6303ca031c27fab079f7c743fbdecc53d007b8d86e32227de5164f06e31 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 7c00de2ca5775..82242850be435 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -195,7 +195,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -414,7 +414,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:9afb0e30b1b5b49a5a934a09ce3b55baf892da99a784387ff72ec92bd2c374c2 - name: kind value: task resolver: bundles @@ -433,7 +433,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:6ead03ca82a1b970dc60eaadae86adf374e8e70f8d951902e1fac7411dbe0550 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 8574961b5d8c0..57dd59ba54659 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -117,7 +117,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 84fd734caa153..9060e25ebfaaf 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -168,7 +168,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:4be9343579d91c7b501cafe966cff59a601dfeca903c476e3763dd8d7599b900 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:c6c414a3b5dcd827720f3443a128a02d27a0a5071ea3e4dc0063d578f1f73a74 - name: kind value: task resolver: bundles @@ -192,7 +192,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:2e8fe30b6d5c8a8a3e6bbc0ea5a55e05b6170d4a399830f25ea43e17881ce544 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:1d7ba568ae6e9e26800054f3942779de8abc80ddc48f0e44df6b1c2f098161dc - name: kind value: task resolver: bundles @@ -271,7 +271,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:374f776bcb2048c3adeaf4dbb460c52d001bc6020320df5e878c2d05391302da + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe - name: kind value: task resolver: bundles @@ -330,7 +330,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:ceecb10bc58092c51a104f62ce6fd188a2d3f06fbdc446d81801cab118929344 + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.1@sha256:9afb0e30b1b5b49a5a934a09ce3b55baf892da99a784387ff72ec92bd2c374c2 - name: kind value: task resolver: bundles @@ -349,7 +349,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:290c9ec319423ff9ae7b2cb78fa859e1d333abcdd2ef6c001533377812020071 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.3.1@sha256:6ead03ca82a1b970dc60eaadae86adf374e8e70f8d951902e1fac7411dbe0550 - name: kind value: task resolver: bundles @@ -370,7 +370,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:1808485d95cf77fb7912f6fe69191bead05fc0f2f71e00031941a7ea38a5f665 + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.1@sha256:bbad5bfd67d1263320a0c4d3f062a18ae611e081b009fd96117c2a00a6ce8dd9 - name: kind value: task resolver: bundles @@ -602,7 +602,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:ef00a86cb22259fcfdefa15a5116b63d0f24ee35c95d05ff9815ee8f84beb548 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:3e59d6303ca031c27fab079f7c743fbdecc53d007b8d86e32227de5164f06e31 - name: kind value: task resolver: bundles From 99689fa43a9e69e6a80735835459cf7ced48dad8 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:18:46 +0200 Subject: [PATCH 278/638] ROX-36784: Mount sysimage RPM DB for V4 node indexing (#22669) --- CHANGELOG.md | 1 + compliance/node/index/indexer.go | 63 ++++++++---- compliance/node/index/indexer_test.go | 94 ++++++++++++++++-- .../virtualmachines/roxagent/cmd/serve.go | 1 - deploy/common/k8sbased.sh | 8 ++ .../templates/collector.yaml.htpl | 3 + .../helmtest/collector-compliance.test.yaml | 14 +++ .../src/test/groovy/NodeIndexTest.groovy | 60 ++++++++++++ .../src/test/groovy/NodeInventoryTest.groovy | 98 ------------------- tests/e2e/lib.sh | 7 ++ 10 files changed, 224 insertions(+), 125 deletions(-) create mode 100644 qa-tests-backend/src/test/groovy/NodeIndexTest.groovy delete mode 100644 qa-tests-backend/src/test/groovy/NodeInventoryTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index b7460b048583a..649e554f6a6f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-35079: installation of the `app.k8s.io/v1beta1/Application` resource when central is installed is deprecated. It will be removed in a future release. ### Technical Changes +- ROX-36784: Scanner V4 node indexing on OpenShift now reads the host RPM database Claircore reports: SQLite on RHEL 9+ (`/usr/share/rpm`, `/usr/lib/sysimage/rpm`) and Berkeley DB on RHEL 8 (`/usr/share/rpm`, `/usr/lib/sysimage/rpm-ostree-base-db`). - ROX-36824: Diagnostic bundles now redact the value of the `openshift.io/token-secret.value` annotation on secrets. Previously this OpenShift-managed annotation, which contains a plaintext service account token on generated dockercfg secrets, was included unredacted in the bundle. - ROX-36660: The **Fixable → CVE is not yet fixable** policy criterion now matches Scanner V4 CVEs that have no fix version. Scanner V4 leaves `Fixed By` unset instead of empty (Scanner V2 always set an empty string), so the matcher previously skipped those CVEs. - ROX-36490: The virtual machine enhanced data model (`ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL`) is now enabled by default. diff --git a/compliance/node/index/indexer.go b/compliance/node/index/indexer.go index b783fc9c5b8cc..673e44b5a256a 100644 --- a/compliance/node/index/indexer.go +++ b/compliance/node/index/indexer.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "strconv" "strings" "time" @@ -39,8 +40,6 @@ import ( const ( layerMediaType = "application/vnd.claircore.filesystem" - rhcosPackageDB = "sqlite:usr/share/rpm" - // scannerDefinitionsRouteInSensor should be in sync with `scannerDefinitionsRoute` in sensor/sensor.go // Direct import is prohibited by import rules scannerDefinitionsRouteInSensor = "/scanner/definitions" @@ -50,6 +49,16 @@ const ( var ( log = logging.LoggerForModule() + // rhcosPackageDBs are Claircore PackageDB names for RHCOS RPM databases. + // RHEL 8 uses Berkeley DB; RHEL 9+ uses SQLite. ostree reports the same RPMs + // under usr/share/rpm and usr/lib/sysimage/rpm-ostree-base-db. + rhcosPackageDBs = []string{ + "sqlite:usr/share/rpm", + "sqlite:usr/lib/sysimage/rpm", + "bdb:usr/share/rpm", + "bdb:usr/lib/sysimage/rpm-ostree-base-db", + } + // layerDigest is a dummy digest solely meant as a workaround to use Claircore. // Claircore indexing requires layers to have a digest, which is not stored, // so we use a fake one for all nodes. @@ -118,11 +127,9 @@ type NodeIndexerConfig struct { Repo2CPEMappingFile string // Timeout controls the timeout for any remote API calls. Timeout time.Duration - // PackageDBFilter removes irrelevant packages. For node scanning, we are - // currently only interested in the RHCOS RPM database. - // Filters out all packages whose packageDB does not match the filter. - // Empty string corresponds to no filtering. - PackageDBFilter string + // PackageDBFilter keeps RHCOS RPM databases and drops other package DBs + // Claircore finds under the host index mount. Empty means no filtering. + PackageDBFilter []string } // DefaultNodeIndexerConfig provides the default configuration for a node indexer. @@ -134,7 +141,7 @@ func DefaultNodeIndexerConfig() NodeIndexerConfig { Client: nil, Repo2CPEMappingURL: buildMappingURL(), Timeout: 10 * time.Second, - PackageDBFilter: rhcosPackageDB, + PackageDBFilter: rhcosPackageDBs, } } @@ -273,23 +280,14 @@ func runRepositoryScanner(ctx context.Context, cfg NodeIndexerConfig, l *clairco return repos, nil } -func runPackageScanner(ctx context.Context, packageDBFilter string, layer *claircore.Layer) ([]*claircore.Package, error) { +func runPackageScanner(ctx context.Context, packageDBFilter []string, layer *claircore.Layer) ([]*claircore.Package, error) { scanner := rhel.PackageScanner{} pkgs, err := scanner.Scan(ctx, layer) if err != nil { return nil, errors.Wrap(err, "failed to invoke RHEL scanner") } - // Filter out packages in which we are not interested. - filtered := pkgs - if packageDBFilter != "" { - filtered = pkgs[:0] - for _, pkg := range pkgs { - if pkg.PackageDB == packageDBFilter { - filtered = append(filtered, pkg) - } - } - } + filtered := filterPackages(pkgs, packageDBFilter) for i, p := range filtered { p.ID = strconv.Itoa(i) } @@ -297,6 +295,33 @@ func runPackageScanner(ctx context.Context, packageDBFilter string, layer *clair return filtered, nil } +// filterPackages keeps packages whose PackageDB is in packageDBFilter. +// ostree nodes expose the same RPMs in two PackageDBs, so duplicates +// collapse to one per name/version/arch/kind. +func filterPackages(pkgs []*claircore.Package, packageDBFilter []string) []*claircore.Package { + if len(packageDBFilter) == 0 { + return pkgs + } + type ident struct { + name, version, arch string + kind types.PackageKind + } + out := make([]*claircore.Package, 0, len(pkgs)) + seen := make(map[ident]struct{}, len(pkgs)) + for _, pkg := range pkgs { + if !slices.Contains(packageDBFilter, pkg.PackageDB) { + continue + } + id := ident{pkg.Name, pkg.Version, pkg.Arch, pkg.Kind} + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + out = append(out, pkg) + } + return out +} + func runCoalescer(ctx context.Context, layerDigest claircore.Digest, repos []*claircore.Repository, pkgs []*claircore.Package) (*claircore.IndexReport, error) { la := &ccindexer.LayerArtifacts{ Hash: layerDigest, diff --git a/compliance/node/index/indexer_test.go b/compliance/node/index/indexer_test.go index 148dbdf20c91a..2adcbebe7807e 100644 --- a/compliance/node/index/indexer_test.go +++ b/compliance/node/index/indexer_test.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "strings" "testing" "time" @@ -164,7 +165,7 @@ func (s *nodeIndexerSuite) TestRunRepositoryScannerAnyPath() { func (s *nodeIndexerSuite) TestRunPackageScanner() { layer := s.mustCreateLayer("testdata") - packages, err := runPackageScanner(context.Background(), rhcosPackageDB, layer) + packages, err := runPackageScanner(context.Background(), rhcosPackageDBs, layer) s.NoError(err) s.Len(packages, 106) @@ -173,7 +174,7 @@ func (s *nodeIndexerSuite) TestRunPackageScanner() { func (s *nodeIndexerSuite) TestRunPackageScannerWithUnmatchedFilter() { layer := s.mustCreateLayer("testdata") - packages, err := runPackageScanner(context.Background(), "invalidPackageDB", layer) + packages, err := runPackageScanner(context.Background(), []string{"invalidPackageDB"}, layer) s.NoError(err) // All packages are filtered out. @@ -183,13 +184,92 @@ func (s *nodeIndexerSuite) TestRunPackageScannerWithUnmatchedFilter() { func (s *nodeIndexerSuite) TestRunPackageScannerAnyPath() { layer := s.mustCreateLayer(s.T().TempDir()) - packages, err := runPackageScanner(context.Background(), rhcosPackageDB, layer) + packages, err := runPackageScanner(context.Background(), rhcosPackageDBs, layer) s.NoError(err) // The scanner must not error out, but produce 0 results s.Len(packages, 0) } +func (s *nodeIndexerSuite) TestRHCOSPackageDBs() { + tests := map[string]bool{ + "sqlite:usr/share/rpm": true, + "sqlite:usr/lib/sysimage/rpm": true, + "bdb:usr/share/rpm": true, + "bdb:usr/lib/sysimage/rpm-ostree-base-db": true, + "sqlite:var/lib/rpm": false, + } + for db, want := range tests { + s.Run(db, func() { + s.Equal(want, slices.Contains(rhcosPackageDBs, db)) + }) + } +} + +func (s *nodeIndexerSuite) TestFilterPackages() { + pkg := func(db, name, version string) *claircore.Package { + return &claircore.Package{ + PackageDB: db, + Name: name, + Version: version, + Arch: "x86_64", + Kind: types.BinaryPackage, + } + } + share := "bdb:usr/share/rpm" + ostree := "bdb:usr/lib/sysimage/rpm-ostree-base-db" + filter := []string{share, ostree} + + tests := map[string]struct { + pkgs []*claircore.Package + filter []string + want int + }{ + "empty filter keeps all": { + pkgs: []*claircore.Package{pkg(share, "bash", "1"), pkg("other", "zsh", "1")}, + filter: nil, + want: 2, + }, + "unmatched PackageDB dropped": { + pkgs: []*claircore.Package{pkg("sqlite:var/lib/rpm", "bash", "1")}, + filter: filter, + want: 0, + }, + "ostree duplicate PackageDBs collapse": { + pkgs: []*claircore.Package{ + pkg(share, "bash", "5.0"), + pkg(ostree, "bash", "5.0"), + }, + filter: filter, + want: 1, + }, + "distinct NEVRAs kept": { + pkgs: []*claircore.Package{ + pkg(share, "bash", "5.0"), + pkg(share, "coreutils", "8.0"), + }, + filter: filter, + want: 2, + }, + } + for name, tc := range tests { + s.Run(name, func() { + s.Len(filterPackages(tc.pkgs, tc.filter), tc.want) + }) + } + + s.Run("does not alias input backing array", func() { + a := pkg(share, "bash", "5.0") + b := pkg(ostree, "bash", "5.0") + in := []*claircore.Package{a, b} + got := filterPackages(in, filter) + s.Len(got, 1) + s.Same(a, in[0]) + s.Same(b, in[1]) + s.Len(in, 2) + }) +} + func (s *nodeIndexerSuite) TestBuildMappingURL() { tcs := map[string]struct { sensorEndpointSetting string @@ -285,7 +365,7 @@ func (s *nodeIndexerSuite) TestIndexerE2E() { cfg := DefaultNodeIndexerConfig() cfg.HostPath = "testdata" cfg.Repo2CPEMappingURL = server.URL - cfg.PackageDBFilter = rhcosPackageDB + cfg.PackageDBFilter = rhcosPackageDBs report, err := NewNodeIndexer(cfg).IndexNode(context.Background()) s.NoError(err) @@ -304,7 +384,7 @@ func (s *nodeIndexerSuite) TestIndexerE2E() { s.Equal(buildMappingURL(), cfg.Repo2CPEMappingURL) s.Nil(cfg.Client) cfg.HostPath = "testdata" - cfg.PackageDBFilter = rhcosPackageDB + cfg.PackageDBFilter = rhcosPackageDBs report, err := NewNodeIndexer(cfg).IndexNode(context.Background()) s.NoError(err) @@ -324,7 +404,7 @@ func (s *nodeIndexerSuite) TestIndexerE2ESeparateOSReleasePath() { cfg.OSReleasePath = "testdata-rhcos" cfg.Repo2CPEMappingURL = server.URL cfg.Client = server.Client() - cfg.PackageDBFilter = rhcosPackageDB + cfg.PackageDBFilter = rhcosPackageDBs indexer := NewNodeIndexer(cfg) report, err := indexer.IndexNode(context.Background()) @@ -354,7 +434,7 @@ func (s *nodeIndexerSuite) TestIndexerE2ENoPath() { cfg.Client = server.Client() cfg.HostPath = "doesnotexist" cfg.Repo2CPEMappingURL = server.URL - cfg.PackageDBFilter = rhcosPackageDB + cfg.PackageDBFilter = rhcosPackageDBs indexer := NewNodeIndexer(cfg) report, err := indexer.IndexNode(context.Background()) diff --git a/compliance/virtualmachines/roxagent/cmd/serve.go b/compliance/virtualmachines/roxagent/cmd/serve.go index 6bc282581f28a..61871ed0cf39b 100644 --- a/compliance/virtualmachines/roxagent/cmd/serve.go +++ b/compliance/virtualmachines/roxagent/cmd/serve.go @@ -198,7 +198,6 @@ func scan(ctx context.Context, hostPath, mappingFilePath string) (*v4.IndexRepor HostPath: hostPath, Client: &http.Client{Transport: proxy.RoundTripper()}, Repo2CPEMappingFile: mappingFilePath, - PackageDBFilter: "", } return index.NewNodeIndexer(cfg).IndexNode(ctx) } diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index 172cdb1bc1f5c..5492e32b9480c 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -963,6 +963,14 @@ function launch_sensor { extra_helm_config+=(--set "virtualMachines.enabled=false") fi + # Shorten node-scan cadence for e2e (production: 5m initial, 4h interval). + # Matcher-not-ready drops the first index as unretryable; a short interval + # covers the next scan without restarting collector. + helm_args+=( + --set customize.envVars.ROX_NODE_SCANNING_MAX_INITIAL_WAIT=1s + --set customize.envVars.ROX_NODE_SCANNING_INTERVAL=30s + ) + if [[ -n "$LOGLEVEL" ]]; then helm_args+=( --set customize.envVars.LOGLEVEL="${LOGLEVEL}" diff --git a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl index e86617992a14f..560c982f38906 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl @@ -230,6 +230,9 @@ spec: - mountPath: /hostindex/usr/share name: usr-share-ro readOnly: true + - mountPath: /hostindex/usr/lib + name: usr-lib-ro + readOnly: true - mountPath: /tmp name: index-volume {{- end }} diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml index e7b9c7577dd11..f0974bcff94af 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml @@ -17,6 +17,20 @@ tests: - openshift-4.12 availableSchemas: - openshift-4.12 +- name: "does not set node index host path on kubernetes" + expect: | + envVars(.daemonsets.collector; "compliance") | assertThat(has("ROX_NODE_INDEX_HOST_PATH") == false) + [container(.daemonsets.collector; "compliance") | .volumeMounts[] | select(.mountPath | startswith("/hostindex"))] | length | assertThat(. == 0) +- name: "OpenShift 4 indexes /hostindex with usr/share and usr/lib RPM paths" + server: + visibleSchemas: + - openshift-4.12 + availableSchemas: + - openshift-4.12 + expect: | + envVars(.daemonsets.collector; "compliance")["ROX_NODE_INDEX_HOST_PATH"] | assertThat(. == "/hostindex") + [container(.daemonsets.collector; "compliance") | .volumeMounts[] | select(.mountPath == "/hostindex/usr/share")] | length | assertThat(. == 1) + [container(.daemonsets.collector; "compliance") | .volumeMounts[] | select(.mountPath == "/hostindex/usr/lib")] | length | assertThat(. == 1) - name: "customize.envVars can re-enable Scanner V2 node inventory calls" values: customize: diff --git a/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy b/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy new file mode 100644 index 0000000000000..6c101aa005ca7 --- /dev/null +++ b/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy @@ -0,0 +1,60 @@ +import static util.Helpers.waitForTrue +import static util.Helpers.withRetry + +import orchestratormanager.OrchestratorTypes + +import io.stackrox.proto.storage.NodeOuterClass.Node + +import org.junit.Assume + +import common.Constants +import services.BaseService +import services.NodeService +import util.Env + +import spock.lang.IgnoreIf +import spock.lang.Requires +import spock.lang.Tag + +// Central's scanner-v4-matcher is required to enrich index reports into node scans. +@IgnoreIf({ Env.ONLY_SECURED_CLUSTER == "true" }) +// V4 node index reports are produced on OpenShift (RHCOS/RHEL RPM DBs). +@Requires({ Env.mustGetOrchestratorType() == OrchestratorTypes.OPENSHIFT }) +@Tag("PZ") +class NodeIndexTest extends BaseSpecification { + def setupSpec() { + BaseService.useBasicAuth() + } + + @Tag("BAT") + def "Verify node index scans"() { + given: + "Scanner V4 is enabled and the cluster has nodes" + Assume.assumeTrue("Scanner V4 node indexing is required", scannerV4Enabled) + List nodes = NodeService.getNodes() + assert nodes.size() > 0 + + when: + "scanner-v4-matcher is ready so index reports can be enriched" + log.info("Waiting for scanner-v4-matcher deployment to be ready") + waitForTrue(20, 6) { + orchestrator.deploymentReady(Constants.STACKROX_NAMESPACE, "scanner-v4-matcher") + } + // Matcher-not-ready drops the first index report as unretryable. CI deploy + // sets ROX_NODE_SCANNING_MAX_INITIAL_WAIT=1s and ROX_NODE_SCANNING_INTERVAL=30s + // so a later scan lands after matcher is up without restarting collector. + + then: + "each OpenShift node scan has RPM packages, not only kubelet/kernel/runtime" + withRetry(12, 30) { + nodes = NodeService.getNodes() + assert nodes.size() > 0, "Expected to find at least one node" + nodes.each { node -> + assert node.getScan(), "Expected to find a nodeScan on the node" + int n = node.getScan().getComponentsList().size() + log.info("Node ${node.getName()} scan contains ${n} components") + assert n > 4, "Expected to find more than 4 components on OpenShift node" + } + } + } +} diff --git a/qa-tests-backend/src/test/groovy/NodeInventoryTest.groovy b/qa-tests-backend/src/test/groovy/NodeInventoryTest.groovy deleted file mode 100644 index d09a67040de1b..0000000000000 --- a/qa-tests-backend/src/test/groovy/NodeInventoryTest.groovy +++ /dev/null @@ -1,98 +0,0 @@ -import static util.Helpers.waitForTrue -import static util.Helpers.withRetry - -import io.stackrox.proto.storage.NodeOuterClass.Node - -import common.Constants -import services.BaseService -import services.ClusterService -import services.NodeService -import util.Env - -import spock.lang.IgnoreIf -import spock.lang.Shared -import spock.lang.Tag - -// skip if executed in a test environment with just secured-cluster deployed in the test cluster -// i.e. central is deployed elsewhere -@IgnoreIf({ Env.ONLY_SECURED_CLUSTER == "true" }) -@IgnoreIf({ true }) // ROX-29634: address legacy scanner-v2 test failing with rhcos layers change -@Tag("PZ") -class NodeInventoryTest extends BaseSpecification { - @Shared - private String clusterId - def setupSpec() { - BaseService.useBasicAuth() - - // Get cluster ID - assert ClusterService.getClusters().size() > 0, "There must be at least one secured cluster" - clusterId = ClusterService.getClusters().get(0).getId() - assert clusterId - } - - @Tag("BAT") - def "Verify node inventories and their scans"() { - given: - "given a non-empty list of nodes on an OpenShift 4 cluster" - List nodes = NodeService.getNodes() - assert nodes.size() > 0 - - when: - boolean nodeInventoryContainerAvailable = - orchestrator.containsDaemonSetContainer(Constants.STACKROX_NAMESPACE, "collector", "node-inventory") - if (nodeInventoryContainerAvailable) { - // Sometimes one pod in daemon set will miss the env variable despite updating. - // Let's try this operation twice before giving up - waitForTrue(2, 20) { - log.info("Setting collector.node-inventory ROX_NODE_SCANNING_MAX_INITIAL_WAIT to 1s") - orchestrator.updateDaemonSetEnv(Constants.STACKROX_NAMESPACE, "collector", "node-inventory", - "ROX_NODE_SCANNING_MAX_INITIAL_WAIT", "1s") - try { - log.info("Wait for collector DS to be restarted with new values") - waitForTrue(20, 10) { - orchestrator.daemonSetEnvVarUpdated(Constants.STACKROX_NAMESPACE, "collector", - "node-inventory", "ROX_NODE_SCANNING_MAX_INITIAL_WAIT", "1s") - } - - log.info("Wait for collector DS to be ready") - waitForTrue(20, 10) { - orchestrator.daemonSetReady(Constants.STACKROX_NAMESPACE, "collector") - } - } - catch (Exception ignored) { - log.info("Unable to bring collector ds to the desired state") - return false - } - return true - } - } - log.info("Waiting for scanner deployment to be ready") - waitForTrue(20, 6) { - orchestrator.deploymentReady(Constants.STACKROX_NAMESPACE, "scanner") - } - - then: - "confirm the number of components in the inventory and their scan" - // ensure that the nodes got scanned at least once - retry up to 6 minutes - withRetry(12, 30) { - nodes = NodeService.getNodes() - assert nodes.size() > 0, "Expected to find at least one node" - nodes.each { node -> - assert node.getScan().getComponentsList().size() >= 4, "Expected to find at least 4 node components" - } - } - nodes.each { node -> - assert node.getScan(), "Expected to find a nodeScan on the node" - log.info("Node ${node.getName()} scan contains ${node.getScan().getComponentsList().size()} components") - - if (!nodeInventoryContainerAvailable) { - // No RHCOS node scanning on this cluster - assert node.getScan().getComponentsList().size() == 4, - "Expected to find exactly 4 components on non-RHCOS node" - return - } - assert node.getScan().getComponentsList().size() > 4, - "Expected to find more than 4 components on RHCOS node" - } - } -} diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 7a4ee7e231f69..76be09180b6e5 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -757,6 +757,13 @@ deploy_sensor_via_operator() { fi customize_envVars="" + # Shorten node-scan cadence for e2e (production: 5m initial, 4h interval). + # Matcher-not-ready drops the first index as unretryable; a short interval + # covers the next scan without restarting collector. + customize_envVars+=$'\n - name: ROX_NODE_SCANNING_MAX_INITIAL_WAIT' + customize_envVars+=$'\n value: "1s"' + customize_envVars+=$'\n - name: ROX_NODE_SCANNING_INTERVAL' + customize_envVars+=$'\n value: "30s"' if [[ -n "${ROX_NETFLOW_BATCHING:-}" ]]; then customize_envVars+=$'\n - name: ROX_NETFLOW_BATCHING' customize_envVars+=$'\n value: "'"${ROX_NETFLOW_BATCHING}"'"' From 7f2e9df4480b65996166f761ea4990236784cdec Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:19:25 +0200 Subject: [PATCH 279/638] ROX-36796: Sensor metrics for mapping path mix and fetch health (#22647) --- sensor/common/scannerdefinitions/metrics.go | 38 ++++++- sensor/common/scannerdefinitions/repo2cpe.go | 27 ++++- .../scannerdefinitions/repo2cpe_test.go | 89 +++++++++++------ .../common/virtualmachine/metrics/metrics.go | 25 +++++ .../virtualmachine/vmscraper/scraper.go | 74 +++++++++++++- .../virtualmachine/vmscraper/scraper_test.go | 98 +++++++++++++++++++ 6 files changed, 309 insertions(+), 42 deletions(-) diff --git a/sensor/common/scannerdefinitions/metrics.go b/sensor/common/scannerdefinitions/metrics.go index 10fa1388cf04a..1c08f98dd8e38 100644 --- a/sensor/common/scannerdefinitions/metrics.go +++ b/sensor/common/scannerdefinitions/metrics.go @@ -1,10 +1,18 @@ package scannerdefinitions import ( + "sync/atomic" + "github.com/prometheus/client_golang/prometheus" "github.com/stackrox/rox/pkg/metrics" ) +const ( + repoCPEMappingFetchSuccess = "success" + repoCPEMappingFetchUnchanged = "unchanged" + repoCPEMappingFetchError = "error" +) + // repoCPEMappingHashChanges counts Sensor replacing its cached mapping when // Central's file hashes differently, including the first populate. It is not // a per-VM VSOCK sync (see vsock_pull_sync_total). @@ -15,6 +23,34 @@ var repoCPEMappingHashChanges = prometheus.NewCounter(prometheus.CounterOpts{ Help: "Times Sensor replaced its cached repo-to-CPE mapping because the content hash from Central (and its upstream source) has changed. This answers the question 'How often does the repo-to-CPE mapping get updated in general?'", }) +// repoCPEMappingFetch counts each Central fetch attempt once: success is a +// hash change, unchanged is HTTP 304 or same-hash 200, error is anything that +// keeps last-good. It is not a per-VM VSOCK sync. +var repoCPEMappingFetch = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "repo_cpe_mapping_fetch_total", + Help: "Central repo-to-CPE mapping fetches by result (success: content hash changed; unchanged: HTTP 304 or same-hash 200; error: request, validation, or unexpected status). Not a per-VM VSOCK sync.", +}, []string{"result"}) + +// repo2CPELastSuccessUnix is the Unix seconds of cache.lastSuccess, or 0 +// until the first successful fetch. The GaugeFunc reads this so scrapes stay +// correct without a Sensor tick. +var repo2CPELastSuccessUnix atomic.Int64 + +func repo2CPELastSuccessSeconds() float64 { + return float64(repo2CPELastSuccessUnix.Load()) +} + +// repoCPEMappingLastSuccess is Unix seconds of Sensor's last successful +// mapping fetch (304 and same-hash 200 count). Zero until the first success. +var repoCPEMappingLastSuccess = prometheus.NewGaugeFunc(prometheus.GaugeOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "repo_cpe_mapping_last_success_timestamp_seconds", + Help: "Unix timestamp of Sensor's last successful repo-to-CPE mapping fetch from Central, including HTTP 304 and same-hash 200. Zero until the first success.", +}, repo2CPELastSuccessSeconds) + func init() { - metrics.EmplaceCollector(repoCPEMappingHashChanges) + metrics.EmplaceCollector(repoCPEMappingHashChanges, repoCPEMappingFetch, repoCPEMappingLastSuccess) } diff --git a/sensor/common/scannerdefinitions/repo2cpe.go b/sensor/common/scannerdefinitions/repo2cpe.go index 6007936e0f493..c088279a9e460 100644 --- a/sensor/common/scannerdefinitions/repo2cpe.go +++ b/sensor/common/scannerdefinitions/repo2cpe.go @@ -182,6 +182,11 @@ func (r *Repo2CPE) FetchRepo2CPE(_ context.Context) (mapping []byte, hash string // mapping and updates the cache on success or "unchanged". It reports // whether the attempt succeeded, so the caller can pick the next delay. func (r *Repo2CPE) attemptRepo2CPERefresh(ctx context.Context) bool { + result := repoCPEMappingFetchError + defer func() { + repoCPEMappingFetch.WithLabelValues(result).Inc() + }() + etag, lastModified := concurrency.WithLock2(&r.cacheMu, func() (string, string) { return r.cache.etag, r.cache.lastModified }) @@ -202,6 +207,7 @@ func (r *Repo2CPE) attemptRepo2CPERefresh(ctx context.Context) bool { switch resp.StatusCode { case http.StatusNotModified: r.recordRepo2CPEUnchanged(resp) + result = repoCPEMappingFetchUnchanged return true case http.StatusOK: body, err := io.ReadAll(io.LimitReader(resp.Body, cpemapping.MaxMappingBytes+1)) @@ -213,7 +219,11 @@ func (r *Repo2CPE) attemptRepo2CPERefresh(ctx context.Context) bool { log.Warnf("Repo-to-CPE mapping failed validation, keeping last-good: %v", err) return false } - r.recordRepo2CPESuccess(body, resp) + if r.recordRepo2CPESuccess(body, resp) { + result = repoCPEMappingFetchSuccess + } else { + result = repoCPEMappingFetchUnchanged + } return true default: log.Warnf("Unexpected status %d fetching repo-to-CPE mapping from central", resp.StatusCode) @@ -241,7 +251,7 @@ func (r *Repo2CPE) newRepo2CPERequest(ctx context.Context, etag, lastModified st func (r *Repo2CPE) recordRepo2CPEUnchanged(resp *http.Response) { concurrency.WithLock(&r.cacheMu, func() { - r.cache.lastSuccess = time.Now() + r.markRepo2CPESuccessNoLock() r.mergeRepo2CPEValidatorsNoLock(resp) }) log.Debug("Repo-to-CPE mapping unchanged (304)") @@ -249,12 +259,12 @@ func (r *Repo2CPE) recordRepo2CPEUnchanged(resp *http.Response) { // recordRepo2CPESuccess keeps the existing cached bytes when the new content // hashes the same as what's cached, treating a same-hash 200 like a 304. -func (r *Repo2CPE) recordRepo2CPESuccess(body []byte, resp *http.Response) { +func (r *Repo2CPE) recordRepo2CPESuccess(body []byte, resp *http.Response) bool { hash := cpemapping.HashMapping(body) var oldHash string var changed bool concurrency.WithLock(&r.cacheMu, func() { - r.cache.lastSuccess = time.Now() + r.markRepo2CPESuccessNoLock() oldHash = r.cache.hash if hash != r.cache.hash { r.cache.mapping = body @@ -268,6 +278,15 @@ func (r *Repo2CPE) recordRepo2CPESuccess(body []byte, resp *http.Response) { log.Infof("Repo-to-CPE mapping content hash changed: old=%q new=%q", oldHash, hash) } log.Debugf("Fetched repo-to-CPE mapping from central, hash=%s", hash) + return changed +} + +// markRepo2CPESuccessNoLock publishes lastSuccess to the last-success gauge +// in the same critical section so scrapes cannot observe a stale timestamp. +func (r *Repo2CPE) markRepo2CPESuccessNoLock() { + now := time.Now() + r.cache.lastSuccess = now + repo2CPELastSuccessUnix.Store(now.Unix()) } // replaceRepo2CPEValidatorsNoLock copies validators from a 200, including diff --git a/sensor/common/scannerdefinitions/repo2cpe_test.go b/sensor/common/scannerdefinitions/repo2cpe_test.go index 2776484802a99..75eeb89c53f29 100644 --- a/sensor/common/scannerdefinitions/repo2cpe_test.go +++ b/sensor/common/scannerdefinitions/repo2cpe_test.go @@ -76,6 +76,7 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { wantEtag string wantLastModified string wantHashChange bool + wantFetchResult string }{ "first fetch succeeds and populates an empty cache": { serverHandler: func(w http.ResponseWriter, r *http.Request) { @@ -83,11 +84,12 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { w.Header().Set(etagHeader, `"v1"`) _, _ = w.Write([]byte(mappingV1)) }, - wantOK: true, - wantMapping: mappingV1, - wantHash: hashV1, - wantEtag: `"v1"`, - wantHashChange: true, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + wantEtag: `"v1"`, + wantHashChange: true, + wantFetchResult: repoCPEMappingFetchSuccess, }, "304 with matching validators keeps the cached mapping": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, etag: `"v1"`, lastModified: "old", lastSuccess: seededSuccess}, @@ -100,69 +102,77 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { wantHash: hashV1, wantEtag: `"v1"`, wantLastModified: "old", + wantFetchResult: repoCPEMappingFetchUnchanged, }, "200 without validators replaces previously cached validators": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, etag: `"v1"`, lastModified: "old", lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(mappingV2)) }, - wantOK: true, - wantMapping: mappingV2, - wantHash: hashV2, - wantHashChange: true, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + wantHashChange: true, + wantFetchResult: repoCPEMappingFetchSuccess, }, "200 with a same-hash body is treated as unchanged": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(mappingV1)) }, - wantOK: true, - wantMapping: mappingV1, - wantHash: hashV1, + wantOK: true, + wantMapping: mappingV1, + wantHash: hashV1, + wantFetchResult: repoCPEMappingFetchUnchanged, }, "200 with a different hash replaces the cached mapping": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(mappingV2)) }, - wantOK: true, - wantMapping: mappingV2, - wantHash: hashV2, - wantHashChange: true, + wantOK: true, + wantMapping: mappingV2, + wantHash: hashV2, + wantHashChange: true, + wantFetchResult: repoCPEMappingFetchSuccess, }, "an unexpected status leaves the cache untouched": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, - wantOK: false, - wantMapping: mappingV1, - wantHash: hashV1, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + wantFetchResult: repoCPEMappingFetchError, }, "a network error leaves the cache untouched": { - seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, - networkErr: true, - wantOK: false, - wantMapping: mappingV1, - wantHash: hashV1, + seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, + networkErr: true, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + wantFetchResult: repoCPEMappingFetchError, }, "an oversized response leaves the cache untouched": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write(bytes.Repeat([]byte("a"), cpemapping.MaxMappingBytes+1)) }, - wantOK: false, - wantMapping: mappingV1, - wantHash: hashV1, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + wantFetchResult: repoCPEMappingFetchError, }, "an invalid mapping body leaves the cache untouched": { seedCache: repo2CPECache{mapping: []byte(mappingV1), hash: hashV1, lastSuccess: seededSuccess}, serverHandler: func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"not":"a mapping"}`)) }, - wantOK: false, - wantMapping: mappingV1, - wantHash: hashV1, + wantOK: false, + wantMapping: mappingV1, + wantHash: hashV1, + wantFetchResult: repoCPEMappingFetchError, }, } @@ -179,23 +189,38 @@ func TestAttemptRepo2CPERefresh(t *testing.T) { r.centralClient = newTestCentralClient(t, tt.serverHandler) } - before := testutil.ToFloat64(repoCPEMappingHashChanges) + beforeHashChanges := testutil.ToFloat64(repoCPEMappingHashChanges) + beforeFetch := map[string]float64{ + repoCPEMappingFetchSuccess: testutil.ToFloat64(repoCPEMappingFetch.WithLabelValues(repoCPEMappingFetchSuccess)), + repoCPEMappingFetchUnchanged: testutil.ToFloat64(repoCPEMappingFetch.WithLabelValues(repoCPEMappingFetchUnchanged)), + repoCPEMappingFetchError: testutil.ToFloat64(repoCPEMappingFetch.WithLabelValues(repoCPEMappingFetchError)), + } + beforeLastSuccess := testutil.ToFloat64(repoCPEMappingLastSuccess) ok := r.attemptRepo2CPERefresh(t.Context()) assert.Equal(t, tt.wantOK, ok) - wantCount := before + wantCount := beforeHashChanges if tt.wantHashChange { wantCount++ } assert.Equal(t, wantCount, testutil.ToFloat64(repoCPEMappingHashChanges)) + for result, before := range beforeFetch { + want := before + if result == tt.wantFetchResult { + want++ + } + assert.Equal(t, want, testutil.ToFloat64(repoCPEMappingFetch.WithLabelValues(result)), result) + } assert.Equal(t, tt.wantMapping, string(r.cache.mapping)) assert.Equal(t, tt.wantHash, r.cache.hash) assert.Equal(t, tt.wantEtag, r.cache.etag) assert.Equal(t, tt.wantLastModified, r.cache.lastModified) if tt.wantOK { assert.False(t, r.cache.lastSuccess.IsZero()) + assert.Equal(t, float64(r.cache.lastSuccess.Unix()), testutil.ToFloat64(repoCPEMappingLastSuccess)) } else { assert.Equal(t, tt.seedCache.lastSuccess, r.cache.lastSuccess) + assert.Equal(t, beforeLastSuccess, testutil.ToFloat64(repoCPEMappingLastSuccess)) } }) } diff --git a/sensor/common/virtualmachine/metrics/metrics.go b/sensor/common/virtualmachine/metrics/metrics.go index baed6a5eda51c..8b76f04f0e862 100644 --- a/sensor/common/virtualmachine/metrics/metrics.go +++ b/sensor/common/virtualmachine/metrics/metrics.go @@ -103,6 +103,14 @@ const ( PullSyncTimeout = "timeout" ) +// Mapping-path label values for PullTrackedVMsByMappingPath. Bounded to +// the three RepoCPEMappingUpdatePath cases so cardinality stays fixed. +const ( + MappingPathSensor = "sensor" + MappingPathURL = "url" + MappingPathUnspecified = "unspecified" +) + // PullDialDurationSeconds measures time to establish a websocket connection per VM. var PullDialDurationSeconds = prometheus.NewHistogram( prometheus.HistogramOpts{ @@ -247,6 +255,19 @@ var PullTrackedVMs = prometheus.NewGauge( }, ) +// PullTrackedVMsByMappingPath is currently tracked VMs partitioned by +// repo-to-CPE mapping update path. It is fleet mix, not scrape events; +// the three series sum to vsock_pull_tracked_vms. +var PullTrackedVMsByMappingPath = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: metrics.PrometheusNamespace, + Subsystem: metrics.SensorSubsystem.String(), + Name: "vsock_pull_tracked_vms_by_mapping_path", + Help: "Number of VMs currently tracked for pull-mode scraping, partitioned by repo-to-CPE mapping update path (sensor, url, unspecified). This is fleet mix, not scrape events.", + }, + []string{"path"}, +) + // PullDueVMs is how many VMs were eligible to scrape at the start of the last // tick (nextAttemptAt had arrived and they were not already in flight). var PullDueVMs = prometheus.NewGauge( @@ -319,9 +340,13 @@ func init() { PullSyncTotal, PullTicksTotal, PullTrackedVMs, + PullTrackedVMsByMappingPath, PullDueVMs, PullStartsPerTick, PullForwardInterarrivalSeconds, PullScheduleOffsetSeconds, ) + for _, path := range []string{MappingPathSensor, MappingPathURL, MappingPathUnspecified} { + PullTrackedVMsByMappingPath.WithLabelValues(path).Set(0) + } } diff --git a/sensor/common/virtualmachine/vmscraper/scraper.go b/sensor/common/virtualmachine/vmscraper/scraper.go index 46e6ba51f2611..5760d3835a452 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper.go +++ b/sensor/common/virtualmachine/vmscraper/scraper.go @@ -143,6 +143,10 @@ type vmState struct { backoff time.Duration vmID virtualmachine.VMID lastAgentVersion string + // mappingPath is the last RepoCPEMappingUpdatePath seen in ResponseMeta + // for this slot (sensor/url/unspecified). New and recreated slots start + // unspecified until a scrape with meta arrives. + mappingPath string } var _ common.SensorComponent = (*VMScraper)(nil) @@ -353,10 +357,9 @@ func (s *VMScraper) tick(ctx context.Context, forceReconcile bool) { due := s.dueKeys() nDue := len(due) + // Mapping-path gauges are rewritten on reconcile, removal, and path updates. nTracked := concurrency.WithLock1(&s.mu, func() int { - n := len(s.vmState) - metrics.PullTrackedVMs.Set(float64(n)) - return n + return len(s.vmState) }) metrics.PullDueVMs.Set(float64(nDue)) capN := min(s.concurrency, startBudget(nTracked, s.tickInterval, newVMIndexReportWindow(s.interval))) @@ -409,11 +412,19 @@ func (s *VMScraper) reconcile() { liveKeys.Add(key) st, ok := s.vmState[key] if !ok { - st = &vmState{nextAttemptAt: now.Add(randOffset(newVMWindow, s.randFloat64())), vmID: vm.ID} + st = &vmState{ + nextAttemptAt: now.Add(randOffset(newVMWindow, s.randFloat64())), + vmID: vm.ID, + mappingPath: metrics.MappingPathUnspecified, + } s.vmState[key] = st } else if st.vmID != vm.ID { // namespace/name can outlive a KubeVirt recreate; do not inherit scrape state. - st = &vmState{nextAttemptAt: now, vmID: vm.ID} + st = &vmState{ + nextAttemptAt: now, + vmID: vm.ID, + mappingPath: metrics.MappingPathUnspecified, + } s.vmState[key] = st } } @@ -424,6 +435,7 @@ func (s *VMScraper) reconcile() { } numVMs = len(s.vmState) s.lastReconcile = now + s.setTrackedVMGaugesNoLock() }) s.warnIfSpreadSaturated(numVMs) } @@ -484,6 +496,7 @@ func (s *VMScraper) scrapeKey(ctx context.Context, key string) bool { concurrency.WithLock(&s.mu, func() { if st, ok := s.vmState[key]; ok && st.vmID == vmID { delete(s.vmState, key) + s.setTrackedVMGaugesNoLock() } }) return false @@ -786,6 +799,9 @@ func isAbnormalClose(err error, target *closeCoder) bool { // Sensor-managed and its reported hash is stale. A URL-managed agent with // a stale hash is only logged and counted, since Sensor doesn't own it. func (s *VMScraper) maybeSyncRepoCPEMapping(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, meta *pb.ResponseMeta) { + if meta != nil { + s.recordMappingPath(key, meta.GetRepoCpeMappingUpdatePath()) + } updatePath := meta.GetRepoCpeMappingUpdatePath() if updatePath == pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED { return @@ -812,6 +828,54 @@ func (s *VMScraper) maybeSyncRepoCPEMapping(ctx context.Context, vm *virtualmach } } +// recordMappingPath stores path on a live slot. Gauges are rewritten only +// when the path changes; missing keys are ignored so a concurrent prune +// cannot resurrect a slot. +func (s *VMScraper) recordMappingPath(key string, path pb.RepoCPEMappingUpdatePath) { + label := mappingPathLabel(path) + concurrency.WithLock(&s.mu, func() { + st, ok := s.vmState[key] + if !ok || st.mappingPath == label { + return + } + st.mappingPath = label + s.setTrackedVMGaugesNoLock() + }) +} + +func mappingPathLabel(path pb.RepoCPEMappingUpdatePath) string { + switch path { + case pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR: + return metrics.MappingPathSensor + case pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL: + return metrics.MappingPathURL + default: + return metrics.MappingPathUnspecified + } +} + +// setTrackedVMGaugesNoLock writes vsock_pull_tracked_vms and the three +// by-mapping-path series from vmState so the labeled counts always sum +// to the unlabeled gauge. +func (s *VMScraper) setTrackedVMGaugesNoLock() { + counts := map[string]int{ + metrics.MappingPathSensor: 0, + metrics.MappingPathURL: 0, + metrics.MappingPathUnspecified: 0, + } + for _, st := range s.vmState { + path := st.mappingPath + if path == "" { + path = metrics.MappingPathUnspecified + } + counts[path]++ + } + metrics.PullTrackedVMs.Set(float64(len(s.vmState))) + for path, n := range counts { + metrics.PullTrackedVMsByMappingPath.WithLabelValues(path).Set(float64(n)) + } +} + // syncRepoCPEMapping pushes mapping on a new connection (one request per // connection). The deadline is independent of GetReport's. func (s *VMScraper) syncRepoCPEMapping(ctx context.Context, vm *virtualmachine.Info, key string, port uint32, mapping []byte) { diff --git a/sensor/common/virtualmachine/vmscraper/scraper_test.go b/sensor/common/virtualmachine/vmscraper/scraper_test.go index c600fb23d5653..f5f3aa6955a1e 100644 --- a/sensor/common/virtualmachine/vmscraper/scraper_test.go +++ b/sensor/common/virtualmachine/vmscraper/scraper_test.go @@ -1269,6 +1269,104 @@ func TestVMScraper_IgnoresLateScrapeAfterVMReplacement(t *testing.T) { }) } +// TestVMScraper_TrackedVMsByMappingPath covers the currently-tracked gauge +// mix. Path updates use maybeSyncRepoCPEMapping so they do not need a VSOCK push. +func TestVMScraper_TrackedVMsByMappingPath(t *testing.T) { + const key = "ns1/vm-a" + + tests := map[string]func(t *testing.T){ + "a new VM starts as unspecified": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + assertTrackedByMappingPath(t, 0, 0, 1) + }, + "SENSOR meta moves the recount to sensor": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + assertTrackedByMappingPath(t, 1, 0, 0) + }, + "URL meta moves the recount to url": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL)) + assertTrackedByMappingPath(t, 0, 1, 0) + }, + "unspecified meta stays unspecified": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_UNSPECIFIED)) + assertTrackedByMappingPath(t, 0, 0, 1) + }, + "nil meta does not change a live slot": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, nil) + assertTrackedByMappingPath(t, 1, 0, 0) + }, + "repeating SENSOR meta keeps the mix": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + assertTrackedByMappingPath(t, 1, 0, 0) + }, + "URL meta after SENSOR recounts to url": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + s, _ := newTestScraper(t, &mockStore{vms: []*virtualmachine.Info{vm}}, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_URL)) + assertTrackedByMappingPath(t, 0, 1, 0) + }, + "a VM leaving vmState drops its series": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + store := &mockStore{vms: []*virtualmachine.Info{vm}} + s, _ := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + require.Equal(t, 1.0, testutil.ToFloat64(metrics.PullTrackedVMsByMappingPath.WithLabelValues(metrics.MappingPathSensor))) + store.vms = nil + s.reconcile() + assertTrackedByMappingPath(t, 0, 0, 0) + }, + "a vmID recreate returns to unspecified": func(t *testing.T) { + vm := makeVM("ns1", "vm-a", 100) + store := &mockStore{vms: []*virtualmachine.Info{vm}} + s, _ := newTestScraper(t, store, &mockDialer{}, &mockProtocolClient{}) + s.reconcile() + s.maybeSyncRepoCPEMapping(t.Context(), vm, key, 1, metaWithMapping("h", pb.RepoCPEMappingUpdatePath_REPO_CPE_MAPPING_UPDATE_PATH_SENSOR)) + store.vms = []*virtualmachine.Info{{ + ID: "uid-new", + Namespace: "ns1", + Name: "vm-a", + VSOCKCID: new(uint32(101)), + Running: true, + }} + s.reconcile() + assertTrackedByMappingPath(t, 0, 0, 1) + }, + } + for name, fn := range tests { + t.Run(name, fn) + } +} + +func assertTrackedByMappingPath(t *testing.T, sensor, url, unspecified float64) { + t.Helper() + assert.Equal(t, sensor, testutil.ToFloat64(metrics.PullTrackedVMsByMappingPath.WithLabelValues(metrics.MappingPathSensor)), "sensor") + assert.Equal(t, url, testutil.ToFloat64(metrics.PullTrackedVMsByMappingPath.WithLabelValues(metrics.MappingPathURL)), "url") + assert.Equal(t, unspecified, testutil.ToFloat64(metrics.PullTrackedVMsByMappingPath.WithLabelValues(metrics.MappingPathUnspecified)), "unspecified") + assert.Equal(t, sensor+url+unspecified, testutil.ToFloat64(metrics.PullTrackedVMs), "labeled series must sum to vsock_pull_tracked_vms") +} + // hasScheduleSlot reports whether key has a vmState slot (reconcile membership). func hasScheduleSlot(t *testing.T, s *VMScraper, key string) bool { t.Helper() From 17610011cea97974d8eb71842008811e4b83224c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:58:11 +0200 Subject: [PATCH 280/638] chore(deps-dev): bump js-yaml from 4.3.1 to 4.3.2 in /tests/performance/load (#22717) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/load/package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 39c545c9dd20c..9655f48ba9e60 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -1487,9 +1487,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -1501,6 +1501,7 @@ "url": "https://github.com/sponsors/nodeca" } ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3667,9 +3668,9 @@ "dev": true }, "js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "requires": { "argparse": "^2.0.1" From c04b0857a9c02e406c145e759f0cedfb18b54612 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:53:46 +0200 Subject: [PATCH 281/638] chore(deps): bump the golang-org-x group with 5 updates (#22771) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 5c356e6d3823e..e5208a4a5444e 100644 --- a/go.mod +++ b/go.mod @@ -144,15 +144,15 @@ require ( go.uber.org/mock v0.6.0 go.uber.org/zap v1.28.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.55.0 - golang.org/x/mod v0.40.0 + golang.org/x/crypto v0.56.0 + golang.org/x/mod v0.41.0 golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.22.0 - golang.org/x/sys v0.47.0 + golang.org/x/sync v0.23.0 + golang.org/x/sys v0.48.0 golang.org/x/term v0.45.0 golang.org/x/text v0.41.0 - golang.org/x/time v0.15.0 + golang.org/x/time v0.16.0 golang.org/x/tools v0.49.0 golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.292.0 diff --git a/go.sum b/go.sum index b9a582cb58c0f..c127259592b73 100644 --- a/go.sum +++ b/go.sum @@ -1550,8 +1550,8 @@ golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqj golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1602,8 +1602,8 @@ golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1693,8 +1693,8 @@ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1793,8 +1793,8 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= @@ -1839,8 +1839,8 @@ golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 8e35919f08acf39d9bef02102f9baeefa128cb7b Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 10 Sep 2026 15:50:55 +0200 Subject: [PATCH 282/638] ROX-35992,ROX-36527: eliminate spin-loop in Seq() (#22456) Signed-off-by: Tomasz Janiszewski Co-authored-by: Claude Sonnet 4.5 Co-authored-by: Piotr Rygielski <114479+vikin91@users.noreply.github.com> --- pkg/queue/queue.go | 67 ++++++-- pkg/queue/queue_test.go | 351 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 403 insertions(+), 15 deletions(-) diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go index 3d34a7ddd5d13..09de1f600df09 100644 --- a/pkg/queue/queue.go +++ b/pkg/queue/queue.go @@ -26,6 +26,9 @@ type Queue[T comparable] struct { queue *list.List notEmptySignal concurrency.Signal mutex sync.Mutex + // afterEmptyPull runs after pullWait sees an empty queue and before it waits. + // Tests inject a Push in that window; production leaves it nil. + afterEmptyPull func() } // OptionFunc provides options for the queue. @@ -87,41 +90,72 @@ func (q *Queue[T]) Pull() T { // PullBlocking will pull an item from the queue, potentially waiting until one is available. // In case the waitable signals done, the default value of T will be returned. func (q *Queue[T]) PullBlocking(waitable concurrency.Waitable) T { - item, ok := q.pull() - // In case multiple go routines are pull blocking, we have to ensure that the result of pull - // is valid, hence the additional for loop here. - for ; !ok; item, ok = q.pull() { - select { - case <-waitable.Done(): - return item - case <-q.notEmptySignal.Done(): - } - } + item, _ := q.pullWait(waitable) return item } // Seq returns a iterator function that yields items from the queue as they become available. // The iterator will continue until the provided waitable signals done. +// +// Note: Seq checks for cancellation before each pull. If the waitable signals done while items +// remain in the queue, Seq will exit immediately without consuming them. This differs from +// PullBlocking, which performs an initial pull before checking whether the waitable is done. func (q *Queue[T]) Seq(waitable concurrency.Waitable) func(yield func(T) bool) { return func(yield func(T) bool) { for { + // Ensure responsive cancellation even when queue has items select { case <-waitable.Done(): return - case <-q.notEmptySignal.Done(): - if item, ok := q.pull(); ok && !yield(item) { - return - } + default: + } + + item, ok := q.pullWait(waitable) + if !ok { + return + } + + if !yield(item) { + return } } } } +// pullWait waits for an item to become available in the queue, blocking until +// either an item is retrieved or the waitable signals done. +// Returns the item and true if retrieved, or zero value and false if cancelled. +func (q *Queue[T]) pullWait(waitable concurrency.Waitable) (T, bool) { + item, ok := q.pull() + // Keep retrying until we actually get an item or the waitable signals done. + // This prevents lost wakeup: if we're signaled but another consumer + // takes the item before we pull, we must continue waiting. + for !ok { + if q.afterEmptyPull != nil { + q.afterEmptyPull() + } + select { + case <-waitable.Done(): + var nilT T + return nilT, false + case <-q.notEmptySignal.Done(): + } + item, ok = q.pull() + } + return item, true +} + func (q *Queue[T]) innerPull() (T, bool) { q.mutex.Lock() defer q.mutex.Unlock() if q.queue.Len() == 0 { + // Reset signal while observing empty queue under lock. + // This prevents the race where a Push signals after we check + // but before we wait - the signal and our observation are atomic. + if q.notEmptySignal.IsDone() { + q.notEmptySignal.Reset() + } var nilT T return nilT, false } @@ -152,6 +186,10 @@ func (q *Queue[T]) innerPush(item T) bool { } q.queue.PushBack(item) + // Signal while holding the lock, atomically with PushBack. + // This prevents the race where a consumer observes empty and resets + // the signal after we've added the item but before we signal. + q.notEmptySignal.Signal() return true } @@ -166,7 +204,6 @@ func (q *Queue[T]) Push(item T) { return } - q.notEmptySignal.Signal() if q.counterMetric != nil { // Using `WithLabelValues` instead of `With` to avoid extra memory allocations. q.counterMetric.WithLabelValues(metrics.Add.String()).Inc() diff --git a/pkg/queue/queue_test.go b/pkg/queue/queue_test.go index 4b8ecb7c92579..e4fbaf825f6b8 100644 --- a/pkg/queue/queue_test.go +++ b/pkg/queue/queue_test.go @@ -3,6 +3,7 @@ package queue import ( "context" "testing" + "testing/synctest" "time" "github.com/stackrox/rox/pkg/sync" @@ -187,4 +188,354 @@ func TestQueueSeq(t *testing.T) { assert.ElementsMatch(t, expectedItems, items) }) + + t.Run("Seq Empty Queue No Spin Loop", func(t *testing.T) { + // This test verifies the fix for the spin-loop bug where Seq() would + // continuously loop without blocking when the queue was empty. + // Under synctest's deterministic execution, a spin-loop causes timeouts. + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + itemReceived := make(chan int, 1) + iterationStarted := make(chan struct{}) + + // Start Seq() iteration on empty queue + go func() { + close(iterationStarted) + for item := range q.Seq(ctx) { + itemReceived <- item + return + } + }() + + <-iterationStarted + + // Wait to ensure the goroutine is properly blocked (not spinning) + // In the buggy version, this would cause synctest to detect a spin-loop + synctest.Wait() + + // Now add an item - it should be received + q.Push(42) + synctest.Wait() + + select { + case item := <-itemReceived: + assert.Equal(t, 42, item) + default: + t.Fatal("expected to receive item from queue") + } + }) + }) + + t.Run("Seq Empty Pull After Signal", func(t *testing.T) { + // This test verifies that Seq() properly re-blocks when a signal occurs + // but another consumer removes the item before Seq can pull it. + // Without proper blocking, Seq would spin-loop after the empty pull. + testEmptyPullAfterSignal(t, "Seq", func(q *Queue[int], ctx context.Context, itemReceived chan int, started chan struct{}) { + go func() { + close(started) + for item := range q.Seq(ctx) { + itemReceived <- item + return + } + }() + }) + }) + + t.Run("Seq Cancellation With Queued Items", func(t *testing.T) { + // This test verifies that Seq() checks waitable.Done() before every pull, + // not just when the queue is empty. If items are already queued and the + // waitable is cancelled, the iterator should exit immediately without + // consuming any items. + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + + // Pre-populate queue with items + q.Push(1) + q.Push(2) + q.Push(3) + + // Create already-cancelled context + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + // Verify context is cancelled before iteration starts + select { + case <-ctx.Done(): + // Expected: context is already done + default: + t.Fatal("context should be cancelled before iteration") + } + + // Attempt to iterate - should exit immediately without consuming items + itemsConsumed := 0 + for range q.Seq(ctx) { + itemsConsumed++ + } + + // Assertions + assert.Equal(t, 0, itemsConsumed, "should not consume any items when cancelled") + assert.Equal(t, 3, q.Len(), "all items should remain in queue") + }) + }) + + t.Run("Seq Lost Wakeup With Competing Consumers", func(t *testing.T) { + // This test reproduces the lost wakeup bug with competing consumers. + // Scenario: Multiple PullBlocking consumers compete with a Seq consumer. + // When an item is pushed: + // 1. Signal fires + // 2. PullBlocking consumer wakes and pulls item (signal resets if queue empty) + // 3. Seq consumer wakes but queue is empty, should re-wait + // 4. Bug: Seq doesn't re-wait, it returns to top of loop and checks empty again + // + // With many iterations, we should see Seq miss items that were stolen. + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + seqReceived := make(chan int, 100) + pullReceived := make(chan int, 100) + pullDone := make(chan struct{}) + + const numItems = 10 + + // Start Seq() consumer + go func() { + for item := range q.Seq(ctx) { + seqReceived <- item + } + }() + + // Start competing PullBlocking consumer + go func() { + defer close(pullDone) + for { + item := q.PullBlocking(ctx) + select { + case <-ctx.Done(): + return + default: + pullReceived <- item + } + } + }() + + synctest.Wait() // Let both consumers start and block + + // Push items one at a time + for i := 1; i <= numItems; i++ { + q.Push(i) + synctest.Wait() // Let consumers race for the item + } + + cancel() // Stop consumers + synctest.Wait() + + <-pullDone + close(seqReceived) + close(pullReceived) + + seqItems := []int{} + for item := range seqReceived { + seqItems = append(seqItems, item) + } + + pullItems := []int{} + for item := range pullReceived { + pullItems = append(pullItems, item) + } + + // Both consumers should collectively receive all items + allItems := append(seqItems, pullItems...) + assert.Equal(t, numItems, len(allItems), "Total items received should equal items pushed") + + // Verify all items were received exactly once. + assert.ElementsMatch(t, itemsFromRange(numItems), allItems) + }) + }) + + t.Run("Seq Stale Not Empty Signal Does Not Spin", func(t *testing.T) { + testStaleNotEmptySignalDoesNotSpin(t, func(q *Queue[int], ctx context.Context) int { + for item := range q.Seq(ctx) { + return item + } + return 0 + }) + }) + + t.Run("Seq Push After Empty Pull Does Not Lose Item", func(t *testing.T) { + testPushAfterEmptyPullDoesNotLoseItem(t, func(q *Queue[int], ctx context.Context) int { + for item := range q.Seq(ctx) { + return item + } + return 0 + }) + }) +} + +func TestQueuePullBlocking(t *testing.T) { + t.Run("PullBlocking Empty Pull After Signal", func(t *testing.T) { + // This test verifies that PullBlocking() properly re-blocks when a signal + // occurs but another consumer removes the item before PullBlocking can pull it. + // Without proper blocking, PullBlocking would spin-loop after the empty pull. + testEmptyPullAfterSignal(t, "PullBlocking", func(q *Queue[int], ctx context.Context, itemReceived chan int, started chan struct{}) { + go func() { + close(started) + item := q.PullBlocking(ctx) + itemReceived <- item + }() + }) + }) + + t.Run("PullBlocking Cancellation", func(t *testing.T) { + // This test verifies that PullBlocking() checks waitable.Done() + // before blocking, not just after. If the waitable is already cancelled, + // PullBlocking should return immediately. + testCancellationWithEmptyQueue(t, func(ctx context.Context, q *Queue[int]) int { + return q.PullBlocking(ctx) + }) + }) + + t.Run("PullBlocking Stale Not Empty Signal Does Not Spin", func(t *testing.T) { + testStaleNotEmptySignalDoesNotSpin(t, func(q *Queue[int], ctx context.Context) int { + return q.PullBlocking(ctx) + }) + }) + + t.Run("PullBlocking Push After Empty Pull Does Not Lose Item", func(t *testing.T) { + testPushAfterEmptyPullDoesNotLoseItem(t, func(q *Queue[int], ctx context.Context) int { + return q.PullBlocking(ctx) + }) + }) +} + +// testEmptyPullAfterSignal is a shared test helper that verifies a consumer properly +// re-blocks when signaled but another consumer steals the item. This prevents spin-loops. +func testEmptyPullAfterSignal(t *testing.T, consumerName string, startConsumer func(*Queue[int], context.Context, chan int, chan struct{})) { + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + itemReceived := make(chan int, 1) + consumerStarted := make(chan struct{}) + + // Start consumer on empty queue. The started channel is closed before the + // first pull, so synctest.Wait below establishes that it is waiting. + startConsumer(q, ctx, itemReceived, consumerStarted) + + <-consumerStarted + synctest.Wait() + + q.Push(99) + pulled := q.Pull() + assert.Equal(t, 99, pulled) + + synctest.Wait() + select { + case <-itemReceived: + t.Fatalf("%s should not have received the pulled item", consumerName) + default: + } + + q.Push(42) + synctest.Wait() + select { + case item := <-itemReceived: + assert.Equal(t, 42, item) + default: + t.Fatal("expected to receive item from queue") + } + }) +} + +// testCancellationWithEmptyQueue is a shared test helper that verifies a consumer +// returns immediately when the waitable is already done. +func testCancellationWithEmptyQueue(t *testing.T, consume func(context.Context, *Queue[int]) int) { + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + select { + case <-ctx.Done(): + default: + t.Fatal("context should be cancelled") + } + + item := consume(ctx, q) + assert.Equal(t, 0, item, "should return zero value when cancelled") + }) +} + +// testStaleNotEmptySignalDoesNotSpin covers an empty queue whose notEmptySignal is +// already triggered. Waiters must clear that latch and block; otherwise synctest.Wait hangs. +func testStaleNotEmptySignalDoesNotSpin(t *testing.T, consume func(*Queue[int], context.Context) int) { + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + q.notEmptySignal.Signal() + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + got := make(chan int, 1) + started := make(chan struct{}) + go func() { + close(started) + got <- consume(q, ctx) + }() + <-started + synctest.Wait() + q.Push(7) + synctest.Wait() + select { + case item := <-got: + assert.Equal(t, 7, item) + default: + t.Fatal("consumer should have received the item after blocking") + } + }) +} + +// testPushAfterEmptyPullDoesNotLoseItem covers the window after pullWait observes +// an empty queue and before it enters select. synctest cannot preempt there, so +// afterEmptyPull injects the Push. +func testPushAfterEmptyPullDoesNotLoseItem(t *testing.T, consume func(*Queue[int], context.Context) int) { + synctest.Test(t, func(t *testing.T) { + q := NewQueue[int]() + q.afterEmptyPull = func() { + q.Push(7) + q.afterEmptyPull = nil + } + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + got := make(chan int, 1) + started := make(chan struct{}) + go func() { + close(started) + got <- consume(q, ctx) + }() + <-started + synctest.Wait() + + select { + case item := <-got: + assert.Equal(t, 7, item) + default: + t.Fatal("Push after empty pull was lost; item is queued but the waiter is blocked") + } + }) +} + +func itemsFromRange(count int) []int { + items := make([]int, count) + for i := range items { + items[i] = i + 1 + } + return items } From d7434804bdcd516b34f7471d1fdee87caade980a Mon Sep 17 00:00:00 2001 From: David Shrewsberry <99685630+dashrews78@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:47:18 -0400 Subject: [PATCH 283/638] chore(deprecation): Deprecate priority field (#22759) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649e554f6a6f9..49896cbc26fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-26281: block creation of new GCR integrations. Users are directed to use Google Artifact Registry instead. - ROX-35079: installation of the `app.k8s.io/v1beta1/Application` resource when central is installed is deprecated. It will be removed in a future release. +- The `priority` field on API responses for deployments, images, nodes, and components is deprecated and will be removed in a future release. Use the `risk_score` field on the same objects instead. Sorting by "Risk Priority" in search queries is also deprecated; sort by "Risk Score" instead. For clusters and namespaces, the `priority` field will be removed and replaced by a `risk_score` field where applicable. ### Technical Changes - ROX-36784: Scanner V4 node indexing on OpenShift now reads the host RPM database Claircore reports: SQLite on RHEL 9+ (`/usr/share/rpm`, `/usr/lib/sysimage/rpm`) and Berkeley DB on RHEL 8 (`/usr/share/rpm`, `/usr/lib/sysimage/rpm-ostree-base-db`). From 1e06242bb18124b433131e32b9632952e7a85649 Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Thu, 10 Sep 2026 21:29:17 -0700 Subject: [PATCH 284/638] chore(ui): Bump qs to 6.16.0 - fixes CVE-2026-82417 (#22630) --- ui/apps/platform/package-lock.json | 29 +++++++++++++++-------------- ui/apps/platform/package.json | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ui/apps/platform/package-lock.json b/ui/apps/platform/package-lock.json index d04b665316236..c383660b6f259 100644 --- a/ui/apps/platform/package-lock.json +++ b/ui/apps/platform/package-lock.json @@ -47,7 +47,7 @@ "object-resolve-path": "^1.1.1", "pluralize": "^8.0.0", "prop-types": "^15.7.2", - "qs": "^6.15.2", + "qs": "^6.16.0", "raven-for-redux": "^1.3.1", "raven-js": "^3.27.0", "react": "^18.2.0", @@ -15418,12 +15418,13 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -17290,14 +17291,14 @@ "integrity": "sha1-+w1xM49TKjoUkhNjni0yy+qLyyg= sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -17309,13 +17310,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" diff --git a/ui/apps/platform/package.json b/ui/apps/platform/package.json index 744d0c0545f98..6273d3a64457e 100644 --- a/ui/apps/platform/package.json +++ b/ui/apps/platform/package.json @@ -50,7 +50,7 @@ "object-resolve-path": "^1.1.1", "pluralize": "^8.0.0", "prop-types": "^15.7.2", - "qs": "^6.15.2", + "qs": "^6.16.0", "raven-for-redux": "^1.3.1", "raven-js": "^3.27.0", "react": "^18.2.0", From 67d24691364aac6757dfa25b7752a6b2c56d75df Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Thu, 10 Sep 2026 21:30:04 -0700 Subject: [PATCH 285/638] fix(ui): override js-yaml to 4.3.2 for CVE-2026-84375 master (#22627) Co-authored-by: Claude Opus 4.8 --- ui/apps/platform/package-lock.json | 106 ++++++++--------------------- ui/apps/platform/package.json | 3 +- 2 files changed, 30 insertions(+), 79 deletions(-) diff --git a/ui/apps/platform/package-lock.json b/ui/apps/platform/package-lock.json index c383660b6f259..5b559226ccd98 100644 --- a/ui/apps/platform/package-lock.json +++ b/ui/apps/platform/package-lock.json @@ -1550,13 +1550,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -1570,19 +1563,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/js": { "version": "9.39.3", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", @@ -2737,12 +2717,6 @@ "node": ">= 14" } }, - "node_modules/@redocly/openapi-core/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "node_modules/@redocly/openapi-core/node_modules/brace-expansion": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", @@ -2765,18 +2739,6 @@ "node": ">= 14" } }, - "node_modules/@redocly/openapi-core/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@redocly/openapi-core/node_modules/minimatch": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", @@ -5330,6 +5292,12 @@ } ] }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -11609,6 +11577,28 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk= sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -13683,13 +13673,6 @@ "mocha": ">=3.1.2" } }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true - }, "node_modules/mocha/node_modules/brace-expansion": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", @@ -13747,19 +13730,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", @@ -19931,13 +19901,6 @@ "@svgr/core": "*" } }, - "node_modules/vite-plugin-svgr/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/vite-plugin-svgr/node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -19985,19 +19948,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vite-plugin-svgr/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/vite-plugin-svgr/node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", diff --git a/ui/apps/platform/package.json b/ui/apps/platform/package.json index 6273d3a64457e..e43032dc8ed86 100644 --- a/ui/apps/platform/package.json +++ b/ui/apps/platform/package.json @@ -200,7 +200,8 @@ "connected-react-router": { "react": "^18.0.0" }, - "react-dropzone": "14.3.2" + "react-dropzone": "14.3.2", + "js-yaml": "^4.3.2" }, "browserslist": [ "Chrome >= 80" From ac29b25cae27b5253ee78c2cfc54e3baacb64ce7 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 11 Sep 2026 09:11:18 +0200 Subject: [PATCH 286/638] fix(ci): optimize Scanner V4 bundle loading for UI E2E tests (#22623) --- scripts/ci/jobs/gke_ui_e2e_tests.py | 3 +++ scripts/ci/jobs/ocp_ui_e2e_tests.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/scripts/ci/jobs/gke_ui_e2e_tests.py b/scripts/ci/jobs/gke_ui_e2e_tests.py index 12aaa2d80dd9c..a09dc8276720c 100755 --- a/scripts/ci/jobs/gke_ui_e2e_tests.py +++ b/scripts/ci/jobs/gke_ui_e2e_tests.py @@ -19,6 +19,9 @@ os.environ["OUTPUT_FORMAT"] = "helm" os.environ["SCANNER_V4_DB_STORAGE_CLASS"] = "stackrox-gke-ssd" os.environ["SENSOR_SCANNER_SUPPORT"] = "true" +# Optimize Scanner V4 startup time by loading only RHEL vulnerability bundles +# UI tests only scan RHEL/UBI images (StackRox components) - no user workloads deployed +os.environ["SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST"] = "rhel-vex,stackrox-rhel-csaf,manual,epss,nvd" ClusterTestRunner( cluster=GKECluster("ui-e2e-test"), diff --git a/scripts/ci/jobs/ocp_ui_e2e_tests.py b/scripts/ci/jobs/ocp_ui_e2e_tests.py index 28e5b281b4d58..f3e4dfe4eeb8b 100755 --- a/scripts/ci/jobs/ocp_ui_e2e_tests.py +++ b/scripts/ci/jobs/ocp_ui_e2e_tests.py @@ -15,6 +15,9 @@ os.environ["INSTALL_COMPLIANCE_OPERATOR"] = "true" os.environ["ORCHESTRATOR_FLAVOR"] = "openshift" os.environ["KUBERNETES_PROVIDER"] = "ocp" +# Optimize Scanner V4 startup time by loading only RHEL vulnerability bundles +# UI tests only scan RHEL/UBI images (OpenShift platform, StackRox, operators, nodes) +os.environ["SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST"] = "rhel-vex,stackrox-rhel-csaf,manual,epss,nvd" ClusterTestRunner( cluster=AutomationFlavorsCluster(), From a77f84a53cf85804324c5eb06d6a77075ef41f97 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Fri, 11 Sep 2026 03:57:54 -0500 Subject: [PATCH 287/638] ROX-36951: Node config reports all jobs tab (#22795) --- .../Configurations/View/NodeReportJobs.tsx | 159 ++++++++++++++++++ .../View/NodeReportJobsTable.tsx | 140 +++++++++++++++ .../View/NodeVulnerabilityReportViewPage.tsx | 144 ++++++++++++++-- 3 files changed, 433 insertions(+), 10 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobs.tsx create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobsTable.tsx diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobs.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobs.tsx new file mode 100644 index 0000000000000..71e36325a0b25 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobs.tsx @@ -0,0 +1,159 @@ +import { useCallback, useMemo } from 'react'; +import { + Pagination, + Toolbar, + ToolbarContent, + ToolbarItem, + useInterval, +} from '@patternfly/react-core'; + +import MyJobsFilter from 'Components/ReportJob/MyJobsFilter'; +import ReportJobStatusFilter, { + isReportJobStatus, +} from 'Components/ReportJob/ReportJobStatusFilter'; +import type { ReportJobStatus } from 'Components/ReportJob/types'; +import useRestQuery from 'hooks/useRestQuery'; +import useURLPagination from 'hooks/useURLPagination'; +import useURLSearch from 'hooks/useURLSearch'; +import useURLSort from 'hooks/useURLSort'; +import useURLStringUnion from 'hooks/useURLStringUnion'; +import { fetchMyNodeReportHistory, fetchNodeReportHistory } from 'services/NodeReportsService'; +import { toggleItemInArray } from 'utils/arrayUtils'; +import { ensureBoolean, ensureStringArray } from 'utils/ensure'; +import { getTableUIState } from 'utils/getTableUIState'; + +import NodeReportJobsTable from './NodeReportJobsTable'; +import { getRequestQueryString } from '../../../../VulnerablityReporting/api/apiUtils'; + +export type NodeReportJobsProps = { + reportId: string; +}; + +const sortOptions = { + sortFields: ['Report Completion Time'], + defaultSortOption: { field: 'Report Completion Time', direction: 'desc' } as const, +}; + +const reportStateByJobStatus: Partial> = { + WAITING: 'WAITING', + PREPARING: 'PREPARING', + ERROR: 'FAILURE', + DOWNLOAD_GENERATED: 'GENERATED', + EMAIL_DELIVERED: 'DELIVERED', +}; + +function NodeReportJobs({ reportId }: NodeReportJobsProps) { + const { page, perPage, setPage, setPerPage } = useURLPagination(10); + const { sortOption, getSortParams } = useURLSort(sortOptions); + const { searchFilter, setSearchFilter } = useURLSearch(); + const [isViewingOnlyMyJobs, setIsViewingOnlyMyJobs] = useURLStringUnion('viewOnlyMyJobs', [ + 'false', + 'true', + ]); + + const reportJobStatusFilters = useMemo(() => { + return ensureStringArray(searchFilter['Report Job Status']).filter(isReportJobStatus); + }, [searchFilter]); + + const fetchNodeReportsHistoryCallback = useCallback(() => { + const fetchHistory = + isViewingOnlyMyJobs === 'true' ? fetchMyNodeReportHistory : fetchNodeReportHistory; + + const query = getRequestQueryString({ + 'Report state': reportJobStatusFilters.flatMap( + (status) => reportStateByJobStatus[status] ?? [] + ), + }); + + return fetchHistory({ + id: reportId, + query, + page, + perPage, + sortOption, + }); + }, [reportId, reportJobStatusFilters, page, perPage, sortOption, isViewingOnlyMyJobs]); + + const { data, isLoading, error, refetch } = useRestQuery(fetchNodeReportsHistoryCallback, { + clearErrorBeforeRequest: false, + }); + + const tableState = getTableUIState({ + isLoading, + data, + error, + searchFilter, + isPolling: true, + }); + + const onReportJobStatusFilterChange = (_checked: boolean, selectedStatus: ReportJobStatus) => { + const newFilters = toggleItemInArray(reportJobStatusFilters, selectedStatus); + setSearchFilter({ + ...searchFilter, + 'Report Job Status': newFilters, + }); + setPage(1); + }; + + const onMyJobsFilterChange = (checked: boolean) => { + setIsViewingOnlyMyJobs(String(checked)); + setPage(1); + }; + + useInterval(refetch, 10000); + + return ( + <> + + + + + + + + + + ( + + + {firstIndex} - {lastIndex} + {' '} + of many + + )} + page={page} + perPage={perPage} + onSetPage={(_, newPage) => setPage(newPage)} + onPerPageSelect={(_, newPerPage) => setPerPage(newPerPage)} + isCompact + /> + + + + { + setSearchFilter({}); + setPage(1); + }} + /> + + ); +} + +export default NodeReportJobs; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobsTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobsTable.tsx new file mode 100644 index 0000000000000..82ed39d09a5c7 --- /dev/null +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeReportJobsTable.tsx @@ -0,0 +1,140 @@ +import { Flex, FlexItem } from '@patternfly/react-core'; +import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; + +import ReportJobStatus from 'Components/ReportJob/ReportJobStatus'; +import { onDownloadReport } from 'Components/ReportJob/utils'; +import TbodyUnified from 'Components/TableStateTemplates/TbodyUnified'; +import useAuthStatus from 'hooks/useAuthStatus'; +import useSet from 'hooks/useSet'; +import type { GetSortParams } from 'hooks/useURLSort'; +import { nodeReportDownloadURL } from 'services/NodeReportsService'; +import type { + ConfiguredReportSnapshot, + NodeVulnerabilityReportConfiguration, +} from 'services/ReportsService.types'; +import { getDateTime } from 'utils/dateUtils'; +import type { TableUIState } from 'utils/getTableUIState'; + +import NodeVulnerabilityReportView from './NodeVulnerabilityReportView'; +import { + attributesSeparateFromConfigForNodeVulnerabilityReport, + searchFilterConfigForNodeVulnerabilityReport, +} from '../../../../searchFilterConfig'; +import JobDetails from '../../../../VulnerablityReporting/ViewVulnReport/JobDetails'; + +export type NodeReportJobsTableProps = { + tableState: TableUIState; + getSortParams: GetSortParams; + onClearFilters: () => void; +}; + +function NodeReportJobsTable({ + tableState, + getSortParams, + onClearFilters, +}: NodeReportJobsTableProps) { + const { currentUser } = useAuthStatus(); + const expandedRowSet = useSet(); + + return ( +
    DescriptionMy last job status}
    {report.description || '-'} ROX-36102 + + { - event.preventDefault(); - openDeleteModal([report.id]); - }, - }, - ]} + items={rowActions} + isDisabled={isRunning} />
    + + + + + + + + + data.map((snapshot, rowIndex) => { + const { user, reportStatus, isDownloadAvailable, reportJobId, name } = + snapshot; + const areDownloadActionsDisabled = currentUser.userId !== user.id; + const isExpanded = expandedRowSet.has(reportJobId); + + return ( + + + + + + + + + + + ); + }) + } + /> +
    + Job statusCompletedRequester
    expandedRowSet.toggle(reportJobId), + }} + /> + + + onDownloadReport({ + reportJobId, + name, + completedAt: reportStatus.completedAt, + baseDownloadURL: nodeReportDownloadURL, + }) + } + /> + + {reportStatus.completedAt + ? getDateTime(reportStatus.completedAt) + : '-'} + {user.name}
    + + + + + + + + + + +
    + ); +} + +export default NodeReportJobsTable; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx index 0eb49df23eb2c..b25829af152f6 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/View/NodeVulnerabilityReportViewPage.tsx @@ -1,6 +1,9 @@ import { useCallback } from 'react'; import { generatePath, useNavigate, useParams } from 'react-router-dom-v5-compat'; import { + Alert, + AlertActionCloseButton, + AlertGroup, Breadcrumb, BreadcrumbItem, Bullseye, @@ -10,6 +13,9 @@ import { FlexItem, PageSection, Spinner, + Tab, + TabTitleText, + Tabs, Title, } from '@patternfly/react-core'; @@ -17,8 +23,13 @@ import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import NotFoundMessage from 'Components/NotFoundMessage/NotFoundMessage'; import PageTitle from 'Components/PageTitle'; import MenuDropdown from 'Components/PatternFly/MenuDropdown'; +import ReportJobsHelpAction from 'Components/ReportJob/ReportJobsHelpAction'; +import { jobContextTabs } from 'Components/ReportJob/types'; +import useToasts from 'hooks/patternfly/useToasts'; +import type { Toast } from 'hooks/patternfly/useToasts'; import usePermissions from 'hooks/usePermissions'; import useRestQuery from 'hooks/useRestQuery'; +import useURLStringUnion from 'hooks/useURLStringUnion'; import { vulnerabilityNodeConfigurationsReportsDetailsPath, vulnerabilityNodeConfigurationsReportsPath, @@ -26,21 +37,30 @@ import { } from 'routePaths'; import { deleteNodeReportConfiguration, + fetchMyNodeReportHistory, fetchNodeReportConfiguration, + runNodeReport, } from 'services/NodeReportsService'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; import DeleteReportsModal from '../../../components/DeleteReportsModal'; +import NodeReportJobs from './NodeReportJobs'; import NodeVulnerabilityReportView from './NodeVulnerabilityReportView'; import { attributesSeparateFromConfigForNodeVulnerabilityReport, searchFilterConfigForNodeVulnerabilityReport, } from '../../../../searchFilterConfig'; +import useRunReport from '../../../../VulnerablityReporting/api/useRunReport'; +import { useWatchLastSnapshotForReports } from '../../../../VulnerablityReporting/api/useWatchLastSnapshotForReports'; import useDeleteModal from '../../../../VulnerablityReporting/hooks/useDeleteModal'; +const configDetailsTabId = 'NodeVulnReportsConfigDetails'; +const allReportJobsTabId = 'NodeVulnReportsConfigReportJobs'; + function NodeVulnerabilityReportViewPage() { const navigate = useNavigate(); const { reportId } = useParams() as { reportId: string }; + const [selectedTab, setSelectedTab] = useURLStringUnion('reportJobsTab', jobContextTabs); const { hasReadWriteAccess, hasReadAccess } = usePermissions(); const hasDeleteAccessForReport = hasReadWriteAccess('WorkflowAdministration'); @@ -57,6 +77,28 @@ function NodeVulnerabilityReportViewPage() { error: fetchError, } = useRestQuery(fetchReportConfigurationCallback); + const { reportSnapshots } = useWatchLastSnapshotForReports( + reportConfiguration ?? null, + fetchMyNodeReportHistory + ); + const reportSnapshot = reportSnapshots[reportId]; + + const { toasts, addToast, removeToast } = useToasts(); + + const { isRunning, runError, runReport } = useRunReport({ + runReportRequest: runNodeReport, + onCompleted: ({ reportNotificationMethod }) => { + if (reportNotificationMethod === 'EMAIL') { + addToast('The report has been sent to the configured email notifier', 'success'); + } else if (reportNotificationMethod === 'DOWNLOAD') { + addToast( + 'The report generation has started and will be available for download once complete', + 'success' + ); + } + }, + }); + const { openDeleteModal, isDeleteModalOpen, @@ -76,8 +118,34 @@ function NodeVulnerabilityReportViewPage() { reportId, }); + const isReportStatusPending = + reportSnapshot?.reportStatus.runState === 'PREPARING' || + reportSnapshot?.reportStatus.runState === 'WAITING'; + return ( <> + + {toasts.map(({ key, variant, title, children }: Toast) => ( + removeToast(key)} + actionClose={ + removeToast(key)} + /> + } + > + {children} + + ))} + + {runError && } {isLoading ? ( @@ -119,10 +187,37 @@ function NodeVulnerabilityReportViewPage() { onClick={() => navigate(`${reportPageURL}?action=edit`) } + isDisabled={isReportStatusPending || isRunning} > Edit report )} + {hasWriteAccessForReport && } + {hasWriteAccessForReport && ( + runReport(reportId, 'EMAIL')} + isDisabled={ + isReportStatusPending || + isRunning || + reportConfiguration.notifiers.length === 0 + } + description={ + reportConfiguration.notifiers.length === 0 + ? 'No delivery destinations set' + : '' + } + > + Send report + + )} + {hasWriteAccessForReport && ( + runReport(reportId, 'DOWNLOAD')} + isDisabled={isReportStatusPending || isRunning} + > + Generate download + + )} {hasWriteAccessForReport && ( @@ -152,17 +247,46 @@ function NodeVulnerabilityReportViewPage() { )}
    - - + + { + setSelectedTab(tab); + }} + aria-label="Node report details tabs" + usePageInsets + > + Configuration details} + /> + All report jobs} + actions={} + /> + + {selectedTab === 'CONFIGURATION_DETAILS' && ( + + + + )} + {selectedTab === 'ALL_REPORT_JOBS' && ( + + + + )} Date: Fri, 11 Sep 2026 02:32:57 -0700 Subject: [PATCH 288/638] fix(be): Default CvesSince to AllVuln for view-based node reports (#22682) Co-authored-by: Claude Opus 4.6 --- central/reports/validation/validate.go | 7 + .../validation/validate_comprehensive_test.go | 151 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/central/reports/validation/validate.go b/central/reports/validation/validate.go index 03418c09311e6..fef9a6ea322fe 100644 --- a/central/reports/validation/validate.go +++ b/central/reports/validation/validate.go @@ -692,6 +692,13 @@ func (v *Validator) ValidateAndGenerateViewBasedReportRequest( return nil, errox.InvalidArgs.New("node vulnerability report filters must be provided") } + // Node vulnerability view based reports should default to allVuln in CVEs since time filter if not provided. + if nodeFilters.GetCvesSince() == nil { + nodeFilters.CvesSince = &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + } + } + if err := v.validateNodeFilters(nodeFilters); err != nil { return nil, err } diff --git a/central/reports/validation/validate_comprehensive_test.go b/central/reports/validation/validate_comprehensive_test.go index 54a53138907c0..868227e87a24f 100644 --- a/central/reports/validation/validate_comprehensive_test.go +++ b/central/reports/validation/validate_comprehensive_test.go @@ -11,6 +11,7 @@ import ( apiV2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/features" + authnMocks "github.com/stackrox/rox/pkg/grpc/authn/mocks" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/sac/resources" "github.com/stretchr/testify/assert" @@ -708,3 +709,153 @@ func nodeViewBasedSnapshot(userID string) *storage.ReportSnapshot { }, } } + +func TestValidateAndGenerateViewBasedReportRequest(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + collectionDS := collectionDSMocks.NewMockDataStore(ctrl) + reportConfigDS := reportConfigDSMocks.NewMockDataStore(ctrl) + snapshotDS := snapshotDSMocks.NewMockDataStore(ctrl) + + validator := New(reportConfigDS, snapshotDS, collectionDS, notifierDS) + + identity := authnMocks.NewMockIdentity(ctrl) + identity.EXPECT().UID().Return("user-1").AnyTimes() + identity.EXPECT().FullName().Return("John Doe").AnyTimes() + identity.EXPECT().FriendlyName().Return("jdoe").AnyTimes() + identity.EXPECT().Roles().Return(nil).AnyTimes() + + tests := map[string]struct { + req *apiV2.ReportRequestViewBased + expectError bool + errContains string + // verify runs on the generated snapshot when no error is expected. + verify func(t *testing.T, snapshot *storage.ReportSnapshot) + }{ + "nil request fails": { + req: nil, + expectError: true, + errContains: "empty request", + }, + "unsupported report type fails": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_ReportType(-1), + }, + expectError: true, + errContains: "unsupported report type", + }, + "vulnerability report without filters fails": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_VULNERABILITY, + }, + expectError: true, + errContains: "view-based vulnerability report filters must be provided", + }, + "vulnerability report with filters succeeds": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_ViewBasedVulnReportFilters{ + ViewBasedVulnReportFilters: &apiV2.ViewBasedVulnerabilityReportFilters{ + Query: "Cluster:prod", + }, + }, + AreaOfConcern: "User Workloads", + }, + verify: func(t *testing.T, snapshot *storage.ReportSnapshot) { + assert.Equal(t, storage.ReportSnapshot_VULNERABILITY, snapshot.GetType()) + assert.Equal(t, "User Workloads", snapshot.GetAreaOfConcern()) + assert.Equal(t, "Cluster:prod", snapshot.GetViewBasedVulnReportFilters().GetQuery()) + }, + }, + "node report without filters fails": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + }, + expectError: true, + errContains: "node vulnerability report filters must be provided", + }, + "node report without CVE time filter defaults to all vulnerabilities": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "Cluster:prod", + }, + }, + }, + verify: func(t *testing.T, snapshot *storage.ReportSnapshot) { + filters := snapshot.GetNodeVulnReportFilters() + assert.Equal(t, "Cluster:prod", filters.GetQuery()) + assert.True(t, filters.GetAllVuln()) + }, + }, + "node report with explicit all vulnerabilities filter is preserved": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: true, + }, + }, + }, + }, + verify: func(t *testing.T, snapshot *storage.ReportSnapshot) { + assert.True(t, snapshot.GetNodeVulnReportFilters().GetAllVuln()) + }, + }, + "node report with all vulnerabilities set to false is not overridden": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + CvesSince: &apiV2.NodeVulnerabilityReportFilters_AllVuln{ + AllVuln: false, + }, + }, + }, + }, + verify: func(t *testing.T, snapshot *storage.ReportSnapshot) { + assert.False(t, snapshot.GetNodeVulnReportFilters().GetAllVuln()) + }, + }, + "node report with invalid query fails": { + req: &apiV2.ReportRequestViewBased{ + Type: apiV2.ReportRequestViewBased_NODE_VULNERABILITY, + Filter: &apiV2.ReportRequestViewBased_NodeVulnReportFilters{ + NodeVulnReportFilters: &apiV2.NodeVulnerabilityReportFilters{ + Query: "invalid query syntax [", + }, + }, + }, + expectError: true, + errContains: "invalid query", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result, err := validator.ValidateAndGenerateViewBasedReportRequest(tc.req, identity) + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + assert.Nil(t, result) + return + } + require.NoError(t, err) + require.NotNil(t, result) + + snapshot := result.ReportSnapshot + require.NotNil(t, snapshot) + assert.Equal(t, storage.ReportStatus_WAITING, snapshot.GetReportStatus().GetRunState()) + assert.Equal(t, storage.ReportStatus_VIEW_BASED, snapshot.GetReportStatus().GetReportRequestType()) + assert.Equal(t, storage.ReportStatus_DOWNLOAD, snapshot.GetReportStatus().GetReportNotificationMethod()) + assert.Equal(t, "user-1", snapshot.GetRequester().GetId()) + assert.Equal(t, "John Doe", snapshot.GetRequester().GetName()) + assert.NotEmpty(t, snapshot.GetName()) + tc.verify(t, snapshot) + }) + } +} From a8f1e989eb5ffab02f852f7ebf276986b7cb0faa Mon Sep 17 00:00:00 2001 From: David House <105243888+davdhacs@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:10:04 -0600 Subject: [PATCH 289/638] ROX-36967: remove ocm token test (#22804) --- qa-tests-backend/src/test/groovy/CloudSourcesTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/qa-tests-backend/src/test/groovy/CloudSourcesTest.groovy b/qa-tests-backend/src/test/groovy/CloudSourcesTest.groovy index 34827d0933a02..ed80fd481fd6a 100644 --- a/qa-tests-backend/src/test/groovy/CloudSourcesTest.groovy +++ b/qa-tests-backend/src/test/groovy/CloudSourcesTest.groovy @@ -49,7 +49,6 @@ class CloudSourcesTest extends BaseSpecification { where: authMethod | token | clientId | clientSecret - "OCM offline token" | Env.mustGetOcmOfflineToken() | "" | "" "OCM service account" | "" | Env.mustGetOcmClientId() | Env.mustGetOcmClientSecret() } } From ce6284187be68b38c25d5bd6d11b470a47690fe0 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Fri, 11 Sep 2026 12:44:08 -0700 Subject: [PATCH 290/638] ROX-36125: Add lightspeed status endpoint (#22635) --- .../service/risk_ai_summary_test.go | 4 + central/lightspeed/client/client.go | 44 +-- central/metadata/service/service.go | 12 +- central/metadata/service/service_impl.go | 31 +- generated/api/v1/metadata_service.pb.go | 107 ++++-- generated/api/v1/metadata_service.pb.gw.go | 60 ++++ .../api/v1/metadata_service.swagger.json | 34 ++ generated/api/v1/metadata_service_grpc.pb.go | 40 +++ .../api/v1/metadata_service_vtproto.pb.go | 317 ++++++++++++++++++ pkg/tlsutils/service_ca.go | 41 +++ proto/api/v1/metadata_service.proto | 10 + 11 files changed, 646 insertions(+), 54 deletions(-) create mode 100644 pkg/tlsutils/service_ca.go diff --git a/central/deployment/service/risk_ai_summary_test.go b/central/deployment/service/risk_ai_summary_test.go index 58a169b4371ad..2e00a58c146e3 100644 --- a/central/deployment/service/risk_ai_summary_test.go +++ b/central/deployment/service/risk_ai_summary_test.go @@ -30,6 +30,10 @@ func (m *mockOLSClient) Query(_ context.Context, req *olsClient.QueryRequest) (* return m.response, m.err } +func (m *mockOLSClient) TestConnectivity() error { + return m.err +} + func TestGetDeploymentRiskAISummary_Success(t *testing.T) { ctrl := gomock.NewController(t) mockDS := deploymentMocks.NewMockDataStore(ctrl) diff --git a/central/lightspeed/client/client.go b/central/lightspeed/client/client.go index abf1aa53e90e1..867f400ffe27d 100644 --- a/central/lightspeed/client/client.go +++ b/central/lightspeed/client/client.go @@ -3,18 +3,16 @@ package client import ( "bytes" "context" - "crypto/tls" - "crypto/x509" "encoding/json" "fmt" "net/http" - "os" "time" "github.com/pkg/errors" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/httputil/proxy" "github.com/stackrox/rox/pkg/satoken" + "github.com/stackrox/rox/pkg/tlsutils" "github.com/stackrox/rox/pkg/utils" ) @@ -42,16 +40,20 @@ type QueryResponse struct { // Client calls the OpenShift Lightspeed API. type Client interface { Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error) + TestConnectivity() error } // NewClient creates a Client that authenticates with the OLS API using the // pod's Kubernetes service account token and trusts the service CA. func NewClient() Client { + transport := tlsutils.TransportWithServiceCA() + transport.Proxy = proxy.FromConfig() + return &clientImpl{ loadToken: satoken.LoadTokenFromFile, endpoint: LightspeedEndpoint.Setting(), httpClient: &http.Client{ - Transport: transportWithServiceCA(), + Transport: transport, Timeout: defaultRequestTimeout, }, } @@ -63,24 +65,6 @@ type clientImpl struct { httpClient *http.Client } -func transportWithServiceCA() http.RoundTripper { - rootCAs, err := x509.SystemCertPool() - if err != nil { - rootCAs = x509.NewCertPool() - } - - if serviceCA, err := os.ReadFile(serviceOperatorCAPath); err == nil { - rootCAs.AppendCertsFromPEM(serviceCA) - } - - return &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: rootCAs, - }, - Proxy: proxy.FromConfig(), - } -} - func (c *clientImpl) Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error) { token, err := c.loadToken() if err != nil { @@ -118,3 +102,19 @@ func (c *clientImpl) Query(ctx context.Context, req *QueryRequest) (*QueryRespon return &result, nil } + +// TestConnectivity checks if the Lightspeed service is reachable. +func (c *clientImpl) TestConnectivity() error { + healthURL := fmt.Sprintf("%s/readiness", c.endpoint) + resp, err := c.httpClient.Get(healthURL) + if err != nil { + return errors.Wrap(err, "failed to connect to AI service") + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("AI service returned status %d", resp.StatusCode) + } + + return nil +} diff --git a/central/metadata/service/service.go b/central/metadata/service/service.go index c1e18a07c8fd6..ab17f66d74b27 100644 --- a/central/metadata/service/service.go +++ b/central/metadata/service/service.go @@ -4,6 +4,7 @@ import ( "context" "github.com/stackrox/rox/central/globaldb" + olsClient "github.com/stackrox/rox/central/lightspeed/client" systemInfoStorage "github.com/stackrox/rox/central/systeminfo/store/postgres" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/pkg/grpc" @@ -24,14 +25,15 @@ type Service interface { // New returns a new instance of service. func New() Service { - return NewWithCertificateProvider(&defaultCertificateProvider{}) + return NewWithCertificateProvider(&defaultCertificateProvider{}, olsClient.NewClient()) } // NewWithCertificateProvider returns a new instance of service with a custom certificate provider. -func NewWithCertificateProvider(certProvider CertificateProvider) Service { +func NewWithCertificateProvider(certProvider CertificateProvider, lightspeedClient olsClient.Client) Service { return &serviceImpl{ - db: globaldb.GetPostgres(), - systemInfoStore: systemInfoStorage.Singleton(), - certProvider: certProvider, + db: globaldb.GetPostgres(), + systemInfoStore: systemInfoStorage.Singleton(), + certProvider: certProvider, + lightspeedClient: lightspeedClient, } } diff --git a/central/metadata/service/service_impl.go b/central/metadata/service/service_impl.go index 6fd1140ee89a4..3f88b23ae1037 100644 --- a/central/metadata/service/service_impl.go +++ b/central/metadata/service/service_impl.go @@ -12,6 +12,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/pkg/errors" "github.com/stackrox/rox/central/globaldb" + olsClient "github.com/stackrox/rox/central/lightspeed/client" "github.com/stackrox/rox/central/metadata/centralcapabilities" systemInfoStorage "github.com/stackrox/rox/central/systeminfo/store/postgres" "github.com/stackrox/rox/central/tlsconfig" @@ -42,6 +43,7 @@ var ( v1.MetadataService_GetDatabaseStatus_FullMethodName, v1.MetadataService_GetDatabaseBackupStatus_FullMethodName, v1.MetadataService_GetCentralCapabilities_FullMethodName, + v1.MetadataService_GetLightspeedStatus_FullMethodName, }, // When this endpoint was public, Sensor relied on it to check Central's // availability. While Sensor might not do so today, we need to ensure @@ -177,9 +179,10 @@ func issueSecondaryCALeafCert(certProvider CertificateProvider) (tls.Certificate type serviceImpl struct { v1.UnimplementedMetadataServiceServer - db postgres.DB - systemInfoStore systemInfoStorage.Store - certProvider CertificateProvider + db postgres.DB + systemInfoStore systemInfoStorage.Store + certProvider CertificateProvider + lightspeedClient olsClient.Client } // RegisterServiceServer registers this service with the given gRPC Server. @@ -368,3 +371,25 @@ func (s *serviceImpl) GetDatabaseBackupStatus(ctx context.Context, _ *v1.Empty) func (s *serviceImpl) GetCentralCapabilities(_ context.Context, _ *v1.Empty) (*v1.CentralServicesCapabilities, error) { return centralcapabilities.GetCentralCapabilities(), nil } + +// GetLightspeedStatus returns the availability status of the Lightspeed AI service. +func (s *serviceImpl) GetLightspeedStatus(_ context.Context, _ *v1.Empty) (*v1.LightspeedStatusResponse, error) { + if s.lightspeedClient == nil { + return &v1.LightspeedStatusResponse{ + Available: false, + Message: "Lightspeed client not configured", + }, nil + } + + if err := s.lightspeedClient.TestConnectivity(); err != nil { + return &v1.LightspeedStatusResponse{ + Available: false, + Message: errors.Wrap(err, "Lightspeed AI service connectivity test failed").Error(), + }, nil + } + + return &v1.LightspeedStatusResponse{ + Available: true, + Message: "Lightspeed AI service is available", + }, nil +} diff --git a/generated/api/v1/metadata_service.pb.go b/generated/api/v1/metadata_service.pb.go index a2fe9cff432c5..a0c8c7b88991c 100644 --- a/generated/api/v1/metadata_service.pb.go +++ b/generated/api/v1/metadata_service.pb.go @@ -180,7 +180,7 @@ func (x CentralServicesCapabilities_CapabilityStatus) Number() protoreflect.Enum // Deprecated: Use CentralServicesCapabilities_CapabilityStatus.Descriptor instead. func (CentralServicesCapabilities_CapabilityStatus) EnumDescriptor() ([]byte, []int) { - return file_api_v1_metadata_service_proto_rawDescGZIP(), []int{6, 0} + return file_api_v1_metadata_service_proto_rawDescGZIP(), []int{7, 0} } type Metadata struct { @@ -569,6 +569,58 @@ func (x *DatabaseBackupStatus) GetBackupInfo() *storage.BackupInfo { return nil } +type LightspeedStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Available bool `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LightspeedStatusResponse) Reset() { + *x = LightspeedStatusResponse{} + mi := &file_api_v1_metadata_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LightspeedStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LightspeedStatusResponse) ProtoMessage() {} + +func (x *LightspeedStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_metadata_service_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LightspeedStatusResponse.ProtoReflect.Descriptor instead. +func (*LightspeedStatusResponse) Descriptor() ([]byte, []int) { + return file_api_v1_metadata_service_proto_rawDescGZIP(), []int{6} +} + +func (x *LightspeedStatusResponse) GetAvailable() bool { + if x != nil { + return x.Available + } + return false +} + +func (x *LightspeedStatusResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + // Provides availability of certain functionality of Central Services in the current configuration. // The initial intended use is to disable certain functionality that does not make sense in the Cloud Service context. type CentralServicesCapabilities struct { @@ -595,7 +647,7 @@ type CentralServicesCapabilities struct { func (x *CentralServicesCapabilities) Reset() { *x = CentralServicesCapabilities{} - mi := &file_api_v1_metadata_service_proto_msgTypes[6] + mi := &file_api_v1_metadata_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -607,7 +659,7 @@ func (x *CentralServicesCapabilities) String() string { func (*CentralServicesCapabilities) ProtoMessage() {} func (x *CentralServicesCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_metadata_service_proto_msgTypes[6] + mi := &file_api_v1_metadata_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -620,7 +672,7 @@ func (x *CentralServicesCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use CentralServicesCapabilities.ProtoReflect.Descriptor instead. func (*CentralServicesCapabilities) Descriptor() ([]byte, []int) { - return file_api_v1_metadata_service_proto_rawDescGZIP(), []int{6} + return file_api_v1_metadata_service_proto_rawDescGZIP(), []int{7} } func (x *CentralServicesCapabilities) GetCentralScanningCanUseContainerIamRoleForEcr() CentralServicesCapabilities_CapabilityStatus { @@ -702,7 +754,10 @@ const file_api_v1_metadata_service_proto_rawDesc = "" + "PostgresDB\x10\x02\"L\n" + "\x14DatabaseBackupStatus\x124\n" + "\vbackup_info\x18\x01 \x01(\v2\x13.storage.BackupInfoR\n" + - "backupInfo\"\x90\x06\n" + + "backupInfo\"R\n" + + "\x18LightspeedStatusResponse\x12\x1c\n" + + "\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"\x90\x06\n" + "\x1bCentralServicesCapabilities\x12\x9a\x01\n" + "3central_scanning_can_use_container_iam_role_for_ecr\x18\x01 \x01(\x0e20.v1.CentralServicesCapabilities.CapabilityStatusR+centralScanningCanUseContainerIamRoleForEcr\x12\x89\x01\n" + ")central_can_use_cloud_backup_integrations\x18\x02 \x01(\x0e20.v1.CentralServicesCapabilities.CapabilityStatusR$centralCanUseCloudBackupIntegrations\x12\x91\x01\n" + @@ -711,13 +766,14 @@ const file_api_v1_metadata_service_proto_rawDesc = "" + "'central_can_use_acscs_email_integration\x18\x05 \x01(\x0e20.v1.CentralServicesCapabilities.CapabilityStatusR\"centralCanUseAcscsEmailIntegration\"C\n" + "\x10CapabilityStatus\x12\x17\n" + "\x13CapabilityAvailable\x10\x00\x12\x16\n" + - "\x12CapabilityDisabled\x10\x012\xc1\x03\n" + + "\x12CapabilityDisabled\x10\x012\xa0\x04\n" + "\x0fMetadataService\x12<\n" + "\vGetMetadata\x12\t.v1.Empty\x1a\f.v1.Metadata\"\x14\x82\xd3\xe4\x93\x02\x0e\x12\f/v1/metadata\x12\\\n" + "\fTLSChallenge\x12\x17.v1.TLSChallengeRequest\x1a\x18.v1.TLSChallengeResponse\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/tls-challenge\x12O\n" + "\x11GetDatabaseStatus\x12\t.v1.Empty\x1a\x12.v1.DatabaseStatus\"\x1b\x82\xd3\xe4\x93\x02\x15\x12\x13/v1/database/status\x12Y\n" + "\x17GetDatabaseBackupStatus\x12\t.v1.Empty\x1a\x18.v1.DatabaseBackupStatus\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/backup/status\x12f\n" + - "\x16GetCentralCapabilities\x12\t.v1.Empty\x1a\x1f.v1.CentralServicesCapabilities\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/central-capabilitiesB'\n" + + "\x16GetCentralCapabilities\x12\t.v1.Empty\x1a\x1f.v1.CentralServicesCapabilities\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/central-capabilities\x12]\n" + + "\x13GetLightspeedStatus\x12\t.v1.Empty\x1a\x1c.v1.LightspeedStatusResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/lightspeed/statusB'\n" + "\x18io.stackrox.proto.api.v1Z\v./api/v1;v1X\x01b\x06proto3" var ( @@ -733,7 +789,7 @@ func file_api_v1_metadata_service_proto_rawDescGZIP() []byte { } var file_api_v1_metadata_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_api_v1_metadata_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_api_v1_metadata_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_api_v1_metadata_service_proto_goTypes = []any{ (Metadata_LicenseStatus)(0), // 0: v1.Metadata.LicenseStatus (DatabaseStatus_DatabaseType)(0), // 1: v1.DatabaseStatus.DatabaseType @@ -744,31 +800,34 @@ var file_api_v1_metadata_service_proto_goTypes = []any{ (*TLSChallengeRequest)(nil), // 6: v1.TLSChallengeRequest (*DatabaseStatus)(nil), // 7: v1.DatabaseStatus (*DatabaseBackupStatus)(nil), // 8: v1.DatabaseBackupStatus - (*CentralServicesCapabilities)(nil), // 9: v1.CentralServicesCapabilities - (*storage.BackupInfo)(nil), // 10: storage.BackupInfo - (*Empty)(nil), // 11: v1.Empty + (*LightspeedStatusResponse)(nil), // 9: v1.LightspeedStatusResponse + (*CentralServicesCapabilities)(nil), // 10: v1.CentralServicesCapabilities + (*storage.BackupInfo)(nil), // 11: storage.BackupInfo + (*Empty)(nil), // 12: v1.Empty } var file_api_v1_metadata_service_proto_depIdxs = []int32{ 0, // 0: v1.Metadata.license_status:type_name -> v1.Metadata.LicenseStatus 1, // 1: v1.DatabaseStatus.database_type:type_name -> v1.DatabaseStatus.DatabaseType - 10, // 2: v1.DatabaseBackupStatus.backup_info:type_name -> storage.BackupInfo + 11, // 2: v1.DatabaseBackupStatus.backup_info:type_name -> storage.BackupInfo 2, // 3: v1.CentralServicesCapabilities.central_scanning_can_use_container_iam_role_for_ecr:type_name -> v1.CentralServicesCapabilities.CapabilityStatus 2, // 4: v1.CentralServicesCapabilities.central_can_use_cloud_backup_integrations:type_name -> v1.CentralServicesCapabilities.CapabilityStatus 2, // 5: v1.CentralServicesCapabilities.central_can_display_declarative_config_health:type_name -> v1.CentralServicesCapabilities.CapabilityStatus 2, // 6: v1.CentralServicesCapabilities.central_can_update_cert:type_name -> v1.CentralServicesCapabilities.CapabilityStatus 2, // 7: v1.CentralServicesCapabilities.central_can_use_acscs_email_integration:type_name -> v1.CentralServicesCapabilities.CapabilityStatus - 11, // 8: v1.MetadataService.GetMetadata:input_type -> v1.Empty + 12, // 8: v1.MetadataService.GetMetadata:input_type -> v1.Empty 6, // 9: v1.MetadataService.TLSChallenge:input_type -> v1.TLSChallengeRequest - 11, // 10: v1.MetadataService.GetDatabaseStatus:input_type -> v1.Empty - 11, // 11: v1.MetadataService.GetDatabaseBackupStatus:input_type -> v1.Empty - 11, // 12: v1.MetadataService.GetCentralCapabilities:input_type -> v1.Empty - 3, // 13: v1.MetadataService.GetMetadata:output_type -> v1.Metadata - 5, // 14: v1.MetadataService.TLSChallenge:output_type -> v1.TLSChallengeResponse - 7, // 15: v1.MetadataService.GetDatabaseStatus:output_type -> v1.DatabaseStatus - 8, // 16: v1.MetadataService.GetDatabaseBackupStatus:output_type -> v1.DatabaseBackupStatus - 9, // 17: v1.MetadataService.GetCentralCapabilities:output_type -> v1.CentralServicesCapabilities - 13, // [13:18] is the sub-list for method output_type - 8, // [8:13] is the sub-list for method input_type + 12, // 10: v1.MetadataService.GetDatabaseStatus:input_type -> v1.Empty + 12, // 11: v1.MetadataService.GetDatabaseBackupStatus:input_type -> v1.Empty + 12, // 12: v1.MetadataService.GetCentralCapabilities:input_type -> v1.Empty + 12, // 13: v1.MetadataService.GetLightspeedStatus:input_type -> v1.Empty + 3, // 14: v1.MetadataService.GetMetadata:output_type -> v1.Metadata + 5, // 15: v1.MetadataService.TLSChallenge:output_type -> v1.TLSChallengeResponse + 7, // 16: v1.MetadataService.GetDatabaseStatus:output_type -> v1.DatabaseStatus + 8, // 17: v1.MetadataService.GetDatabaseBackupStatus:output_type -> v1.DatabaseBackupStatus + 10, // 18: v1.MetadataService.GetCentralCapabilities:output_type -> v1.CentralServicesCapabilities + 9, // 19: v1.MetadataService.GetLightspeedStatus:output_type -> v1.LightspeedStatusResponse + 14, // [14:20] is the sub-list for method output_type + 8, // [8:14] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name @@ -786,7 +845,7 @@ func file_api_v1_metadata_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_metadata_service_proto_rawDesc), len(file_api_v1_metadata_service_proto_rawDesc)), NumEnums: 3, - NumMessages: 7, + NumMessages: 8, NumExtensions: 0, NumServices: 1, }, diff --git a/generated/api/v1/metadata_service.pb.gw.go b/generated/api/v1/metadata_service.pb.gw.go index 38d8e5bbb1052..59807b91a4e92 100644 --- a/generated/api/v1/metadata_service.pb.gw.go +++ b/generated/api/v1/metadata_service.pb.gw.go @@ -154,6 +154,27 @@ func local_request_MetadataService_GetCentralCapabilities_0(ctx context.Context, return msg, metadata, err } +func request_MetadataService_GetLightspeedStatus_0(ctx context.Context, marshaler runtime.Marshaler, client MetadataServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq Empty + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.GetLightspeedStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_MetadataService_GetLightspeedStatus_0(ctx context.Context, marshaler runtime.Marshaler, server MetadataServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq Empty + metadata runtime.ServerMetadata + ) + msg, err := server.GetLightspeedStatus(ctx, &protoReq) + return msg, metadata, err +} + // RegisterMetadataServiceHandlerServer registers the http handlers for service MetadataService to "mux". // UnaryRPC :call MetadataServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -260,6 +281,26 @@ func RegisterMetadataServiceHandlerServer(ctx context.Context, mux *runtime.Serv } forward_MetadataService_GetCentralCapabilities_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_MetadataService_GetLightspeedStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/v1.MetadataService/GetLightspeedStatus", runtime.WithHTTPPathPattern("/v1/lightspeed/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_MetadataService_GetLightspeedStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MetadataService_GetLightspeedStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -385,6 +426,23 @@ func RegisterMetadataServiceHandlerClient(ctx context.Context, mux *runtime.Serv } forward_MetadataService_GetCentralCapabilities_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_MetadataService_GetLightspeedStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/v1.MetadataService/GetLightspeedStatus", runtime.WithHTTPPathPattern("/v1/lightspeed/status")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_MetadataService_GetLightspeedStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MetadataService_GetLightspeedStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -394,6 +452,7 @@ var ( pattern_MetadataService_GetDatabaseStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "database", "status"}, "")) pattern_MetadataService_GetDatabaseBackupStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "backup", "status"}, "")) pattern_MetadataService_GetCentralCapabilities_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "central-capabilities"}, "")) + pattern_MetadataService_GetLightspeedStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "lightspeed", "status"}, "")) ) var ( @@ -402,4 +461,5 @@ var ( forward_MetadataService_GetDatabaseStatus_0 = runtime.ForwardResponseMessage forward_MetadataService_GetDatabaseBackupStatus_0 = runtime.ForwardResponseMessage forward_MetadataService_GetCentralCapabilities_0 = runtime.ForwardResponseMessage + forward_MetadataService_GetLightspeedStatus_0 = runtime.ForwardResponseMessage ) diff --git a/generated/api/v1/metadata_service.swagger.json b/generated/api/v1/metadata_service.swagger.json index c2e9f54cf2902..a5fd57814c65f 100644 --- a/generated/api/v1/metadata_service.swagger.json +++ b/generated/api/v1/metadata_service.swagger.json @@ -82,6 +82,29 @@ ] } }, + "/v1/lightspeed/status": { + "get": { + "summary": "GetLightspeedStatus returns the availability status of the Lightspeed AI service.", + "operationId": "MetadataService_GetLightspeedStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1LightspeedStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googleRpcStatus" + } + } + }, + "tags": [ + "MetadataService" + ] + } + }, "/v1/metadata": { "get": { "operationId": "MetadataService_GetMetadata", @@ -286,6 +309,17 @@ } } }, + "v1LightspeedStatusResponse": { + "type": "object", + "properties": { + "available": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + }, "v1Metadata": { "type": "object", "properties": { diff --git a/generated/api/v1/metadata_service_grpc.pb.go b/generated/api/v1/metadata_service_grpc.pb.go index 622cf63cb8f1c..65201270aa09f 100644 --- a/generated/api/v1/metadata_service_grpc.pb.go +++ b/generated/api/v1/metadata_service_grpc.pb.go @@ -24,6 +24,7 @@ const ( MetadataService_GetDatabaseStatus_FullMethodName = "/v1.MetadataService/GetDatabaseStatus" MetadataService_GetDatabaseBackupStatus_FullMethodName = "/v1.MetadataService/GetDatabaseBackupStatus" MetadataService_GetCentralCapabilities_FullMethodName = "/v1.MetadataService/GetCentralCapabilities" + MetadataService_GetLightspeedStatus_FullMethodName = "/v1.MetadataService/GetLightspeedStatus" ) // MetadataServiceClient is the client API for MetadataService service. @@ -40,6 +41,8 @@ type MetadataServiceClient interface { GetDatabaseStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*DatabaseStatus, error) GetDatabaseBackupStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*DatabaseBackupStatus, error) GetCentralCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*CentralServicesCapabilities, error) + // GetLightspeedStatus returns the availability status of the Lightspeed AI service. + GetLightspeedStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*LightspeedStatusResponse, error) } type metadataServiceClient struct { @@ -100,6 +103,16 @@ func (c *metadataServiceClient) GetCentralCapabilities(ctx context.Context, in * return out, nil } +func (c *metadataServiceClient) GetLightspeedStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*LightspeedStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LightspeedStatusResponse) + err := c.cc.Invoke(ctx, MetadataService_GetLightspeedStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // MetadataServiceServer is the server API for MetadataService service. // All implementations should embed UnimplementedMetadataServiceServer // for forward compatibility. @@ -114,6 +127,8 @@ type MetadataServiceServer interface { GetDatabaseStatus(context.Context, *Empty) (*DatabaseStatus, error) GetDatabaseBackupStatus(context.Context, *Empty) (*DatabaseBackupStatus, error) GetCentralCapabilities(context.Context, *Empty) (*CentralServicesCapabilities, error) + // GetLightspeedStatus returns the availability status of the Lightspeed AI service. + GetLightspeedStatus(context.Context, *Empty) (*LightspeedStatusResponse, error) } // UnimplementedMetadataServiceServer should be embedded to have @@ -138,6 +153,9 @@ func (UnimplementedMetadataServiceServer) GetDatabaseBackupStatus(context.Contex func (UnimplementedMetadataServiceServer) GetCentralCapabilities(context.Context, *Empty) (*CentralServicesCapabilities, error) { return nil, status.Error(codes.Unimplemented, "method GetCentralCapabilities not implemented") } +func (UnimplementedMetadataServiceServer) GetLightspeedStatus(context.Context, *Empty) (*LightspeedStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetLightspeedStatus not implemented") +} func (UnimplementedMetadataServiceServer) testEmbeddedByValue() {} // UnsafeMetadataServiceServer may be embedded to opt out of forward compatibility for this service. @@ -248,6 +266,24 @@ func _MetadataService_GetCentralCapabilities_Handler(srv interface{}, ctx contex return interceptor(ctx, in, info, handler) } +func _MetadataService_GetLightspeedStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MetadataServiceServer).GetLightspeedStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MetadataService_GetLightspeedStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MetadataServiceServer).GetLightspeedStatus(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + // MetadataService_ServiceDesc is the grpc.ServiceDesc for MetadataService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -275,6 +311,10 @@ var MetadataService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetCentralCapabilities", Handler: _MetadataService_GetCentralCapabilities_Handler, }, + { + MethodName: "GetLightspeedStatus", + Handler: _MetadataService_GetLightspeedStatus_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/v1/metadata_service.proto", diff --git a/generated/api/v1/metadata_service_vtproto.pb.go b/generated/api/v1/metadata_service_vtproto.pb.go index ff149b12f4c3e..e22445a280ed5 100644 --- a/generated/api/v1/metadata_service_vtproto.pb.go +++ b/generated/api/v1/metadata_service_vtproto.pb.go @@ -182,6 +182,24 @@ func (m *DatabaseBackupStatus) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *LightspeedStatusResponse) CloneVT() *LightspeedStatusResponse { + if m == nil { + return (*LightspeedStatusResponse)(nil) + } + r := new(LightspeedStatusResponse) + r.Available = m.Available + r.Message = m.Message + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *LightspeedStatusResponse) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *CentralServicesCapabilities) CloneVT() *CentralServicesCapabilities { if m == nil { return (*CentralServicesCapabilities)(nil) @@ -386,6 +404,28 @@ func (this *DatabaseBackupStatus) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *LightspeedStatusResponse) EqualVT(that *LightspeedStatusResponse) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Available != that.Available { + return false + } + if this.Message != that.Message { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *LightspeedStatusResponse) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*LightspeedStatusResponse) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *CentralServicesCapabilities) EqualVT(that *CentralServicesCapabilities) bool { if this == that { return true @@ -776,6 +816,56 @@ func (m *DatabaseBackupStatus) MarshalToSizedBufferVT(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *LightspeedStatusResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LightspeedStatusResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *LightspeedStatusResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Message) > 0 { + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x12 + } + if m.Available { + i-- + if m.Available { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *CentralServicesCapabilities) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -979,6 +1069,23 @@ func (m *DatabaseBackupStatus) SizeVT() (n int) { return n } +func (m *LightspeedStatusResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Available { + n += 2 + } + l = len(m.Message) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *CentralServicesCapabilities) SizeVT() (n int) { if m == nil { return 0 @@ -1874,6 +1981,109 @@ func (m *DatabaseBackupStatus) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *LightspeedStatusResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LightspeedStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LightspeedStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Available", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Available = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *CentralServicesCapabilities) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -2906,6 +3116,113 @@ func (m *DatabaseBackupStatus) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *LightspeedStatusResponse) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LightspeedStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LightspeedStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Available", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Available = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var stringValue string + if intStringLen > 0 { + stringValue = unsafe.String(&dAtA[iNdEx], intStringLen) + } + m.Message = stringValue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *CentralServicesCapabilities) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/pkg/tlsutils/service_ca.go b/pkg/tlsutils/service_ca.go new file mode 100644 index 0000000000000..6736c711585c0 --- /dev/null +++ b/pkg/tlsutils/service_ca.go @@ -0,0 +1,41 @@ +package tlsutils + +import ( + "crypto/tls" + "crypto/x509" + "net/http" + "os" +) + +const ( + // serviceOperatorCAPath points to the service account secret which within + // an OpenShift environment also contains the service-ca.crt. This CA is + // used to verify certificates issued by the service-ca operator. + serviceOperatorCAPath = "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" +) + +// TransportWithServiceCA returns an http.Transport whose TLS config trusts the +// system cert pool plus the OpenShift service-serving CA (if available). +func TransportWithServiceCA() *http.Transport { + return TransportWithAdditionalCA(serviceOperatorCAPath) +} + +// TransportWithAdditionalCA returns an http.Transport whose TLS config trusts +// the system cert pool plus an additional CA file (if readable). +func TransportWithAdditionalCA(caFile string) *http.Transport { + rootCAs, err := x509.SystemCertPool() + if err != nil { + rootCAs = x509.NewCertPool() + } + + if caData, err := os.ReadFile(caFile); err == nil { + rootCAs.AppendCertsFromPEM(caData) + } + + return &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: rootCAs, + }, + } +} diff --git a/proto/api/v1/metadata_service.proto b/proto/api/v1/metadata_service.proto index 470192ba0f1d8..7382aea0c5353 100644 --- a/proto/api/v1/metadata_service.proto +++ b/proto/api/v1/metadata_service.proto @@ -77,6 +77,11 @@ message DatabaseBackupStatus { storage.BackupInfo backup_info = 1; } +message LightspeedStatusResponse { + bool available = 1; + string message = 2; +} + // Provides availability of certain functionality of Central Services in the current configuration. // The initial intended use is to disable certain functionality that does not make sense in the Cloud Service context. message CentralServicesCapabilities { @@ -139,4 +144,9 @@ service MetadataService { rpc GetCentralCapabilities(Empty) returns (CentralServicesCapabilities) { option (google.api.http) = {get: "/v1/central-capabilities"}; } + + // GetLightspeedStatus returns the availability status of the Lightspeed AI service. + rpc GetLightspeedStatus(Empty) returns (LightspeedStatusResponse) { + option (google.api.http) = {get: "/v1/lightspeed/status"}; + } } From 0e29945820b4571253ad33e89010ed72ea39422f Mon Sep 17 00:00:00 2001 From: David Vail Date: Fri, 11 Sep 2026 15:47:07 -0400 Subject: [PATCH 291/638] chore: Enable feature flag for Risk AI integration + tests connection (#22714) --- pkg/features/list.go | 2 +- .../src/ConsolePlugin/PluginProvider.tsx | 5 +- .../src/Containers/Risk/RiskDetailsPage.tsx | 16 ++----- .../src/hooks/useLightspeedStatus.tsx | 24 ++++++++++ ui/apps/platform/src/index.tsx | 5 +- .../providers/LightspeedStatusProvider.tsx | 47 +++++++++++++++++++ .../src/services/DeploymentsService.ts | 20 -------- .../platform/src/services/MetadataService.ts | 9 ++++ 8 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 ui/apps/platform/src/hooks/useLightspeedStatus.tsx create mode 100644 ui/apps/platform/src/providers/LightspeedStatusProvider.tsx diff --git a/pkg/features/list.go b/pkg/features/list.go index 4ef0b00b830d4..eabcfa09eba5d 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -156,7 +156,7 @@ var ( ACMAccessControlDelegation = registerFeature("Enable ACS access control integration with Red Hat Advanced Cluster Management", "ROX_ACM_ACCESS_CONTROL_DELEGATION") // LightspeedRiskSummary enables Lightspeed AI risk summary - LightspeedRiskSummary = registerFeature("Enable Lightspeed AI risk summary", "ROX_LIGHTSPEED_RISK_SUMMARY") + LightspeedRiskSummary = registerFeature("Enable Lightspeed AI risk summary", "ROX_LIGHTSPEED_RISK_SUMMARY", enabled) ) // The following feature flags are related to Scanner V4. diff --git a/ui/apps/platform/src/ConsolePlugin/PluginProvider.tsx b/ui/apps/platform/src/ConsolePlugin/PluginProvider.tsx index 1e5a77209c5c6..daa73f3a77ffe 100644 --- a/ui/apps/platform/src/ConsolePlugin/PluginProvider.tsx +++ b/ui/apps/platform/src/ConsolePlugin/PluginProvider.tsx @@ -7,6 +7,7 @@ import configureApolloClient from 'init/configureApolloClient'; import { setAnalyticsSource } from 'init/initializeAnalytics'; import { UserPermissionProvider } from 'providers/UserPermissionProvider'; import { FeatureFlagsProvider } from 'providers/FeatureFlagProvider'; +import { LightspeedStatusProvider } from 'providers/LightspeedStatusProvider'; import { MetadataProvider } from 'providers/MetadataProvider'; import { PublicConfigProvider } from 'providers/PublicConfigProvider'; import { TelemetryConfigProvider } from 'providers/TelemetryConfigProvider'; @@ -46,7 +47,9 @@ function PluginProviderContent({ children }: { children: ReactNode }) { - {children} + + {children} + diff --git a/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx b/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx index daf7c9978529c..8f1cbc5878362 100644 --- a/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/Risk/RiskDetailsPage.tsx @@ -1,4 +1,3 @@ -import { useCallback } from 'react'; import type { ReactElement } from 'react'; import { Breadcrumb, @@ -28,9 +27,8 @@ import { } from 'routePaths'; import useFeatureFlags from 'hooks/useFeatureFlags'; import useIsRouteEnabled from 'hooks/useIsRouteEnabled'; -import useRestQuery from 'hooks/useRestQuery'; +import useLightspeedStatus from 'hooks/useLightspeedStatus'; import AiExperienceIcon from 'images/aiExperience.svg?react'; -import { testLightspeedConnection } from 'services/DeploymentsService'; import RiskDetailTabs from './RiskDetailTabs'; import useDeploymentWithRisk from './useDeploymentWithRisk'; @@ -62,16 +60,8 @@ function RiskDetailsPage(): ReactElement { const { isFeatureFlagEnabled } = useFeatureFlags(); const isRiskSummaryEnabled = isFeatureFlagEnabled('ROX_LIGHTSPEED_RISK_SUMMARY'); - const connectionTest = useRestQuery( - useCallback( - () => - isRiskSummaryEnabled - ? testLightspeedConnection() - : Promise.resolve({ success: false, message: '' }), - [isRiskSummaryEnabled] - ) - ); - const isAiSummaryAvailable = isRiskSummaryEnabled && connectionTest.data?.success === true; + const { isAvailable } = useLightspeedStatus(); + const isAiSummaryAvailable = isRiskSummaryEnabled && isAvailable; const aiRiskSummary = useAiRiskSummary(deploymentId); const hasAiRiskSummary = Boolean(aiRiskSummary.summary); diff --git a/ui/apps/platform/src/hooks/useLightspeedStatus.tsx b/ui/apps/platform/src/hooks/useLightspeedStatus.tsx new file mode 100644 index 0000000000000..039d348b1ab95 --- /dev/null +++ b/ui/apps/platform/src/hooks/useLightspeedStatus.tsx @@ -0,0 +1,24 @@ +import { createContext, useContext } from 'react'; + +import type { LightspeedStatusContextType } from 'providers/LightspeedStatusProvider'; + +export const LightspeedStatusContext = createContext( + undefined +); + +function useLightspeedStatus(): LightspeedStatusContextType { + const context = useContext(LightspeedStatusContext); + if (context === undefined) { + return { + isAvailable: false, + isLoading: false, + error: new Error( + 'useLightspeedStatus must be used within a LightspeedStatusProvider - returning static default values' + ), + }; + } + + return context; +} + +export default useLightspeedStatus; diff --git a/ui/apps/platform/src/index.tsx b/ui/apps/platform/src/index.tsx index 2036977768d4a..ee2273eaf84bf 100644 --- a/ui/apps/platform/src/index.tsx +++ b/ui/apps/platform/src/index.tsx @@ -31,6 +31,7 @@ import installRaven from 'init/installRaven'; import configureApollo from 'init/configureApolloClient'; import { setAnalyticsSource } from 'init/initializeAnalytics'; import { FeatureFlagsProvider } from 'providers/FeatureFlagProvider'; +import { LightspeedStatusProvider } from 'providers/LightspeedStatusProvider'; import { PublicConfigProvider } from 'providers/PublicConfigProvider'; import { TelemetryConfigProvider } from 'providers/TelemetryConfigProvider'; import { MetadataProvider } from 'providers/MetadataProvider'; @@ -71,7 +72,9 @@ root.render( - + + + diff --git a/ui/apps/platform/src/providers/LightspeedStatusProvider.tsx b/ui/apps/platform/src/providers/LightspeedStatusProvider.tsx new file mode 100644 index 0000000000000..a944006ee1315 --- /dev/null +++ b/ui/apps/platform/src/providers/LightspeedStatusProvider.tsx @@ -0,0 +1,47 @@ +import { useCallback, useMemo } from 'react'; +import type { ReactNode } from 'react'; + +import { LightspeedStatusContext } from 'hooks/useLightspeedStatus'; +import useFeatureFlags from 'hooks/useFeatureFlags'; +import useRestQuery from 'hooks/useRestQuery'; +import { fetchLightspeedStatus } from 'services/MetadataService'; + +export type LightspeedStatusProviderProps = { + children: ReactNode; +}; + +export type LightspeedStatusContextType = { + isAvailable: boolean; + isLoading: boolean; + error: Error | undefined; +}; + +export function LightspeedStatusProvider({ children }: LightspeedStatusProviderProps) { + const { isFeatureFlagEnabled } = useFeatureFlags(); + const isRiskSummaryEnabled = isFeatureFlagEnabled('ROX_LIGHTSPEED_RISK_SUMMARY'); + + const { data, isLoading, error } = useRestQuery( + useCallback( + () => + isRiskSummaryEnabled + ? fetchLightspeedStatus() + : Promise.resolve({ available: false, message: 'Feature disabled' }), + [isRiskSummaryEnabled] + ) + ); + + const value: LightspeedStatusContextType = useMemo( + () => ({ + isAvailable: data?.available ?? false, + isLoading, + error, + }), + [data?.available, isLoading, error] + ); + + return ( + + {children} + + ); +} diff --git a/ui/apps/platform/src/services/DeploymentsService.ts b/ui/apps/platform/src/services/DeploymentsService.ts index a35ea856dde90..054cce9a94bee 100644 --- a/ui/apps/platform/src/services/DeploymentsService.ts +++ b/ui/apps/platform/src/services/DeploymentsService.ts @@ -159,11 +159,6 @@ export type DeploymentRiskSummary = { summary: string; }; -export type TestLightspeedConnectionResponse = { - success: boolean; - message: string; -}; - /** * Fetches an AI-generated risk summary for a deployment by ID. */ @@ -175,18 +170,3 @@ export function fetchDeploymentRiskSummary(id: string): Promise(`${deploymentWithRiskUrl}/${id}/ai-summary`, { timeout: 60000 }) .then((response) => response.data); } - -/** - * Tests connectivity to the Lightspeed AI service. - */ -export function testLightspeedConnection(): CancellableRequest { - return makeCancellableAxiosRequest((signal) => - axios - .post( - `${deploymentWithRiskUrl}/ai-summary/test`, - undefined, - { signal } - ) - .then((response) => response.data) - ); -} diff --git a/ui/apps/platform/src/services/MetadataService.ts b/ui/apps/platform/src/services/MetadataService.ts index 59df280e2a037..0fede63fa82d3 100644 --- a/ui/apps/platform/src/services/MetadataService.ts +++ b/ui/apps/platform/src/services/MetadataService.ts @@ -56,3 +56,12 @@ export function fetchCentralCapabilities(): Promise .get('/v1/central-capabilities') .then((response) => response.data); } + +export type LightspeedStatus = { + available: boolean; + message: string; +}; + +export function fetchLightspeedStatus(): Promise { + return axios.get('/v1/lightspeed/status').then((response) => response.data); +} From de84d6230e90f70474e0477331e867a717cbda54 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Fri, 11 Sep 2026 13:22:03 -0700 Subject: [PATCH 292/638] Remove args from risk process violations (#22496) --- .../service/risk_ai_summary_test.go | 144 ++++++++++++++++++ .../deployment/service/risk_sanitization.go | 24 ++- .../deployment/process_baseline_violations.go | 12 +- .../process_baseline_violations_test.go | 4 +- 4 files changed, 174 insertions(+), 10 deletions(-) diff --git a/central/deployment/service/risk_ai_summary_test.go b/central/deployment/service/risk_ai_summary_test.go index 2e00a58c146e3..faf65f83c5a32 100644 --- a/central/deployment/service/risk_ai_summary_test.go +++ b/central/deployment/service/risk_ai_summary_test.go @@ -8,7 +8,9 @@ import ( deploymentMocks "github.com/stackrox/rox/central/deployment/datastore/mocks" olsClient "github.com/stackrox/rox/central/lightspeed/client" + processViews "github.com/stackrox/rox/central/processindicator/views" riskMocks "github.com/stackrox/rox/central/risk/datastore/mocks" + deployment "github.com/stackrox/rox/central/risk/multipliers/deployment" v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stretchr/testify/assert" @@ -459,3 +461,145 @@ func TestBuildSanitizedRiskContext_FieldSelection(t *testing.T) { assert.NotContains(t, contextJSON, "notPullable") assert.NotContains(t, contextJSON, "isClusterLocal") } + +func TestBuildSanitizedRiskContext_ProcessArgsStripped(t *testing.T) { + dep := &storage.Deployment{ + Id: "dep-1", + Name: "test-app", + Namespace: "default", + ClusterName: "test-cluster", + Type: "Deployment", + } + + // Use FormatProcess to generate realistic messages. This ensures the test + // will catch any changes to FormatProcess that break our sanitization regex. + processWithSensitiveArgs1 := &processViews.ProcessIndicatorRiskView{ + SignalName: "/bin/bash", + SignalArgs: "-c export PASSWORD=secret123 && ./script.sh", + ContainerName: "app", + } + processWithSensitiveArgs2 := &processViews.ProcessIndicatorRiskView{ + SignalName: "/usr/bin/curl", + SignalArgs: "https://api.example.com?token=abc123&secret=xyz", + ContainerName: "app", + } + processWithoutArgs := &processViews.ProcessIndicatorRiskView{ + SignalName: "/bin/sh", + SignalArgs: "", // no args + ContainerName: "sidecar", + } + + risk := &storage.Risk{ + Id: "risk-1", + Score: 8.0, + Results: []*storage.Risk_Result{ + { + Name: deployment.ProcessBaselineHeading, + Score: 2.5, + Factors: []*storage.Risk_Result_Factor{ + // Generated using FormatProcess to stay in sync with the actual format. + {Message: deployment.FormatProcess(processWithSensitiveArgs1)}, + {Message: deployment.FormatProcess(processWithSensitiveArgs2)}, + {Message: deployment.FormatProcess(processWithoutArgs)}, + }, + }, + { + // Other risk results should NOT have args stripped. + Name: "Policy Violations", + Score: 1.5, + Factors: []*storage.Risk_Result_Factor{ + {Message: `Some message with args "should not be stripped"`}, + }, + }, + }, + } + + contextJSON, err := buildSanitizedRiskContext(dep, risk) + require.NoError(t, err) + + // Process names and container names SHOULD be present. + assert.Contains(t, contextJSON, "/bin/bash") + assert.Contains(t, contextJSON, "/usr/bin/curl") + assert.Contains(t, contextJSON, "/bin/sh") + assert.Contains(t, contextJSON, "container app") + assert.Contains(t, contextJSON, "container sidecar") + assert.Contains(t, contextJSON, deployment.ProcessBaselineHeading) + + // Process arguments MUST be redacted from process baseline results (may contain secrets). + assert.NotContains(t, contextJSON, "PASSWORD=secret123") + assert.NotContains(t, contextJSON, "token=abc123") + assert.NotContains(t, contextJSON, "secret=xyz") + assert.NotContains(t, contextJSON, "script.sh") + + // The ' with args "..."' pattern should be replaced with ''. + // Note: JSON encoding escapes < and > as \u003c and \u003e. + assert.NotContains(t, contextJSON, `with args "-c export`) + assert.NotContains(t, contextJSON, `with args "https://api`) + assert.Contains(t, contextJSON, `\u003credacted args\u003e`) + + // Message without args should remain intact (no added). + // Use the exact format from FormatProcess. + assert.Contains(t, contextJSON, `Detected execution of suspicious process \"/bin/sh\" in container sidecar`) + + // Other risk results should NOT have their messages modified. + assert.Contains(t, contextJSON, `with args \"should not be stripped\"`) +} + +func TestBuildSanitizedRiskContext_ProcessArgsWithEmbeddedQuotes(t *testing.T) { + // Regression test: strconv.Quote escapes embedded quotes as \". + // The regex must handle escaped quotes to avoid leaving sensitive data exposed. + // Using FormatProcess ensures this test will catch format changes. + dep := &storage.Deployment{ + Id: "dep-1", + Name: "test-app", + Namespace: "default", + ClusterName: "test-cluster", + Type: "Deployment", + } + + // Args with embedded quotes - FormatProcess uses strconv.Quote which escapes them. + processWithEmbeddedQuotes1 := &processViews.ProcessIndicatorRiskView{ + SignalName: "/bin/bash", + SignalArgs: `-c echo "password=secret123"`, + ContainerName: "app", + } + processWithEmbeddedQuotes2 := &processViews.ProcessIndicatorRiskView{ + SignalName: "/bin/sh", + SignalArgs: `cmd with "nested" quotes and token=abc123`, + ContainerName: "app", + } + + risk := &storage.Risk{ + Id: "risk-1", + Score: 8.0, + Results: []*storage.Risk_Result{ + { + Name: deployment.ProcessBaselineHeading, + Score: 2.0, + Factors: []*storage.Risk_Result_Factor{ + {Message: deployment.FormatProcess(processWithEmbeddedQuotes1)}, + {Message: deployment.FormatProcess(processWithEmbeddedQuotes2)}, + }, + }, + }, + } + + contextJSON, err := buildSanitizedRiskContext(dep, risk) + require.NoError(t, err) + + // Process names SHOULD be present. + assert.Contains(t, contextJSON, "/bin/bash") + assert.Contains(t, contextJSON, "/bin/sh") + + // Sensitive data with embedded quotes MUST be fully redacted. + assert.NotContains(t, contextJSON, "password=secret123") + assert.NotContains(t, contextJSON, "token=abc123") + assert.NotContains(t, contextJSON, "nested") + + // The entire args section should be replaced with . + assert.Contains(t, contextJSON, `\u003credacted args\u003e`) + + // Ensure no partial args remain (the old regex would stop at the first escaped quote). + assert.NotContains(t, contextJSON, `with args "-c echo`) + assert.NotContains(t, contextJSON, `with args "cmd with`) +} diff --git a/central/deployment/service/risk_sanitization.go b/central/deployment/service/risk_sanitization.go index 1c510d987c928..2a9c8ab2da26e 100644 --- a/central/deployment/service/risk_sanitization.go +++ b/central/deployment/service/risk_sanitization.go @@ -2,10 +2,19 @@ package service import ( "encoding/json" + "regexp" + "github.com/stackrox/rox/central/risk/multipliers/deployment" "github.com/stackrox/rox/generated/storage" ) +// processArgsPattern matches ' with args "..."' in process baseline messages. +// Process arguments may contain sensitive data (passwords, tokens) and must be +// redacted before sending to an external LLM. +// The pattern handles escaped quotes (from strconv.Quote) to ensure the entire +// argument string is matched even when it contains embedded quotes. +var processArgsPattern = regexp.MustCompile(` with args "(\\.|[^"\\])*"`) + // buildSanitizedRiskContext produces a minimal JSON representation of the // deployment and risk data suitable for sending to an external LLM. It keeps // only fields relevant to risk analysis and strips sensitive data (env vars, @@ -124,11 +133,18 @@ func sanitizeRisk(r *storage.Risk) sanitizedRisk { Score: result.GetScore(), } for _, factor := range result.GetFactors() { - if factor.GetMessage() != "" { - srr.Factors = append(srr.Factors, sanitizedRiskFactor{ - Message: factor.GetMessage(), - }) + msg := factor.GetMessage() + if msg == "" { + continue + } + // Redact process arguments from process baseline violation messages + // as they may contain sensitive information (passwords, tokens, etc.). + if result.GetName() == deployment.ProcessBaselineHeading { + msg = processArgsPattern.ReplaceAllString(msg, "") } + srr.Factors = append(srr.Factors, sanitizedRiskFactor{ + Message: msg, + }) } sr.Results = append(sr.Results, srr) } diff --git a/central/risk/multipliers/deployment/process_baseline_violations.go b/central/risk/multipliers/deployment/process_baseline_violations.go index 095cb9344c1b2..06907a8a2d170 100644 --- a/central/risk/multipliers/deployment/process_baseline_violations.go +++ b/central/risk/multipliers/deployment/process_baseline_violations.go @@ -13,7 +13,9 @@ import ( ) const ( - processBaselineHeading = `Suspicious Process Executions` + // ProcessBaselineHeading is the risk result name for process baseline violations. + // Exported for use in risk data sanitization. + ProcessBaselineHeading = `Suspicious Process Executions` processBaselineSaturation = 10 processBaselineValue = 4 @@ -56,7 +58,9 @@ func NewProcessBaselines(evaluator evaluator.Evaluator) Multiplier { } } -func formatProcess(process *views.ProcessIndicatorRiskView) string { +// FormatProcess formats a process indicator into a human-readable message for risk factors. +// Exported for use in tests to ensure sanitization regex stays in sync with format changes. +func FormatProcess(process *views.ProcessIndicatorRiskView) string { sb := strings.Builder{} sb.Grow(128) sb.WriteString("Detected execution of suspicious process ") @@ -82,13 +86,13 @@ func (p *processBaselineMultiplier) Score(_ context.Context, deployment *storage scorer := newScorer() riskResult := &storage.Risk_Result{ - Name: processBaselineHeading, + Name: ProcessBaselineHeading, } for _, process := range violatingProcesses { scorer.addProcess() riskResult.Factors = append(riskResult.Factors, &storage.Risk_Result_Factor{ - Message: formatProcess(process), + Message: FormatProcess(process), }) } diff --git a/central/risk/multipliers/deployment/process_baseline_violations_test.go b/central/risk/multipliers/deployment/process_baseline_violations_test.go index 1d47bdec6db7d..f5cede11106e1 100644 --- a/central/risk/multipliers/deployment/process_baseline_violations_test.go +++ b/central/risk/multipliers/deployment/process_baseline_violations_test.go @@ -44,7 +44,7 @@ func TestProcessBaselines(t *testing.T) { }, }, expected: &storage.Risk_Result{ - Name: processBaselineHeading, + Name: ProcessBaselineHeading, Score: 1.6, Factors: []*storage.Risk_Result_Factor{ {Message: "Detected execution of suspicious process \"apt-get\" with args \"install nmap\" in container containerName"}, @@ -66,7 +66,7 @@ func TestProcessBaselines(t *testing.T) { }, }, expected: &storage.Risk_Result{ - Name: processBaselineHeading, + Name: ProcessBaselineHeading, Score: 2.14, Factors: []*storage.Risk_Result_Factor{ {Message: "Detected execution of suspicious process \"apt-get\" with args \"install nmap\" in container containerName"}, From b40f46c8826288a2dc286b6c31bb09784196e28b Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Fri, 11 Sep 2026 23:56:12 +0200 Subject: [PATCH 293/638] ROX-36654: Remove Scanner V2 from install methods [CLEANED UP] (#22645) Co-authored-by: Claude Opus 4.6 Co-authored-by: Marcin Owsiany Co-authored-by: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Co-authored-by: Kirk Bater Co-authored-by: David Vail Co-authored-by: David Caravello <119438707+dcaravel@users.noreply.github.com> --- .github/workflows/e2e-byodb-tests.yaml | 6 +- .github/workflows/e2e-gke-upgrade-tests.yaml | 1 + .github/workflows/e2e-nongroovy-tests.yaml | 4 +- central/certgen/central_scanner.go | 26 +- central/certgen/central_scanner_test.go | 42 +++ central/clusters/zip/render_test.go | 15 +- central/securedclustercertgen/certificates.go | 2 + .../nodeindex/three_pipelines_test.go | 10 +- .../pipeline/nodeinventory/pipeline_test.go | 7 + .../nodeinventory/two_pipelines_test.go | 5 + .../service/pipeline/nodes/pipeline_test.go | 1 + deploy/common/deploy.sh | 16 - deploy/common/env.sh | 3 - deploy/common/k8sbased.sh | 23 -- deploy/common/local-dev-values.yaml | 20 -- deploy/common/scanner-hpa-patch.yaml | 2 - deploy/common/scanner-local-patch.yaml | 13 - deploy/common/scanner-patch.yaml | 2 - image/templates/common/setup-scanner.sh | 25 -- .../config-templates/scanner/config.yaml.tpl | 48 --- .../shared/internal/scanner-config-shape.yaml | 44 --- .../02-scanner-00-serviceaccount.yaml | 21 -- .../shared/templates/02-scanner-01-psps.yaml | 69 ---- .../templates/02-scanner-01-security.yaml | 45 --- .../02-scanner-02-db-password-secret.yaml | 27 -- .../templates/02-scanner-03-tls-secret.yaml | 57 ---- .../02-scanner-04-scanner-config.yaml | 18 - .../02-scanner-05-network-policy.yaml | 91 ----- .../02-scanner-06-deployment.yaml.htpl | 313 ------------------ .../templates/02-scanner-07-service.yaml | 51 --- .../shared/templates/02-scanner-08-hpa.yaml | 25 -- .../templates/_scanner-v4_defaulting.tpl | 11 +- .../shared/templates/_scanner_init.tpl.htpl | 40 --- .../helm/stackrox-central/.helmtplignore.htpl | 2 - .../helm/stackrox-central/README.md.htpl | 31 +- .../internal/config-shape.yaml | 12 - .../internal/defaults.yaml.htpl | 62 ---- .../internal/expandables.yaml | 13 - .../01-central-13-deployment.yaml.htpl | 5 - .../stackrox-central/templates/_init.tpl.htpl | 38 ++- .../values-private.yaml.example | 33 -- .../values-public.yaml.example.htpl | 137 +------- .../helm/stackrox-central/values.yaml.htpl | 84 +---- .../.helmtplignore.htpl | 7 - .../stackrox-secured-cluster/README.md.htpl | 14 +- .../internal/config-shape.yaml | 11 - .../internal/defaults/50-images.yaml.htpl | 18 - .../internal/defaults/70-scanner.yaml | 37 --- .../internal/expandables.yaml | 13 - .../templates/_init.tpl.htpl | 25 +- .../templates/_scanner-defaulting.tpl | 29 -- .../templates/collector.yaml.htpl | 43 --- .../templates/sensor-netpol.yaml.htpl | 3 - .../templates/sensor.yaml.htpl | 2 - .../values-public-scanner-v4.yaml.example | 6 +- .../values-scanner.yaml.example | 92 ----- operator/api/v1alpha1/central_types.go | 21 +- operator/api/v1alpha1/securedcluster_types.go | 16 +- .../platform.stackrox.io_centrals.yaml | 8 +- .../platform.stackrox.io_securedclusters.yaml | 15 +- .../rhacs-operator.clusterserviceversion.yaml | 38 +-- .../bases/platform.stackrox.io_centrals.yaml | 8 +- .../platform.stackrox.io_securedclusters.yaml | 15 +- .../rhacs-operator.clusterserviceversion.yaml | 39 +-- .../internal/central/defaults/scanner_v4.go | 63 +--- operator/internal/central/defaults/static.go | 10 - .../extensions/reconcile_defaulting_test.go | 39 +-- .../reconcile_scanner_db_password.go | 4 - .../reconcile_scanner_db_password_test.go | 46 +-- .../central/extensions/reconcile_tls.go | 45 --- .../reconcile_tls_ca_rotation_test.go | 2 - .../central/extensions/reconcile_tls_test.go | 52 --- .../central/values/translation/translation.go | 18 - .../translation_deployment_defaults_test.go | 2 - .../values/translation/translation_test.go | 61 ---- .../common/defaulting_test_helpers/helpers.go | 12 + .../securedcluster/defaults/scanner_v4.go | 54 +-- .../securedcluster/defaults/static.go | 11 - .../extensions/reconcile_defaulting_test.go | 41 +-- .../securedcluster/scanner/auto_sense.go | 24 +- .../securedcluster/scanner/auto_sense_test.go | 18 +- .../securedcluster/scanner/defaults.go | 13 - .../values/translation/translation.go | 25 +- .../values/translation/translation_test.go | 163 --------- .../central/central-basic/100-assert.yaml | 22 -- .../central-basic/100-set-host-aliases.yaml | 10 - .../central-basic/101-unset-host-aliases.yaml | 5 - .../central/central-misc/010-central-cr.yaml | 20 -- .../central/central-misc/061-assert.yaml | 35 -- .../061-set-expose-monitoring.yaml | 3 - .../central/central-misc/063-assert.yaml | 17 - .../063-unset-expose-monitoring.yaml | 3 - .../080-assert-non-openshift.yaml | 2 +- .../central-misc/080-assert-openshift.yaml | 2 +- .../081-assert-non-openshift.yaml | 2 +- .../central-misc/081-assert-openshift.yaml | 2 +- operator/tests/common/central-cr-assert.yaml | 22 -- ...cr-with-scanner-v4-enabled-explicitly.yaml | 20 -- .../central-cr-without-scanner-v4-assert.yaml | 22 -- .../common/central-cr-without-scanner-v4.yaml | 20 -- operator/tests/common/central-cr.yaml | 7 + .../common/config-hash-assert.envsubst.yaml | 20 -- operator/tests/common/pods-debug.yaml | 8 - ...cured-cluster-cr-assert-no-scanner-v4.yaml | 7 - .../common/secured-cluster-cr-assert.yaml | 7 - .../012-assert-image-pull-secret-refs.yaml | 7 - .../013-assert-image-pull-secret-refs.yaml | 8 - .../014-assert-image-pull-secret-refs.yaml | 8 - .../015-assert-image-pull-secret-refs.yaml | 9 - .../sc-basic/910-assert-openshift.yaml | 16 +- .../sc-basic/910-errors-non-openshift.yaml | 13 +- .../securedcluster/sc-basic/910-errors.yaml | 18 + ...h-pre-4-9-admission-controller-config.yaml | 5 + .../tests/upgrade/upgrade/033-errors.yaml | 21 ++ pkg/certgen/scanner.go | 1 + pkg/env/node_scan.go | 2 +- pkg/features/list.go | 4 +- .../centralservices/flavor/flavor_test.go | 2 +- .../testdata/helmtest/custom/custom.test.yaml | 4 +- .../development_build.test.yaml | 4 +- .../development_build.test.yaml | 4 +- .../opensource.test.yaml | 4 +- .../opensource-release/opensource.test.yaml | 4 +- .../helmtest/override/override.test.yaml | 4 +- .../testdata/helmtest/rhacs/rhacs.test.yaml | 4 +- .../helmtest/stackrox/stackrox.test.yaml | 4 +- .../flavor/testdata/helmtest/suite.yaml | 14 +- .../chart-label-truncation.test.yaml | 12 +- .../testdata/helmtest/central.test.yaml | 14 +- .../helmtest/default-labels.test.yaml | 10 +- .../testdata/helmtest/host-aliases.test.yaml | 33 -- .../image-pull-secrets-legacy.test.yaml | 2 - .../testdata/helmtest/monitoring.test.yaml | 20 -- .../testdata/helmtest/node-selector.test.yaml | 30 +- .../helmtest/priority-class-name.test.yaml | 12 - .../testdata/helmtest/scanner-v4.test.yaml | 52 +-- .../testdata/helmtest/scanner.test.yaml | 150 +-------- .../testdata/helmtest/tolerations.test.yaml | 21 -- .../flavor/flavor_test.go | 2 +- .../development_build.test.yaml | 6 +- .../development_build.test.yaml | 6 +- .../opensource.test.yaml | 6 +- .../opensource-release/opensource.test.yaml | 6 +- .../helmtest/override/override.test.yaml | 8 +- .../testdata/helmtest/rhacs/rhacs.test.yaml | 6 +- .../helmtest/stackrox/stackrox.test.yaml | 6 +- .../flavor/testdata/helmtest/suite.yaml | 4 +- .../helmtest/collector-compliance.test.yaml | 8 - .../testdata/helmtest/host-aliases.test.yaml | 26 -- .../helmtest/image-pull-secrets.test.yaml | 16 - .../testdata/helmtest/scanner-slim.test.yaml | 174 +--------- .../testdata/helmtest/scanner-v4.test.yaml | 82 +---- .../testdata/helmtest/sensor-netpol.test.yaml | 17 +- pkg/images/enricher/enricher_impl.go | 2 +- pkg/images/enricher/enricher_v2_impl.go | 21 +- pkg/images/enricher/enricher_v2_impl_test.go | 99 ++++++ pkg/renderer/kubernetes.go | 2 + pkg/renderer/readme.go | 6 +- pkg/renderer/readme_test.go | 8 +- pkg/renderer/render_new.go | 27 +- pkg/renderer/render_test.go | 9 - .../templates/private_values.yaml.tpl | 27 -- .../private_values_postgres.yaml.tpl | 27 -- pkg/renderer/templates/public_values.yaml.tpl | 45 +-- pkg/securedcluster/constants.go | 6 +- qa-tests-backend/scripts/run-compatibility.sh | 4 +- .../services/CredentialExpiryService.groovy | 6 - .../services/ImageIntegrationService.groovy | 4 + .../src/test/groovy/CertExpiryTest.groovy | 21 -- .../src/test/groovy/CertRotationTest.groovy | 17 - .../GraphQLResourcePaginationTest.groovy | 2 +- .../src/test/groovy/ImageScanningTest.groovy | 18 +- .../test/groovy/IntegrationHealthTest.groovy | 3 + roxctl/central/generate/interactive.go | 12 +- roxctl/central/generate/k8s.go | 2 - roxctl/common/flags/imageFlavor.go | 4 - roxctl/maincommand/command_tree_debug.yaml | 17 - roxctl/maincommand/command_tree_release.yaml | 17 - roxctl/scanner/generate/generate.go | 3 +- scripts/ci/is-scanner-v2-available.sh | 22 -- scripts/ci/jobs/gke_nongroovy_e2e_tests.py | 2 +- scripts/ci/jobs/ocp_nongroovy_e2e_tests.py | 2 +- .../securedcluster/cert_repo_init.go | 2 - .../securedcluster/cert_repo_init_test.go | 4 - .../securedcluster_tls_issuer_test.go | 26 +- tests/e2e/lib-compat.sh | 43 ++- tests/e2e/lib.sh | 8 - tests/e2e/run-scanner-v4-install.bats | 56 +--- .../bats-tests/cluster/scanner-generate.bats | 30 +- tests/roxctl/bats-tests/helpers.bash | 17 +- .../local/central-generate-development.bats | 16 +- .../central-generate-interactive-flavors.bats | 28 +- .../local/central-generate-release.bats | 16 +- .../expect/flavor-interactive.expect.tcl | 54 +-- ...oxctl-helm-output-central-development.bats | 10 +- .../roxctl-helm-output-central-release.bats | 10 +- tests/upgrade/lib.sh | 154 +++++++-- tests/upgrade/postgres_run.sh | 14 +- .../cypress/constants/apiEndpoints.js | 2 +- .../fixtures/vulnmanagement/nodeCves.json | 55 +++ .../platform/cypress/helpers/systemHealth.js | 7 +- .../collections/deploymentMatching.test.js | 10 +- .../credentialExpiry.helpers.js | 8 +- .../credentialExpiry/credentialExpiry.test.js | 40 +-- .../legacyScannerFeatureFlag.test.ts | 127 ------- .../networkDeploymentSidebar.test.js | 2 +- .../networkGraph/networkGraph.test.js | 27 +- .../cypress/integration/risk/risk.test.js | 2 +- .../certificateHealthCards.test.js | 19 +- .../systemHealth/vulnDefinitions.test.js | 3 +- .../VulnerabilityManagement.helpers.js | 1 - .../vulnmanagement/clusters.test.js | 14 +- .../vulnmanagement/nodeCves.test.js | 158 +++------ 213 files changed, 1029 insertions(+), 4177 deletions(-) create mode 100644 central/certgen/central_scanner_test.go delete mode 100644 deploy/common/scanner-hpa-patch.yaml delete mode 100644 deploy/common/scanner-local-patch.yaml delete mode 100644 deploy/common/scanner-patch.yaml delete mode 100755 image/templates/common/setup-scanner.sh delete mode 100644 image/templates/helm/shared/config-templates/scanner/config.yaml.tpl delete mode 100644 image/templates/helm/shared/internal/scanner-config-shape.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-00-serviceaccount.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-01-psps.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-01-security.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-02-db-password-secret.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-03-tls-secret.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-04-scanner-config.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-05-network-policy.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-06-deployment.yaml.htpl delete mode 100644 image/templates/helm/shared/templates/02-scanner-07-service.yaml delete mode 100644 image/templates/helm/shared/templates/02-scanner-08-hpa.yaml delete mode 100644 image/templates/helm/shared/templates/_scanner_init.tpl.htpl delete mode 100644 image/templates/helm/stackrox-secured-cluster/internal/defaults/70-scanner.yaml delete mode 100644 image/templates/helm/stackrox-secured-cluster/templates/_scanner-defaulting.tpl delete mode 100644 image/templates/helm/stackrox-secured-cluster/values-scanner.yaml.example delete mode 100644 operator/internal/securedcluster/scanner/defaults.go create mode 100644 operator/tests/securedcluster/sc-basic/910-errors.yaml create mode 100644 operator/tests/upgrade/upgrade/033-errors.yaml delete mode 100755 scripts/ci/is-scanner-v2-available.sh create mode 100644 ui/apps/platform/cypress/fixtures/vulnmanagement/nodeCves.json delete mode 100644 ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index cad5a34347a3a..55831a9784654 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -42,10 +42,12 @@ jobs: POSTGRES_VERSION: "17" LONGTERM_LICENSE: "unused" BYODB_TEST: "true" + # roxctl helm values pin scannerV4.disable=true unless this is set. + ROX_SCANNER_V4: "true" ROX_RISK_REPROCESSING_INTERVAL: "15s" ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL: "30s" PYTHONUNBUFFERED: "1" - timeout-minutes: 150 + timeout-minutes: 361 steps: - name: Checkout repo @@ -138,7 +140,7 @@ jobs: CLOUD_SOURCES_TEST_OCM_CLIENT_ID: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_ID }} CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET }} BUILD_ID: ${{ github.run_id }} - timeout-minutes: 110 + timeout-minutes: 245 run: tests/byodb/run.sh /tmp/byodb-test-logs - name: Persist env vars for post-test diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index 3d000345db4a7..9d33fc971103d 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -39,6 +39,7 @@ jobs: # gke-upgrade-tests expect all sensor, central and postgres tests to run together in a single sequence. CI_JOB_NAME: gke-upgrade-tests-central ARTIFACT_DIR: ./junit-reports + SCANNER_V4_DB_STORAGE_CLASS: faster # Make sure Python steps don't buffer stdout/stderr. PYTHONUNBUFFERED: "1" timeout-minutes: 180 # Keep in sync with cluster's lifespan diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index e68926ead76db..a26a14434d63a 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -39,13 +39,13 @@ jobs: LOAD_BALANCER: lb ORCHESTRATOR_FLAVOR: k8s KUBERNETES_PROVIDER: gke - SENSOR_SCANNER_SUPPORT: "true" + SENSOR_SCANNER_V4_SUPPORT: "true" ROX_DEPLOY_SENSOR_WITH_CRS: "true" SENSOR_HELM_MANAGED: "true" SCANNER_V4_DB_STORAGE_CLASS: faster STORAGE: pvc STORAGE_CLASS: faster - ROX_SCANNER_V4: "false" + ROX_SCANNER_V4: "true" PYTHONUNBUFFERED: "1" timeout-minutes: 120 steps: diff --git a/central/certgen/central_scanner.go b/central/certgen/central_scanner.go index ac0d482b346df..485a91162e519 100644 --- a/central/certgen/central_scanner.go +++ b/central/certgen/central_scanner.go @@ -77,33 +77,19 @@ func (s *serviceImpl) scannerHandler(w http.ResponseWriter, r *http.Request) { return } - secrets, ca, err := initializeSecretsWithCACertAndKey() - if err != nil { - httputil.WriteGRPCStyleError(w, codes.Internal, err) - return - } - - namespace := env.Namespace.Setting() - if r.URL.Query().Get("v") == "4" { - s.scannerV4Handler(w, secrets, ca, namespace) + if r.URL.Query().Get("v") != "4" { + httputil.WriteErrorf(w, http.StatusBadRequest, + "legacy Scanner certificate generation is not supported; pass v=4 for Scanner V4") return } - if err := certgen.IssueScannerCerts(secrets, ca, mtls.WithNamespace(namespace)); err != nil { - httputil.WriteGRPCStyleError(w, codes.Internal, err) - return - } - - rendered, err := renderer.RenderScannerTLSSecretOnly(renderer.Config{ - K8sConfig: &renderer.K8sConfig{}, - SecretsByteMap: secrets, - }, defaults.GetImageFlavorFromEnv()) + secrets, ca, err := initializeSecretsWithCACertAndKey() if err != nil { - httputil.WriteGRPCStyleErrorf(w, codes.Internal, "failed to render scanner TLS file: %v", err) + httputil.WriteGRPCStyleError(w, codes.Internal, err) return } - writeFile(w, rendered, "scanner-tls.yaml") + s.scannerV4Handler(w, secrets, ca, env.Namespace.Setting()) } func (s *serviceImpl) scannerV4Handler(w http.ResponseWriter, secrets map[string][]byte, ca mtls.CA, namespace string) { diff --git a/central/certgen/central_scanner_test.go b/central/certgen/central_scanner_test.go new file mode 100644 index 0000000000000..3ee7daee90088 --- /dev/null +++ b/central/certgen/central_scanner_test.go @@ -0,0 +1,42 @@ +package certgen + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestScannerHandlerRejectsUnsupportedVersion(t *testing.T) { + s := &serviceImpl{} + cases := map[string]struct { + method string + rawURL string + want int + }{ + "should reject GET": { + method: http.MethodGet, + rawURL: "/api/extensions/certgen/scanner?v=4", + want: http.StatusMethodNotAllowed, + }, + "should reject scanner v2": { + method: http.MethodPost, + rawURL: "/api/extensions/certgen/scanner", + want: http.StatusBadRequest, + }, + "should reject unknown version": { + method: http.MethodPost, + rawURL: "/api/extensions/certgen/scanner?v=2", + want: http.StatusBadRequest, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + req := httptest.NewRequest(tc.method, tc.rawURL, nil) + rec := httptest.NewRecorder() + s.scannerHandler(rec, req) + assert.Equal(t, tc.want, rec.Code) + }) + } +} diff --git a/central/clusters/zip/render_test.go b/central/clusters/zip/render_test.go index 97774b9b343cc..d07839e397119 100644 --- a/central/clusters/zip/render_test.go +++ b/central/clusters/zip/render_test.go @@ -3,7 +3,6 @@ package zip import ( "bufio" "bytes" - "strings" "testing" "github.com/stackrox/rox/central/clusters" @@ -88,18 +87,8 @@ func doTestRenderOpenshif(t *testing.T, clusterType storage.ClusterType) { _, exists := getEnvVarValue(complianceCont.Env, env.NodeInventoryContainerEnabled.EnvVar()) assert.False(t, exists, "compliance should not set %s; Go default is false", env.NodeInventoryContainerEnabled.EnvVar()) - - if clusterType == storage.ClusterType_OPENSHIFT4_CLUSTER { - nInvCont, found := findContainer(ds.Spec.Template.Spec.Containers, "node-inventory") - assert.True(t, found, "node-inventory container should exist under collector DS") - assert.Equal(t, "node-inventory", nInvCont.Name) - - expectedScannerParts := strings.Split(strings.ReplaceAll(complianceCont.Image, "/main:", "/scanner-slim:"), ":") - assert.Truef(t, strings.HasPrefix(nInvCont.Image, expectedScannerParts[0]), "scanner-slim image (%q) should be from the same registry as main (%q)", nInvCont.Image, complianceCont.Image) - } else { - _, foundNInv := findContainer(ds.Spec.Template.Spec.Containers, "node-inventory") - assert.False(t, foundNInv, "node-inventory container must not exist under collector DS") - } + _, foundNInv := findContainer(ds.Spec.Template.Spec.Containers, "node-inventory") + assert.False(t, foundNInv, "node-inventory container must not exist under collector DS") } cases := map[string]func(object runtime.Object){ diff --git a/central/securedclustercertgen/certificates.go b/central/securedclustercertgen/certificates.go index 750f0ef15754b..0ad193df79a4e 100644 --- a/central/securedclustercertgen/certificates.go +++ b/central/securedclustercertgen/certificates.go @@ -21,6 +21,8 @@ var ( // secretDataMap represents data stored as part of a secret. type secretDataMap = map[string][]byte +// TODO(ROX-36705): Remove Scanner V2 certs once 4.11 goes out of support. +// Older Secured Clusters may still run Scanner V2 and need these certificates. var scannerV2ServiceTypes = set.NewFrozenSet[storage.ServiceType](storage.ServiceType_SCANNER_SERVICE, storage.ServiceType_SCANNER_DB_SERVICE) var scannerV4ServiceTypes = set.NewFrozenSet[storage.ServiceType](storage.ServiceType_SCANNER_V4_INDEXER_SERVICE, storage.ServiceType_SCANNER_V4_DB_SERVICE) var localScannerServiceTypes = scannerV2ServiceTypes.Union(scannerV4ServiceTypes) diff --git a/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go b/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go index f57672c774c86..32f96a4cdf149 100644 --- a/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go +++ b/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go @@ -28,6 +28,7 @@ import ( "github.com/stackrox/rox/pkg/scancomponent" "github.com/stackrox/rox/pkg/scanners" "github.com/stackrox/rox/pkg/scanners/types" + "github.com/stackrox/rox/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -140,13 +141,12 @@ func Test_ThreePipelines_Run(t *testing.T) { }, }, setUpMocksAndEnv: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, false) t.Setenv(features.ScannerV4.EnvVar(), "true") t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") gomock.InOrder( - // node arrives + // node arrives: non-RHCOS with LegacyScanner off upserts UNSUPPORTED and skips Clairify m.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Eq(clusterID)).Times(1).Return(clusterID, true, nil), - m.cveDatastore.EXPECT().EnrichNodeWithSuppressedCVEs(gomock.Any()).Times(1).Return(), - m.riskStorage.EXPECT().UpsertRisk(gomock.Any(), gomock.Any()).MinTimes(1).Return(nil), m.nodeDatastore.EXPECT().UpsertNode(gomock.Any(), gomock.Any()).Times(1).Return(nil), // node index arrives m.nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(nodeID)).Times(1).Return(nodeWithScore, true, nil), @@ -175,6 +175,7 @@ func Test_ThreePipelines_Run(t *testing.T) { }, }, setUpMocksAndEnv: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) t.Setenv(features.ScannerV4.EnvVar(), "true") t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") gomock.InOrder( @@ -211,6 +212,7 @@ func Test_ThreePipelines_Run(t *testing.T) { }, }, setUpMocksAndEnv: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) t.Setenv(features.ScannerV4.EnvVar(), "true") t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") gomock.InOrder( @@ -244,6 +246,7 @@ func Test_ThreePipelines_Run(t *testing.T) { }, }, setUpMocksAndEnv: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) t.Setenv(features.ScannerV4.EnvVar(), "false") t.Setenv(features.NodeIndexEnabled.EnvVar(), "false") gomock.InOrder( @@ -269,6 +272,7 @@ func Test_ThreePipelines_Run(t *testing.T) { }, }, setUpMocksAndEnv: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) t.Setenv(features.ScannerV4.EnvVar(), "true") t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") gomock.InOrder( diff --git a/central/sensor/service/pipeline/nodeinventory/pipeline_test.go b/central/sensor/service/pipeline/nodeinventory/pipeline_test.go index d8741d4c24c88..b2ed240fef7cd 100644 --- a/central/sensor/service/pipeline/nodeinventory/pipeline_test.go +++ b/central/sensor/service/pipeline/nodeinventory/pipeline_test.go @@ -83,6 +83,7 @@ func Test_pipelineImpl_Run(t *testing.T) { { name: "when event action is CREATE_RESOURCE then do not ignore event", setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) node := storage.Node{ Id: "test node id", } @@ -103,6 +104,7 @@ func Test_pipelineImpl_Run(t *testing.T) { {Action: central.NodeInventoryACK_ACK}, }, setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) node := storage.Node{ Id: "test node id", } @@ -118,6 +120,7 @@ func Test_pipelineImpl_Run(t *testing.T) { { name: "when injector is nil then handle normally and don't panic", setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) node := storage.Node{ Id: "test node id", } @@ -134,6 +137,7 @@ func Test_pipelineImpl_Run(t *testing.T) { name: "when event has inventory for unknown node then no ACK should be sent", wantInjectorContain: []*central.NodeInventoryACK{}, setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) a.msg = createMsg("node1") a.injector = &recordingInjector{} gomock.InOrder( @@ -146,6 +150,7 @@ func Test_pipelineImpl_Run(t *testing.T) { wantInjectorContain: []*central.NodeInventoryACK{}, wantErr: "fetching error from DB", setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) a.msg = createMsg("node1") a.injector = &recordingInjector{} gomock.InOrder( @@ -188,6 +193,7 @@ func Test_pipelineImpl_Run(t *testing.T) { } func Test_pipelineImpl_Run_SendsSensorAndLegacyACKs(t *testing.T) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) @@ -254,6 +260,7 @@ func Test_pipelineImpl_Run_SendsSensorAndLegacyACKs(t *testing.T) { } func Test_pipelineImpl_Run_SkipsSensorACKWhenCapabilityMissing(t *testing.T) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) diff --git a/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go b/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go index 566793f840382..ce04f4ed06ff5 100644 --- a/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go +++ b/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go @@ -21,11 +21,13 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/metrics" nodeEnricher "github.com/stackrox/rox/pkg/nodes/enricher" "github.com/stackrox/rox/pkg/scancomponent" "github.com/stackrox/rox/pkg/scanners" "github.com/stackrox/rox/pkg/scanners/types" + "github.com/stackrox/rox/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -105,6 +107,7 @@ func Test_TwoPipelines_Run(t *testing.T) { }, }, setUpMocks: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) m.nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(nodeID)).MinTimes(1).Return(nil, false, nil) }, wantNodeExists: false, @@ -122,6 +125,7 @@ func Test_TwoPipelines_Run(t *testing.T) { }, }, setUpMocks: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) gomock.InOrder( // node arrives m.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Eq(clusterID)).Times(1).Return(clusterID, true, nil), @@ -154,6 +158,7 @@ func Test_TwoPipelines_Run(t *testing.T) { }, }, setUpMocks: func(t *testing.T, m *usedMocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) gomock.InOrder( // node inventory arrives m.nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(nodeID)).Return(nil, false, nil), diff --git a/central/sensor/service/pipeline/nodes/pipeline_test.go b/central/sensor/service/pipeline/nodes/pipeline_test.go index d9f1dfb13b602..c6ba35109fdae 100644 --- a/central/sensor/service/pipeline/nodes/pipeline_test.go +++ b/central/sensor/service/pipeline/nodes/pipeline_test.go @@ -77,6 +77,7 @@ func Test_pipelineImpl_Run(t *testing.T) { { name: "when node is not full host scanned then enrich and upsert with risk", setUp: func(t *testing.T, a *args, m *mocks) { + testutils.MustUpdateFeature(t, features.LegacyScanner, true) a.msg = createMsg("Something that is not RHCOS") a.clusterID = "test cluster id" gomock.InOrder( diff --git a/deploy/common/deploy.sh b/deploy/common/deploy.sh index 39be0003ffe1b..f4bfce7728b87 100755 --- a/deploy/common/deploy.sh +++ b/deploy/common/deploy.sh @@ -27,22 +27,6 @@ echo "StackRox central db image tag set to $CENTRAL_DB_IMAGE_TAG" export CENTRAL_DB_IMAGE="${CENTRAL_DB_IMAGE:-${CENTRAL_DB_IMAGE_REPO}:${CENTRAL_DB_IMAGE_TAG}}" echo "StackRox central db image set to $CENTRAL_DB_IMAGE" -export SCANNER_IMAGE_REPO="${SCANNER_IMAGE_REPO:-$DEFAULT_IMAGE_REGISTRY/scanner}" -echo "SCANNER_IMAGE_REPO set to $SCANNER_IMAGE_REPO" - -SCANNER_IMAGE_TAG=$(make --quiet --no-print-directory -C "$(git rev-parse --show-toplevel)" scanner-tag) -export SCANNER_IMAGE_TAG -echo "SCANNER_IMAGE_TAG set to $SCANNER_IMAGE_TAG" - -export SCANNER_IMAGE="${SCANNER_IMAGE:-${SCANNER_IMAGE_REPO}:${SCANNER_IMAGE_TAG}}" -echo "SCANNER_IMAGE image set to $SCANNER_IMAGE" - -export SCANNER_DB_IMAGE_REPO="${SCANNER_DB_IMAGE_REPO:-$DEFAULT_IMAGE_REGISTRY/scanner-db}" -echo "SCANNER_DB_IMAGE_REPO set to $SCANNER_DB_IMAGE_REPO" - -export SCANNER_DB_IMAGE="${SCANNER_DB_IMAGE:-${SCANNER_DB_IMAGE_REPO}:${SCANNER_IMAGE_TAG}}" -echo "SCANNER_DB_IMAGE image set to $SCANNER_DB_IMAGE" - export ROXCTL_IMAGE_REPO="${ROXCTL_IMAGE_REPO:-$DEFAULT_IMAGE_REGISTRY/roxctl}" echo "ROXCTL_IMAGE_REPO set to $ROXCTL_IMAGE_REPO" diff --git a/deploy/common/env.sh b/deploy/common/env.sh index 4c0c1219aa163..7d105df97857c 100644 --- a/deploy/common/env.sh +++ b/deploy/common/env.sh @@ -98,9 +98,6 @@ fi export SENSOR_DEV_RESOURCES="${SENSOR_DEV_RESOURCES:-true}" echo "SENSOR_DEV_RESOURCES is set to ${SENSOR_DEV_RESOURCES}" -export SENSOR_SCANNER_SUPPORT="${SENSOR_SCANNER_SUPPORT:-false}" -echo "SENSOR_SCANNER_SUPPORT is set to ${SENSOR_SCANNER_SUPPORT}" - export SENSOR_SCANNER_V4_SUPPORT="${SENSOR_SCANNER_V4_SUPPORT:-false}" echo "SENSOR_SCANNER_V4_SUPPORT is set to ${SENSOR_SCANNER_V4_SUPPORT}" diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index 5492e32b9480c..bbef8350d1bf2 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -224,8 +224,6 @@ function launch_central { add_args -i "${MAIN_IMAGE}" add_args "--central-db-image=${CENTRAL_DB_IMAGE}" - add_args "--scanner-image=${SCANNER_IMAGE}" - add_args "--scanner-db-image=${SCANNER_DB_IMAGE}" add_args "--image-defaults=${ROXCTL_ROX_IMAGE_FLAVOR}" @@ -369,10 +367,6 @@ function launch_central { if [[ "${central_namespace}" != "stackrox" ]]; then helm_args+=(--set "allowNonstandardNamespace=true") fi - if [[ "$SCANNER_SUPPORT" != "true" ]]; then - helm_args+=(--set scanner.disable=true) - fi - if [[ "${CGO_CHECKS}" == "true" ]]; then echo "CGO_CHECKS set to true. Setting GOEXPERIMENT=cgocheck2 and MUTEX_WATCHDOG_TIMEOUT_SECS=15" # Extend mutex watchdog timeout because cgochecks hamper performance @@ -588,11 +582,6 @@ function launch_central { fi if [[ "$SCANNER_SUPPORT" == "true" ]]; then - echo "Deploying Scanner..." - if [[ -n "${REGISTRY_USERNAME}" ]]; then - "${unzip_dir}/scanner/scripts/setup.sh" - fi - launch_service "${unzip_dir}" scanner if [[ "${ROX_SCANNER_V4:-}" != "false" ]]; then if [[ -d "${unzip_dir}/scanner-v4" ]]; then echo "Deploying ScannerV4..." @@ -629,14 +618,6 @@ function launch_central { echo >&2 "Possible reason for this: the roxctl in PATH does not support Scanner V4." fi fi - - if [[ -n "$CI" ]]; then - ${ORCH_CMD} -n stackrox patch deployment scanner --patch "$(cat "${common_dir}/scanner-patch.yaml")" - ${ORCH_CMD} -n stackrox patch hpa scanner --patch "$(cat "${common_dir}/scanner-hpa-patch.yaml")" - elif [[ "${is_local_dev}" == "true" ]]; then - ${ORCH_CMD} -n stackrox patch deployment scanner --patch "$(cat "${common_dir}/scanner-local-patch.yaml")" - ${ORCH_CMD} -n stackrox patch hpa scanner --patch "$(cat "${common_dir}/scanner-hpa-patch.yaml")" - fi echo fi fi @@ -946,10 +927,6 @@ function launch_sensor { helm_args+=(--set "helmManaged=false") fi - if [[ "$SENSOR_SCANNER_SUPPORT" == "true" ]]; then - helm_args+=(--set scanner.disable=false) - fi - if [[ "$SENSOR_SCANNER_V4_SUPPORT" == "true" ]]; then helm_args+=(--set scannerV4.disable=false) fi diff --git a/deploy/common/local-dev-values.yaml b/deploy/common/local-dev-values.yaml index 2a834c5b34d1b..25b8701ab94cb 100644 --- a/deploy/common/local-dev-values.yaml +++ b/deploy/common/local-dev-values.yaml @@ -56,23 +56,3 @@ scannerV4: limits: cpu: "1000m" memory: "2500Mi" - -scanner: - replicas: 1 - autoscaling: - disable: true - - resources: - requests: - memory: "500Mi" - cpu: "500m" - limits: - memory: "2500Mi" - cpu: "2000m" - dbResources: - requests: - cpu: "400m" - memory: "512Mi" - limits: - cpu: "2000m" - memory: "4Gi" diff --git a/deploy/common/scanner-hpa-patch.yaml b/deploy/common/scanner-hpa-patch.yaml deleted file mode 100644 index ef680ea967e8b..0000000000000 --- a/deploy/common/scanner-hpa-patch.yaml +++ /dev/null @@ -1,2 +0,0 @@ -spec: - minReplicas: 1 diff --git a/deploy/common/scanner-local-patch.yaml b/deploy/common/scanner-local-patch.yaml deleted file mode 100644 index eb614fc564391..0000000000000 --- a/deploy/common/scanner-local-patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -spec: - replicas: 1 - template: - spec: - containers: - - name: scanner - resources: - requests: - cpu: "250m" - memory: "500Mi" - limits: - cpu: null - memory: "2500Mi" diff --git a/deploy/common/scanner-patch.yaml b/deploy/common/scanner-patch.yaml deleted file mode 100644 index 9f295c35179c2..0000000000000 --- a/deploy/common/scanner-patch.yaml +++ /dev/null @@ -1,2 +0,0 @@ -spec: - replicas: 2 diff --git a/image/templates/common/setup-scanner.sh b/image/templates/common/setup-scanner.sh deleted file mode 100755 index eace4888c7d64..0000000000000 --- a/image/templates/common/setup-scanner.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" - -KUBE_COMMAND=${KUBE_COMMAND:-{{.K8sConfig.Command}}} -NAMESPACE="${ROX_NAMESPACE:-stackrox}" - -${KUBE_COMMAND} get namespace "$NAMESPACE" &>/dev/null || ${KUBE_COMMAND} create namespace "$NAMESPACE" -${KUBE_COMMAND} annotate "namespace/${NAMESPACE}" --overwrite openshift.io/node-selector="" - -if ! ${KUBE_COMMAND} get secret/{{.K8sConfig.ScannerSecretName}} -n "$NAMESPACE" > /dev/null; then - registry_auth="$("${DIR}/docker-auth.sh" -m k8s "{{.K8sConfig.ScannerRegistry}}")" - [[ -n "$registry_auth" ]] || { echo >&2 "Unable to get registry auth info." ; exit 1 ; } - ${KUBE_COMMAND} create --namespace "$NAMESPACE" -f - <] !templates/_*.tpl -!templates/02-scanner-[0-9]*.yaml !templates/02-scanner-v4-*.yaml templates/* [< else if eq .RenderMode "scannerTLSOnly" >] !templates/_*.tpl -!templates/02-scanner-03-tls-secret.yaml templates/* [< else if eq .RenderMode "scannerV4TLSOnly" >] !templates/_*.tpl diff --git a/image/templates/helm/stackrox-central/README.md.htpl b/image/templates/helm/stackrox-central/README.md.htpl index 0512705ac7a59..6fbade7eeacf1 100644 --- a/image/templates/helm/stackrox-central/README.md.htpl +++ b/image/templates/helm/stackrox-central/README.md.htpl @@ -1,7 +1,7 @@ # StackRox Kubernetes Security Platform - Central Services Helm Chart This Helm chart allows you to deploy the central services of the StackRox -Kubernetes Security Platform: StackRox Central and StackRox Scanner. +Kubernetes Security Platform: StackRox Central and Scanner V4. If you want to install Red Hat Advanced Cluster Security, refer to [Installing quickly using Helm charts](https://docs.openshift.com/acs/installing/installing_helm/install-helm-quick.html) @@ -124,15 +124,6 @@ can be configured via those files: loadBalancer: enabled: true - scanner: - # Run without StackRox Scanner (NOT RECOMMENDED) - disable: true - - scannerV4: - # Enable Scanner V4, which will become the default scanner for StackRox. - # Note that enabling Scanner V4 while having the StackRox Scanner disabled (scanner.disable=true) - # is not a supported configuration. - disable: false customize: # Apply the important-service=true label for all objects managed by this chart. @@ -195,27 +186,15 @@ If, during an upgrade of the Helm release, the Helm chart needs to generate a ne some component, it is required to pass in the existing CA, for example by adding `-f generated-values.yaml` to the arguments for `helm upgrade`. -### Enabling Scanner V4 +### Scanner V4 -Beginning with StackRox 4.4, a new scanner component based on ClairCore named Scanner V4 is integrated. -In a future version of StackRox, Scanner V4 will become the new default scanner and replace the existing -scanner named StackRox Scanner. Currently, Scanner V4 is not installed by default, but it -can be enabled alongside StackRox Scanner using the following Helm chart setting: +Scanner V4 is the vulnerability scanner for StackRox, based on ClairCore. It is enabled by default +and can be disabled by setting: ``` scannerV4: - disable: false + disable: true ``` - -To enable Scanner V4 for an existing installation of this chart, the CA certificate -and its key must be provided to the `helm upgrade` command. -For example, if the CA was generated ahead of time and injected into the `helm install` command, then -the `--reuse-values` flag for `helm upgrade` can be used to provide the certificate and key. However, if the CA was -generated by the Helm chart at installation time, you must retrieve the generated secret -values from the cluster (see above) and provide those to the `helm upgrade` command. - -Note that because Scanner V4 currently runs simultaneously with StackRox Scanner, your -cluster must have sufficient computing resources available to host both scanner components at the same time. The default resource configuration for Scanner V4 components can be found in the file internal/defaults.yaml. diff --git a/image/templates/helm/stackrox-central/internal/config-shape.yaml b/image/templates/helm/stackrox-central/internal/config-shape.yaml index 46786401bd947..d0d96cd6ccda9 100644 --- a/image/templates/helm/stackrox-central/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-central/internal/config-shape.yaml @@ -163,18 +163,6 @@ customize: podLabels: {} # dict podAnnotations: {} # dict envVars: {} # dict - scanner: - labels: {} # dict - annotations: {} # dict - podLabels: {} # dict - podAnnotations: {} # dict - envVars: {} # dict - scanner-db: - labels: {} # dict - annotations: {} # dict - podLabels: {} # dict - podAnnotations: {} # dict - envVars: {} # dict scanner-v4-indexer: labels: {} # dict annotations: {} # dict diff --git a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl index 969760137a7c6..b008adbca4019 100644 --- a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl +++ b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl @@ -157,68 +157,6 @@ defaults: limits: memory: "128Mi" cpu: "500m" - scanner: - disable: false - replicas: 3 - logLevel: INFO - mode: full - - autoscaling: - disable: false - minReplicas: 2 - maxReplicas: 5 - - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchLabels: - app: scanner - topologyKey: kubernetes.io/hostname - nodeAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 50 - preference: - matchExpressions: - - key: node-role.kubernetes.io/infra - operator: Exists - - weight: 25 - preference: - matchExpressions: - - key: node-role.kubernetes.io/compute - operator: Exists - - weight: 100 - preference: - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: DoesNotExist - - resources: - requests: - memory: "1500Mi" - cpu: "1000m" - limits: - memory: "4Gi" - cpu: "2000m" - - image: - name: [< required "" .ScannerImageRemote >] - tag: [< required "" .ScannerImageTag >] - - dbResources: - limits: - cpu: "2000m" - memory: "4Gi" - requests: - cpu: "200m" - memory: "512Mi" - - dbImage: - name: [< required "" .ScannerDBImageRemote >] - tag: [< required "" .ScannerImageTag >] - scannerV4: image: name: [< required "" .ScannerV4ImageRemote >] diff --git a/image/templates/helm/stackrox-central/internal/expandables.yaml b/image/templates/helm/stackrox-central/internal/expandables.yaml index 8d9beded19d05..759e3018bd375 100644 --- a/image/templates/helm/stackrox-central/internal/expandables.yaml +++ b/image/templates/helm/stackrox-central/internal/expandables.yaml @@ -39,19 +39,6 @@ centralWorker: configController: nodeSelector: true resources: true -scanner: - resources: true - dbResources: true - nodeSelector: true - dbNodeSelector: true - dbPassword: - value: true - serviceTLS: - cert: true - key: true - dbServiceTLS: - cert: true - key: true scannerV4: indexer: nodeSelector: true diff --git a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl index b9dfda2932057..a1118f3121512 100644 --- a/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl +++ b/image/templates/helm/stackrox-central/templates/01-central-13-deployment.yaml.htpl @@ -147,13 +147,8 @@ spec: - name: ROX_SCANNER_V4 value: "true" {{- end }} - {{- if ._rox._legacyScannerEnabled }} - - name: ROX_LEGACY_SCANNER - value: "true" - {{- else }} - name: ROX_LEGACY_SCANNER value: "false" - {{- end }} [<- end >] {{- if ._rox.centralWorker.enabled }} - name: ROX_CENTRAL_WORKER_ENABLED diff --git a/image/templates/helm/stackrox-central/templates/_init.tpl.htpl b/image/templates/helm/stackrox-central/templates/_init.tpl.htpl index 3b235a9bb7543..dfce7350839b7 100644 --- a/image/templates/helm/stackrox-central/templates/_init.tpl.htpl +++ b/image/templates/helm/stackrox-central/templates/_init.tpl.htpl @@ -43,8 +43,6 @@ {{- include "srox.checkVersionRequirements" $ -}} {{ $configShape := $.Files.Get "internal/config-shape.yaml" | fromYaml }} -{{ $configShapeScanner := $.Files.Get "internal/scanner-config-shape.yaml" | fromYaml }} -{{ $_ = include "srox.mergeInto" (list $configShape $configShapeScanner) }} {{ $configShapeScannerV4 := $.Files.Get "internal/scanner-v4-config-shape.yaml" | fromYaml }} {{ $_ = include "srox.mergeInto" (list $configShape $configShapeScannerV4) }} @@ -269,6 +267,7 @@ {{ $_ = set $env "_proxyConfig" $proxyCfg }} {{ $_ = set $._rox "_renderMode" "[< .RenderMode >]" }} + {{/* Central setup. */}} @@ -278,23 +277,25 @@ [< end >] {{/* - Scanner setup. + Scanner V2 has been removed. Force disable and set legacy flag. + + We detect upgrades from a previous version that had scanner v2 by checking + whether the incoming values contain scanner.disable=false. This is the case + when --reuse-values carries forward the old chart's default. After the first + upgrade with this chart, scanner is overwritten to {disable: true}, so + subsequent --reuse-values upgrades will not trigger the note again. + (Compare with scanner v4 defaulting, which uses cluster lookups via the + stackrox-central-services-helm ConfigMap to detect the install version.) */}} -{{ $scannerCfg := $._rox.scanner }} +{{- if and $._rox.scanner (not (kindIs "invalid" $._rox.scanner.disable)) (not $._rox.scanner.disable) -}} + {{- if $.Release.IsUpgrade -}} + {{- include "srox.note" (list $ "The legacy Scanner has been removed in this release. Scanner V4 is the sole vulnerability scanner. Existing scanner/scanner-db resources will be removed.") -}} + {{- else -}} + {{- include "srox.warn" (list $ "The legacy Scanner has been removed. The `scanner.disable` setting is ignored. Scanner V4 is the replacement.") -}} + {{- end -}} +{{- end -}} -{{ if and $scannerCfg.disable (or $.Release.IsInstall $.Release.IsUpgrade) }} - {{/* We generally don't recommend customers run without scanner, so show a warning to the user */}} - {{ $action := ternary "deploy StackRox Central Services without Scanner" "upgrade StackRox Central Services without Scanner (possibly removing an existing Scanner deployment)" $.Release.IsInstall }} - {{ include "srox.warn" (list $ (printf "You have chosen to %s. Certain features dependent on image scanning might not work." $action)) }} -{{ else if not $scannerCfg.disable }} - {{ if and (ne $scannerCfg.mode "full") (ne $scannerCfg.mode "") }} - {{ include "srox.fail" (print "Only scanner full mode is allowed in Central. To solve this, set to full mode: scanner.mode=full.") }} - {{ end }} - {{ include "srox.scannerInit" (list $ $scannerCfg) }} -{{ end }} - -{{ $_ := set $._rox "_legacyScannerEnabled" (not $scannerCfg.disable) }} {{/* ScannerV4 setup. @@ -303,6 +304,11 @@ {{ $scannerV4Cfg := $._rox.scannerV4 }} {{ include "srox.scannerV4Init" (list $ $scannerV4Cfg) }} +{{/* Warn if scanner v4 disabled. */}} +{{- if $._rox.scannerV4.disable -}} + {{ include "srox.warn" (list $ "You are installing StackRox Central Services without Scanner V4. Vulnerability scanning will not be available.") }} +{{- end -}} + {{ if $._rox.scannerV4._dbEnabled }} {{ include "srox.scannerV4Volume" $ }} {{ end }} diff --git a/image/templates/helm/stackrox-central/values-private.yaml.example b/image/templates/helm/stackrox-central/values-private.yaml.example index 8576ca85f1e73..9cd7da00a8a4a 100644 --- a/image/templates/helm/stackrox-central/values-private.yaml.example +++ b/image/templates/helm/stackrox-central/values-private.yaml.example @@ -144,39 +144,6 @@ # # -----END RSA PRIVATE KEY----- # -# # Secret configuration options for the StackRox Scanner deployment. -# scanner: -# # The password to be used for authenticating database access. This is not user-relevant -# # and only serves to properly secure the database with a pre-shared secret. If this -# # setting is omitted, a password will be automatically generated upon initial deployment, -# # and the existing password will be used upon upgrades. -# dbPassword: -# value: -# -# # Internal "scanner.stackrox.svc" service TLS certificate for the Scanner deployment. -# # Omit to auto-generate (initial deployment) or use existing (upgrade). -# serviceTLS: -# cert: | -# -----BEGIN CERTIFICATE----- -# -# -----END CERTIFICATE----- -# key: | -# -----BEGIN RSA PRIVATE KEY----- -# -# -----END RSA PRIVATE KEY----- -# -# # Internal "scanner-db.stackrox" service TLS certificate for the Scanner DB deployment. -# # Omit to auto-generate (initial deployment) or use existing (upgrade). -# dbServiceTLS: -# cert: | -# -----BEGIN CERTIFICATE----- -# -# -----END CERTIFICATE----- -# key: | -# -----BEGIN RSA PRIVATE KEY----- -# -# -----END RSA PRIVATE KEY----- -# # # Secret configuration options for the Scanner V4 deployment. # scannerV4: # indexer: diff --git a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl index 0592d03c24a6e..c4be9e07fe0f4 100644 --- a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl +++ b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl @@ -44,7 +44,7 @@ # image: # # The registry relative to which all image references are resolved, unless # # a specific registry is provided for particular workloads which takes precedence -# # (see `central.image`, `db.image`, `scanner.image`, and `scanner.dbImage` below). +# # (see `central.image` and `db.image` below). # # This can be just a registry hostname such as `quay.io`, or a registry hostname with # # a "remote" component such as `us.gcr.io/my-stackrox-mirror`. # registry: us.gcr.io/my-stackrox-mirror @@ -334,127 +334,12 @@ # # been created. # pdName: gke-pv # -# # Public configuration options for the StackRox Scanner. -# scanner: -# # disable=true will cause the StackRox Kubernetes Security Platform to be -# # deployed without the StackRox Scanner, meaning that certain functionalities -# # may not be available. If this setting is changed prior to a `helm upgrade` -# # invocation, the existing StackRox scanner deployment will be removed. -# disable: false -# -# # The number of replicas for the Scanner deployment. If autoscaling is enabled (see below), -# # this determines the initial number of replicas. -# replicas: 3 -# -# # The log level for the scanner deployment. This typically does not need to be changed. -# logLevel: INFO -# -# # If you want to enforce StackRox Scanner to only run on certain nodes, you can specify -# # a node selector here to make sure Scanner can only be scheduled on Nodes with the -# # given label. -# nodeSelector: -# # This can contain arbitrary `label-key: label-value` pairs. -# role: stackrox-scanner -# -# If the nodes selected by the node selector are tainted, you can specify the corresponding taint tolerations here. -# tolerations: -# - effect: NoSchedule -# key: infra -# value: reserved -# - effect: NoExecute -# key: infra -# value: reserved -# -# If scheduling needs specific affinities, you can specify the corresponding affinities here. -# affinity: -# podAntiAffinity: -# preferredDuringSchedulingIgnoredDuringExecution: -# - weight: 100 -# podAffinityTerm: -# labelSelector: -# matchLabels: -# app: scanner -# topologyKey: kubernetes.io/hostname -# nodeAffinity: -# preferredDuringSchedulingIgnoredDuringExecution: -# - weight: 50 -# preference: -# matchExpressions: -# - key: node-role.kubernetes.io/infra -# operator: Exists -# - weight: 25 -# preference: -# matchExpressions: -# - key: node-role.kubernetes.io/compute -# operator: Exists -# - weight: 100 -# preference: -# matchExpressions: -# - key: node-role.kubernetes.io/control-plane -# operator: DoesNotExist -# -# # If you want to enforce StackRox Scanner DB to only run on certain nodes, you can specify -# # a node selector here to make sure Scanner DB can only be scheduled on Nodes with the -# # given label. -# dbNodeSelector: -# # This can contain arbitrary `label-key: label-value` pairs. -# role: stackrox-scanner-db -# -# If the nodes selected by the node selector are tainted, you can specify the corresponding taint tolerations here. -# dbTolerations: -# - effect: NoSchedule -# key: infra -# value: reserved -# - effect: NoExecute -# key: infra -# value: reserved -# -# # Configuration for autoscaling the Scanner deployment. -# autoscaling: -# # disable=true causes autoscaling to be disabled. All other settings in this section -# # will have no effect. -# disable: false -# # The minimum number of replicas for autoscaling. The following value is the default. -# minReplicas: 2 -# # The maximum number of replicas for autoscaling. The following value is the default. -# maxReplicas: 5 -# -# # Custom resource overrides for the Scanner deployment. -# resources: -# requests: -# memory: "1500Mi" -# cpu: "1000m" -# limits: -# memory: "4Gi" -# cpu: "2000m" -# -# # Custom resource overrides for the Scanner DB deployment. -# dbResources: -# limits: -# cpu: "2000m" -# memory: "4Gi" -# requests: -# cpu: "200m" -# memory: "200Mi" -# -# # Custom configuration of the image to be used for the Scanner deployment. -# # See `central.image` for a full example. -# image: -# registry: us.gcr.io/stackrox-scanner-repo -# name: scanner # "scanner" is the default -# -# dbImage: -# registry: us.gcr.io/stackrox-scanner-db-repo -# name: scanner-db # "scanner-db" is the default -# # # Public configuration options for Scanner V4. # scannerV4: # -# # If this is set to false, Scanner V4 will be enabled and deployed. -# # Note that currently Scanner V4 cannot be used as the only scanner component. -# # Instead it must be currently deployed side-by-side with StackRox Scanner (scanner.disable=false). +# # If this is set to true, Scanner V4 will not be deployed. # # If it is set to true, no other setting in this section will have any effect. -# disable: true +# disable: false # # # Scanner V4 consists of three components: indexer, matcher and db. # @@ -701,22 +586,6 @@ # podAnnotations: {} # envVars: {} # -# # Extra metadata for the scanner deployment only. -# scanner: -# labels: {} -# annotations: {} -# podLabels: {} -# podAnnotations: {} -# envVars: {} -# -# # Extra metadata for the scanner-db deployment only. -# scanner-db: -# labels: {} -# annotations: {} -# podLabels: {} -# podAnnotations: {} -# envVars: {} -# # # Extra metadata for all other objects. The keys in the following map can be # # an object name of the form "service/central-loadbalancer", or a reference to all # # objects of a given type in the form "service/*". The values under each key diff --git a/image/templates/helm/stackrox-central/values.yaml.htpl b/image/templates/helm/stackrox-central/values.yaml.htpl index 0be2a28e5d985..10325b1af358a 100644 --- a/image/templates/helm/stackrox-central/values.yaml.htpl +++ b/image/templates/helm/stackrox-central/values.yaml.htpl @@ -7,18 +7,16 @@ ## Instead, create a file that contains only those settings you want to override, ## and pass it to helm or roxctl via the `-f` parameter. ## -## For example, if you want to disable the deployment of scanner, create a file +## For example, if you want to disable Scanner V4, create a file ## `values-override.yaml` (or any name you choose) with the following contents: ## -## scanner: +## scannerV4: ## disable: true ## -## and then invoke helm by passing `-f values-override.yaml` to -## `helm install`/`helm upgrade`. +## and then invoke helm by passing `-f values-override.yaml` +## to `helm install`/`helm upgrade`. ## -## Alternatively, if you want to override just a few values, you can set them directly -## via the `--set` command, e.g., -## $ helm install --set scanner.disable=true ... +## Alternatively, you can set values directly via the `--set` command. ## ## Note that an arbitrary number of `-f` and `--set` parameters can be combined. It is ## generally a good practice to store secret data such as the admin password separate from @@ -248,78 +246,6 @@ central: # size: null # none: null # -## Configuration options relating to StackRox Scanner. -#scanner: -# # If this is set to true, StackRox will be deployed without scanner. No other setting in this -# # section will have any effect. -# disable: false -# -# # Default number of scanner replicas created upon startup. The actual number might be higher -# # or lower if autoscaling is enabled (see below). -# replicas: 3 -# -# logLevel: INFO -# -# # Settings related to autoscaling the scanner deployment. -# autoscaling: -# # If true, autoscaling will be disabled. None of the other settings in this section will -# # have any effect. -# disable: false -# minReplicas: 1 -# maxReplicas: 5 -# -# # Resource settings for the scanner deployment. -# resources: -# requests: -# memory: "1500Mi" -# cpu: "1000m" -# limits: -# memory: "4Gi" -# cpu: "2000m" -# -# image: -# registry: null -# name: scanner -# tag: 2.3.2 -# fullRef: null -# -# dbImage: -# registry: null -# name: scanner-db -# tag: 2.3.2 -# fullRef: null -# -# # Resource settings for the scanner-db deployment. -# dbResources: -# limits: -# cpu: 2 -# memory: 4Gi -# requests: -# cpu: 200m -# memory: 200Mi -# -# # The admin password setting for communication with scanner's DB. -# # When a value is set explicitly, this is always used, even on upgrade. -# # Otherwise, a password will be automatically generated if `generate` is set to true, -# # or left unset (null) and the Helm chart is being installed (as opposed to upgraded). -# dbPassword: -# value: null -# generate: null -# -# # Settings for the internal service-to-service TLS certificate used by scanner. -# # See the documentation for `ca` at the top level for an explanation. -# serviceTLS: -# cert: null -# key: null -# generate: null -# -# # Settings for the internal service-to-service TLS certificate used by scanner-db. -# # See the documentation for `ca` at the top level for an explanation. -# dbServiceTLS: -# cert: null -# key: null -# generate: null -# ## Configuration options relating to Scanner V4. #scannerV4: # diff --git a/image/templates/helm/stackrox-secured-cluster/.helmtplignore.htpl b/image/templates/helm/stackrox-secured-cluster/.helmtplignore.htpl index 31b00526c4d30..dd972083f89c9 100644 --- a/image/templates/helm/stackrox-secured-cluster/.helmtplignore.htpl +++ b/image/templates/helm/stackrox-secured-cluster/.helmtplignore.htpl @@ -22,13 +22,8 @@ feature-flag-values.yaml [<- end >] *.swp [<- if .KubectlOutput >] -templates/*scanner* -internal/scanner-config-shape.yaml -config-templates/scanner/ -values-scanner.yaml.example values-public-scanner-v4.yaml.example values-private-scanner-v4.yaml.example -internal/defaults/70-scanner.yaml templates/*scanner-v4* internal/*scanner-v4* internal/defaults/*scanner-v4* @@ -38,7 +33,5 @@ templates/00-storage-class.yaml [<- end >] [] templates/02-scanner-v4-*-matcher*.yaml -[] -templates/02-scanner-03-tls-secret.yaml templates/02-scanner-v4-03-db-tls-secret.yaml templates/02-scanner-v4-03-indexer-tls-secret.yaml diff --git a/image/templates/helm/stackrox-secured-cluster/README.md.htpl b/image/templates/helm/stackrox-secured-cluster/README.md.htpl index 467dd1d844dab..7cd10ae37398b 100644 --- a/image/templates/helm/stackrox-secured-cluster/README.md.htpl +++ b/image/templates/helm/stackrox-secured-cluster/README.md.htpl @@ -2,8 +2,7 @@ This Helm chart allows you to deploy the necessary services on a StackRox secured cluster: StackRox Sensor, StackRox Collector, and StackRox Admission -Control. Optionally StackRox Scanner can be deployed, either alone or together -with the new Scanner V4. +Control. It also deploys StackRox Scanner V4 by default. If you want to install Secured Cluster Services for Red Hat Advanced Cluster Security, refer to [Installing the secured-cluster-services Helm chart](https://docs.openshift.com/acs/installing/installing_helm/install-helm-quick.html#installing-secured-cluster-services-quickly_acs-install-helm-quick). @@ -170,9 +169,9 @@ The Secured Cluster Services Helm chart contains example configuration files (called `values-public.yaml.example` and `values-private.yaml.example`), that list all the available configuration options, along with documentation. -Furthermore, the files `values-scanner.yaml.example`, `values-public-scanner-v4.yaml.example` +Furthermore, the files `values-public-scanner-v4.yaml.example` and `values-private-scanner-v4.yaml.example` list the most commonly used configuration options -for StackRox Scanner and Scanner V4. +for Scanner V4. The following sample configuration file (`secured-cluster.yaml`) uses a few of the options which you can configure: @@ -321,8 +320,7 @@ non-standard environments: |`admissionControl.nodeSelector` | Node selector for Admission Control pods placement. | `null` (no placement constraints) | |`exposeMonitoring`| This setting controls whether the monitoring port (TCP 9090) should be exposed on the services. | `false` | |`env.openshift`| The StackRox Kubernetes Security Platform automatically detects the OpenShift version (`3.x` or `4.x`). Use this parameter to override the automatically detected version number, for example `4`. | `null` | -|`scanner.disable`| Scan images stored in the cluster's local registries. | `true` | -|`scannerV4.disable`| Enable Scanner V4. StackRox Scanner must be enabled as well (`scanner.disable=false``). | `true` | +|`scannerV4.disable`| Enable Scanner V4 for local image scanning. | `false` | ### Default resources Each of the baseline container's default resource settings are defined in the @@ -337,8 +335,8 @@ paths to the respective defaults for each container that this chart deploys: |NodeInventory |`collector.nodeScanningResources` | |Admission Control|`admissionControl.resources` | -Furthermore, default resource settings for StackRox Scanner and Scanner V4 can be -found in the files `internal/defaults/70-scanner.yaml` and `internal/defaults/70-scanner-v4.yaml`. +Furthermore, default resource settings for Scanner V4 can be +found in the file `internal/defaults/70-scanner-v4.yaml`. ### Customization settings diff --git a/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml b/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml index fa41057a3d802..c0c84066a2003 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml @@ -46,17 +46,6 @@ image: tag: null # string fullRef: null # string pullPolicy: null # string - scanner: - registry: null # string - name: null # string - repository: null # string - tag: null # string - fullRef: null # string - scannerDb: - registry: null # string - name: null # string - tag: null # string - fullRef: null # string scannerV4: registry: null # string name: null # string diff --git a/image/templates/helm/stackrox-secured-cluster/internal/defaults/50-images.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/internal/defaults/50-images.yaml.htpl index 01220b514dc68..7b254faf5ac70 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/defaults/50-images.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/internal/defaults/50-images.yaml.htpl @@ -14,13 +14,7 @@ image: name: [< required "" .FactImageRemote >] tag: [< required "" .FactImageTag >] [<- end >] - scanner: - name: [< required "" .ScannerSlimImageRemote >] - tag: [< required "" .ScannerImageTag >] [<- if not .KubectlOutput >] - scannerDb: - name: [< required "" .ScannerDBSlimImageRemote >] - tag: [< required "" .ScannerImageTag >] scannerV4: name: [< required "" .ScannerV4ImageRemote >] tag: [< required "" .ScannerV4ImageTag >] @@ -39,11 +33,7 @@ image: fact: registry: {{ ._rox.image.registry }} [<- end >] - scanner: - registry: {{ ._rox.image.registry }} [<- if not .KubectlOutput >] - scannerDb: - registry: {{ ._rox.image.registry }} scannerV4: registry: {{ ._rox.image.registry }} scannerV4DB: @@ -60,11 +50,7 @@ image: fact: repository: {{ list ._rox.image.fact.registry ._rox.image.fact.name | compact | join "/" }} [<- end >] - scanner: - repository: {{ list ._rox.image.scanner.registry ._rox.image.scanner.name | compact | join "/" }} [<- if not .KubectlOutput >] - scannerDb: - repository: {{ list ._rox.image.scannerDb.registry ._rox.image.scannerDb.name | compact | join "/" }} scannerV4: repository: {{ list ._rox.image.scannerV4.registry ._rox.image.scannerV4.name | compact | join "/" }} scannerV4DB: @@ -113,11 +99,7 @@ image: fact: fullRef: "{{ printf "%s:%s" ._rox.image.fact.repository ._rox.image.fact.tag }}" [<- end >] - scanner: - fullRef: "{{ printf "%s:%s" ._rox.image.scanner.repository ._rox.image.scanner.tag }}" [<- if not .KubectlOutput >] - scannerDb: - fullRef: "{{ printf "%s:%s" ._rox.image.scannerDb.repository ._rox.image.scannerDb.tag }}" scannerV4: fullRef: "{{ printf "%s:%s" ._rox.image.scannerV4.repository ._rox.image.scannerV4.tag }}" scannerV4DB: diff --git a/image/templates/helm/stackrox-secured-cluster/internal/defaults/70-scanner.yaml b/image/templates/helm/stackrox-secured-cluster/internal/defaults/70-scanner.yaml deleted file mode 100644 index 7ac80ee89e3be..0000000000000 --- a/image/templates/helm/stackrox-secured-cluster/internal/defaults/70-scanner.yaml +++ /dev/null @@ -1,37 +0,0 @@ -scanner: - replicas: 3 - logLevel: INFO - mode: slim - - autoscaling: - disable: false - minReplicas: 2 - maxReplicas: 5 - - resources: - requests: - memory: "1500Mi" - cpu: "1000m" - limits: - memory: "4Gi" - cpu: "2000m" - - dbResources: - limits: - cpu: "2000m" - memory: "4Gi" - requests: - cpu: "200m" - memory: "512Mi" - - slimImage: - name: "" - tag: "" - fullRef: "" - repository: "" - - slimDBImage: - name: "" - tag: "" - fullRef: "" - repository: "" diff --git a/image/templates/helm/stackrox-secured-cluster/internal/expandables.yaml b/image/templates/helm/stackrox-secured-cluster/internal/expandables.yaml index 4c5ebcc9ae580..d477c4a72aa5a 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/expandables.yaml +++ b/image/templates/helm/stackrox-secured-cluster/internal/expandables.yaml @@ -30,19 +30,6 @@ collector: nodeScanningResources: true famResources: true nodeSelector: true -scanner: - resources: true - dbResources: true - nodeSelector: true - dbNodeSelector: true - dbPassword: - value: true - serviceTLS: - cert: true - key: true - dbServiceTLS: - cert: true - key: true scannerV4: indexer: nodeSelector: true diff --git a/image/templates/helm/stackrox-secured-cluster/templates/_init.tpl.htpl b/image/templates/helm/stackrox-secured-cluster/templates/_init.tpl.htpl index 4314ed69a5add..dbcb084e0455d 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/_init.tpl.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/_init.tpl.htpl @@ -37,11 +37,8 @@ {{ $_ := set $ "_rox" $rox }} {{ $configShape := $.Files.Get "internal/config-shape.yaml" | fromYaml }} -{{/* Only merge scanner config shapes if kubectl output is disabled */}} +{{/* Only merge scanner-v4 config shapes if kubectl output is disabled */}} [<- if not .KubectlOutput >] - {{ $configShapeScanner := $.Files.Get "internal/scanner-config-shape.yaml" | fromYaml }} - {{ $_ = include "srox.mergeInto" (list $configShape $configShapeScanner) }} - {{/* Only merge scanner-v4 config shapes if kubectl output is disabled and feature flag is enabled */}} {{ $configShapeScannerV4 := $.Files.Get "internal/scanner-v4-config-shape.yaml" | fromYaml }} {{ $_ = include "srox.mergeInto" (list $configShape $configShapeScannerV4) }} [<- end >] @@ -149,12 +146,11 @@ [<- if not .KubectlOutput >] {{ $_ := include "srox.scannerV4Defaulting" (list $ .Release $._rox.scannerV4 $.stackroxHelm) }} -{{ include "srox.scannerDefaulting" (list . .Release $._rox.scanner $.stackroxHelm) }} -{{ if ne $._rox.scanner.mode "slim" }} - {{ include "srox.fail" (print "Only scanner slim mode is allowed in Secured Cluster. To solve this, set to slim mode: scanner.mode=slim.") }} -{{ end }} -{{ $_ := set $._rox.scanner "slimImage" $._rox.image.scanner }} -{{ $_ := set $._rox.scanner "slimDBImage" $._rox.image.scannerDb }} + +{{/* Scanner V2 has been removed. Warn if a user still tries to enable it. */}} +{{- if and $._rox.scanner (not (kindIs "invalid" $._rox.scanner.disable)) (not $._rox.scanner.disable) -}} + {{- include "srox.warn" (list $ "The legacy Scanner has been removed. The `scanner.disable` setting is ignored. Scanner V4 is the replacement.") -}} +{{- end -}} [<- end >] {{/* Expand applicable config values */}} @@ -264,7 +260,6 @@ {{ include "srox.configureImage" (list $ ._rox.image.main) }} {{ include "srox.configureImage" (list $ ._rox.image.collector) }} [<- if not .KubectlOutput >] -{{ include "srox.configureImage" (list $ ._rox.image.scanner) }} {{ include "srox.configureImage" (list $ ._rox.image.scannerV4) }} [<- end >] @@ -348,24 +343,18 @@ The following block checks for the validity of the provided init bundle. (`helm */}} -{{ $anyScannerEnabled := or (eq ._rox.scanner.disable false) (eq ._rox.scannerV4.disable false) }} +{{ $anyScannerEnabled := not ._rox.scannerV4.disable }} {{ if $anyScannerEnabled }} {{/* Check for sibling central in the same namespace. */}} {{ $centralDeployment := dict }} {{ include "srox.safeLookup" (list $ $centralDeployment "apps/v1" "Deployment" $.Release.Namespace "central") }} {{ if $centralDeployment.result }} {{ include "srox.note" (list $ "Detected central running in the same namespace. Not deploying scanner from this chart and configuring sensor to use existing scanner instance, if any.") }} - {{ $_ := set $._rox.scanner "disable" true }} {{ end }} {{ $_ := set $._rox.sensor.localImageScanning "enabled" "true" }} {{ end }} -{{ if eq ._rox.scanner.disable false }} - {{ include "srox.scannerInit" (list $ $._rox.scanner) }} - {{ $imagePullSecretNames = append $imagePullSecretNames "stackrox-scanner" }} -{{ end }} - {{ $scannerV4Cfg := $._rox.scannerV4 }} {{ if eq $scannerV4Cfg.disable false }} {{/* Copy secured-cluster-style image configuration to what the Scanner V4 configuration machinery expects. */}} diff --git a/image/templates/helm/stackrox-secured-cluster/templates/_scanner-defaulting.tpl b/image/templates/helm/stackrox-secured-cluster/templates/_scanner-defaulting.tpl deleted file mode 100644 index fabc13d1887eb..0000000000000 --- a/image/templates/helm/stackrox-secured-cluster/templates/_scanner-defaulting.tpl +++ /dev/null @@ -1,29 +0,0 @@ -{{/* - srox.scannerDefaulting - - Encapsulates the Scanner defaulting logic. - - Can be removed later, together with StackRox Scanner. -*/}} - -{{- define "srox.scannerDefaulting" -}} - -{{- $ := index . 0 -}} -{{- $helmRelease := index . 1 -}} -{{- $scanner := index . 2 -}} -{{- $stackroxHelm := index . 3 -}} - -{{- if kindIs "invalid" $scanner.disable -}} - {{/* Scanner neither explicitly enabled or disabled, apply defaulting logic. */}} - {{/* By default Scanner will be installed. */}} - {{- $_ := set $scanner "disable" false -}} - {{/* Currently there is one exception: when upgrading from a pre-4.8 version, which did not - install Scanner by default. */}} - {{- $installVersionUnknown := kindIs "invalid" $stackroxHelm.installXYVersion -}} - {{- $upgradingFromPre4_8 := or $installVersionUnknown (semverCompare "< 4.8" $stackroxHelm.installXYVersion) -}} - {{- if and $helmRelease.IsUpgrade $upgradingFromPre4_8 -}} - {{- include "srox.note" (list $ "StackRox Scanner disabled by default: this deployment was initially installed before version 4.8.") -}} - {{- $_ := set $scanner "disable" true -}} - {{- end -}} -{{- end -}} -{{- end -}} diff --git a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl index 560c982f38906..dbe4d1e98476c 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml.htpl @@ -236,43 +236,6 @@ spec: - mountPath: /tmp name: index-volume {{- end }} - {{- if eq ._rox.env.openshift 4 }} - - name: node-inventory - image: {{ quote ._rox.image.scanner.fullRef }} - imagePullPolicy: IfNotPresent - command: ["/scanner", "--nodeinventory", "--config=", ""] - ports: - - containerPort: 8444 - name: grpc - resources: - {{- ._rox.collector._nodeScanningResources | nindent 10 }} - securityContext: - privileged: true - env: - - name: ROX_NODE_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: spec.nodeName - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - {{- include "srox.envVars" (list . "daemonset" "collector" "node-inventory") | nindent 8 }} - volumeMounts: - - mountPath: /host - name: host-root-ro - readOnly: true - mountPropagation: HostToContainer - - mountPath: /tmp/ - name: tmp-volume - - mountPath: /cache - name: cache-volume - {{- end }} volumes: - hostPath: path: /proc @@ -322,14 +285,8 @@ spec: emptyDir: {} - name: etc-pki-volume emptyDir: {} - - name: tmp-volume - emptyDir: {} - name: collector-profiles emptyDir: {} - - name: cache-volume - emptyDir: - sizeLimit: 200Mi - - name: collector-config configMap: name: collector-config diff --git a/image/templates/helm/stackrox-secured-cluster/templates/sensor-netpol.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/sensor-netpol.yaml.htpl index 00e643e04921b..f9e3b522b4f80 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/sensor-netpol.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/sensor-netpol.yaml.htpl @@ -23,9 +23,6 @@ spec: matchLabels: app: admission-control {{- if ._rox.sensor.localImageScanning.enabled }} - - podSelector: - matchLabels: - app: scanner {{- if ._rox.scannerV4._indexerEnabled }} - podSelector: matchLabels: diff --git a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl index fb01669747cb3..3ae137cd892ca 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/templates/sensor.yaml.htpl @@ -233,8 +233,6 @@ spec: {{- end }} [<- if (not .KubectlOutput) >] {{- if ._rox.sensor.localImageScanning.enabled }} - - name: ROX_SCANNER_GRPC_ENDPOINT - value: {{ printf "scanner.%s.svc:8443" .Release.Namespace }} - name: ROX_LOCAL_IMAGE_SCANNING_ENABLED value: "true" {{- end }} diff --git a/image/templates/helm/stackrox-secured-cluster/values-public-scanner-v4.yaml.example b/image/templates/helm/stackrox-secured-cluster/values-public-scanner-v4.yaml.example index 18601be563a64..bb2f5cf4b7dfa 100644 --- a/image/templates/helm/stackrox-secured-cluster/values-public-scanner-v4.yaml.example +++ b/image/templates/helm/stackrox-secured-cluster/values-public-scanner-v4.yaml.example @@ -20,11 +20,9 @@ # # scannerV4: # -# # If this is set to false, Scanner V4 will be enabled and deployed. -# # Note that currently Scanner V4 cannot be used as the only scanner component. -# # Instead it must be currently deployed side-by-side with StackRox Scanner (scanner.disable=false). +# # If this is set to true, Scanner V4 will not be deployed. # # If it is set to true, no other setting in this section will have any effect. -# disable: true +# disable: false # # # Scanner V4 consists of three components: indexer, matcher and db. # # But when deployed as part of secured-cluster-services the only components diff --git a/image/templates/helm/stackrox-secured-cluster/values-scanner.yaml.example b/image/templates/helm/stackrox-secured-cluster/values-scanner.yaml.example deleted file mode 100644 index f7f3eb61a5f0a..0000000000000 --- a/image/templates/helm/stackrox-secured-cluster/values-scanner.yaml.example +++ /dev/null @@ -1,92 +0,0 @@ -# # NOTE: -# # The Scanner is not available in the secured clusters installed via roxctl. -# -# # Public configuration options for the StackRox Scanner: -# # When installing the Secured Cluster chart, a slim scanner mode is deployed with reduced image caching. -# # To run the scanner in the secured cluster, you must connect the Scanner to Sensor. -# -# # WARNING: -# # If deployed in the same namespace with Central it is only supported to install Scanner as part of Central's installation. -# # Sensor will use the existing Scanner to scan for local images. -# -# Image configuration for scanner: -# # For a complete example, see the `values-public.yaml.example` file. -# image: -# # Configuration for the `scanner` image that is used by Scanner. -# scanner: -# registry: null -# name: scanner-slim -# tag: null -# fullRef: null -# -# scanner: -# # disable=false Deploys a StackRox Scanner in the secured cluster to allow scanning images -# # from the OpenShift Container Platform cluster's local registries. -# disable: false -# -# # The number of replicas for the Scanner deployment. If autoscaling is enabled (see below), -# # this determines the initial number of replicas. -# replicas: 3 -# -# # The log level for the scanner deployment. This typically does not need to be changed. -# logLevel: INFO -# -# # If you want to enforce StackRox Scanner to only run on certain nodes, you can specify -# # a node selector here to make sure Scanner can only be scheduled on Nodes with the -# # given label. -# nodeSelector: -# # This can contain arbitrary `label-key: label-value` pairs. -# role: stackrox-scanner -# -# If the nodes selected by the node selector are tainted, you can specify the corresponding taint tolerations here. -# tolerations: -# - effect: NoSchedule -# key: infra -# value: reserved -# - effect: NoExecute -# key: infra -# value: reserved -# -# # If you want to enforce StackRox Scanner DB to only run on certain nodes, you can specify -# # a node selector here to make sure Scanner DB can only be scheduled on Nodes with the -# # given label. -# dbNodeSelector: -# # This can contain arbitrary `label-key: label-value` pairs. -# role: stackrox-scanner-db -# -# If the nodes selected by the node selector are tainted, you can specify the corresponding taint tolerations here. -# dbTolerations: -# - effect: NoSchedule -# key: infra -# value: reserved -# - effect: NoExecute -# key: infra -# value: reserved -# -# # Configuration for autoscaling the Scanner deployment. -# autoscaling: -# # disable=true causes autoscaling to be disabled. All other settings in this section -# # will have no effect. -# disable: false -# # The minimum number of replicas for autoscaling. The following value is the default. -# minReplicas: 2 -# # The maximum number of replicas for autoscaling. The following value is the default. -# maxReplicas: 5 -# -# # Custom resource overrides for the Scanner deployment. -# resources: -# requests: -# memory: "1500Mi" -# cpu: "1000m" -# limits: -# memory: "4Gi" -# cpu: "2000m" -# -# # Custom resource overrides for the Scanner DB deployment. -# dbResources: -# limits: -# cpu: "2000m" -# memory: "4Gi" -# requests: -# cpu: "200m" -# memory: "200Mi" diff --git a/operator/api/v1alpha1/central_types.go b/operator/api/v1alpha1/central_types.go index e0f9703aed71a..a33c3769d82f3 100644 --- a/operator/api/v1alpha1/central_types.go +++ b/operator/api/v1alpha1/central_types.go @@ -33,12 +33,13 @@ type CentralSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1,displayName="Central Component Settings" Central *CentralComponentSpec `json:"central,omitempty"` - // Settings for the Scanner component, which is responsible for vulnerability scanning of container - // images. - //+operator-sdk:csv:customresourcedefinitions:type=spec,order=2,displayName="Scanner Component Settings" + // Obsolete field. This field will be removed in a future release. + // The legacy Scanner has been removed. This field is ignored. + //+operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} Scanner *ScannerComponentSpec `json:"scanner,omitempty"` // Settings for the Scanner V4 component, which can run in addition to the previously existing Scanner components + // TODO(ROX-36705): renumber order annotations after legacy Scanner field removal //+operator-sdk:csv:customresourcedefinitions:type=spec,order=3,displayName="Scanner V4 Component Settings" ScannerV4 *ScannerV4Spec `json:"scannerV4,omitempty"` @@ -576,9 +577,7 @@ type ScannerComponentSpec struct { // ScannerV4Spec defines settings for the central "Scanner V4" component. type ScannerV4Spec struct { // Can be specified as "Enabled" or "Disabled". - // If this field is not specified, the following defaulting takes place: - // * for upgrades to 4.8 from previous releases, the default is: Disabled; - // * for new installations starting with ACS 4.8, the default is: Enabled. + // The default is: Enabled. //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1,displayName="Scanner V4 component" ScannerComponent *ScannerV4ComponentPolicy `json:"scannerComponent,omitempty"` @@ -617,14 +616,6 @@ func (s *ScannerComponentSpec) GetAnalyzer() *ScannerAnalyzerComponent { return s.Analyzer } -// IsEnabled checks whether scanner is enabled. This method is safe to be used with nil receivers. -func (s *ScannerComponentSpec) IsEnabled() bool { - if s == nil || s.ScannerComponent == nil { - return true // enabled by default - } - return *s.ScannerComponent == ScannerComponentEnabled -} - // ScannerComponentPolicy is a type for values of spec.scanner.scannerComponent. // +kubebuilder:validation:Enum=Enabled;Disabled type ScannerComponentPolicy string @@ -795,7 +786,7 @@ var ( // IsScannerEnabled returns true if scanner is enabled. func (c *Central) IsScannerEnabled() bool { - return c.Spec.Scanner.IsEnabled() + return false } // IsScannerV4Enabled returns true if Scanner V4 is enabled. diff --git a/operator/api/v1alpha1/securedcluster_types.go b/operator/api/v1alpha1/securedcluster_types.go index c04023ee039ff..922d58f0dadda 100644 --- a/operator/api/v1alpha1/securedcluster_types.go +++ b/operator/api/v1alpha1/securedcluster_types.go @@ -69,12 +69,13 @@ type SecuredClusterSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,order=7,displayName="Process Baselines Settings" ProcessBaselines *ProcessBaselinesSpec `json:"processBaselines,omitempty"` - // Settings for the Scanner component, which is responsible for vulnerability scanning of container - // images stored in a cluster-local image repository. - //+operator-sdk:csv:customresourcedefinitions:type=spec,order=8,displayName="Scanner Component Settings" + // Obsolete field. This field will be removed in a future release. + // The legacy Scanner has been removed. This field is ignored. + //+operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} Scanner *LocalScannerComponentSpec `json:"scanner,omitempty"` // Settings for the Scanner V4 components, which can run in addition to the previously existing Scanner components + // TODO(ROX-36705): renumber order annotations after legacy Scanner field removal //+operator-sdk:csv:customresourcedefinitions:type=spec,order=9,displayName="Scanner V4 Component Settings" ScannerV4 *LocalScannerV4ComponentSpec `json:"scannerV4,omitempty"` // Above default is necessary to make the nested default work see: https://github.com/kubernetes-sigs/controller-tools/issues/622 @@ -427,10 +428,7 @@ const ( // LocalScannerComponentSpec defines settings for the "scanner" component. type LocalScannerComponentSpec struct { - // If you do not want to deploy the Red Hat Advanced Cluster Security Scanner, you can disable it here - // (not recommended). - // If you do so, all the settings in this section will have no effect. - // The default is: AutoSense. + // Obsolete: The legacy Scanner has been removed. This field is ignored. //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Scanner Component",order=1 ScannerComponent *LocalScannerComponentPolicy `json:"scannerComponent,omitempty"` @@ -450,9 +448,7 @@ type LocalScannerV4ComponentSpec struct { // unless there is a Central resource in the same namespace. // In that case typically a central Scanner V4 will be deployed as a component of Central. // A value of "Disabled" means that Scanner V4 should not be installed. - // If this field is not specified or set to "Default", the following defaulting takes place: - // * for upgrades to 4.8 from previous releases, the default is: Disabled; - // * for new installations starting with ACS 4.8, the default is: AutoSense. + // The default is: AutoSense. //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Scanner V4 component",order=1 ScannerComponent *LocalScannerV4ComponentPolicy `json:"scannerComponent,omitempty"` diff --git a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml index 0dfdf85a0f630..157f231ecac11 100644 --- a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml @@ -2269,8 +2269,8 @@ spec: type: array scanner: description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images. + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. properties: analyzer: description: Settings pertaining to the analyzer deployment, such @@ -3131,9 +3131,7 @@ spec: scannerComponent: description: |- Can be specified as "Enabled" or "Disabled". - If this field is not specified, the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: Enabled. + The default is: Enabled. enum: - Default - Enabled diff --git a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml index 3de0b1b37d921..42d024df618ec 100644 --- a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml @@ -1032,8 +1032,8 @@ spec: type: string scanner: description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images stored in a cluster-local image repository. + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. properties: analyzer: description: Settings pertaining to the analyzer deployment, such @@ -1339,11 +1339,8 @@ spec: type: array type: object scannerComponent: - description: |- - If you do not want to deploy the Red Hat Advanced Cluster Security Scanner, you can disable it here - (not recommended). - If you do so, all the settings in this section will have no effect. - The default is: AutoSense. + description: 'Obsolete: The legacy Scanner has been removed. This + field is ignored.' enum: - AutoSense - Disabled @@ -1717,9 +1714,7 @@ spec: unless there is a Central resource in the same namespace. In that case typically a central Scanner V4 will be deployed as a component of Central. A value of "Disabled" means that Scanner V4 should not be installed. - If this field is not specified or set to "Default", the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: AutoSense. + The default is: AutoSense. enum: - Default - AutoSense diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index 933b94b863bb5..bddf14416dc89 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -84,11 +84,6 @@ spec: - description: Settings for the Central component, which is responsible for all user interaction. displayName: Central Component Settings path: central - - description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images. - displayName: Scanner Component Settings - path: scanner - description: Settings for the Scanner V4 component, which can run in addition to the previously existing Scanner components displayName: Scanner V4 Component Settings path: scannerV4 @@ -142,6 +137,13 @@ spec: path: misc x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. + displayName: Scanner + path: scanner + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: |- Specify a secret that contains the administrator password in the "password" data item. If omitted, the operator will auto-generate a password and store it in the "password" item @@ -775,9 +777,7 @@ spec: path: scanner.monitoring.exposeEndpoint - description: |- Can be specified as "Enabled" or "Disabled". - If this field is not specified, the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: Enabled. + The default is: Enabled. displayName: Scanner V4 component path: scannerV4.scannerComponent - description: Settings pertaining to the indexer deployment. @@ -1034,11 +1034,6 @@ spec: - description: Settings relating to process baselines. displayName: Process Baselines Settings path: processBaselines - - description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images stored in a cluster-local image repository. - displayName: Scanner Component Settings - path: scanner - description: Settings for the Scanner V4 components, which can run in addition to the previously existing Scanner components displayName: Scanner V4 Component Settings path: scannerV4 @@ -1090,6 +1085,13 @@ spec: path: misc x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. + displayName: Scanner + path: scanner + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: |- Set to Disabled to disable policy enforcement for the admission controller. This is not recommended. On upgrades to 4.9 from previous releases, defaults to Enabled only if at least one of listenOnCreates or listenOnUpdates is true. @@ -1372,11 +1374,7 @@ spec: - description: A regex specifying namespaces whose process indicators should not be persisted. displayName: Exclude Namespace Regex path: processIndicators.excludeNamespaceRegex - - description: |- - If you do not want to deploy the Red Hat Advanced Cluster Security Scanner, you can disable it here - (not recommended). - If you do so, all the settings in this section will have no effect. - The default is: AutoSense. + - description: 'Obsolete: The legacy Scanner has been removed. This field is ignored.' displayName: Scanner Component path: scanner.scannerComponent - description: Settings pertaining to the analyzer deployment, such as for autoscaling. @@ -1459,9 +1457,7 @@ spec: unless there is a Central resource in the same namespace. In that case typically a central Scanner V4 will be deployed as a component of Central. A value of "Disabled" means that Scanner V4 should not be installed. - If this field is not specified or set to "Default", the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: AutoSense. + The default is: AutoSense. displayName: Scanner V4 component path: scannerV4.scannerComponent - description: Settings pertaining to the indexer deployment. diff --git a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml index 3a8cfbede7a24..4dd017d30ccea 100644 --- a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml @@ -2268,8 +2268,8 @@ spec: type: array scanner: description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images. + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. properties: analyzer: description: Settings pertaining to the analyzer deployment, such @@ -3130,9 +3130,7 @@ spec: scannerComponent: description: |- Can be specified as "Enabled" or "Disabled". - If this field is not specified, the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: Enabled. + The default is: Enabled. enum: - Default - Enabled diff --git a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml index 237fe377697b9..e3f7245601a5e 100644 --- a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml @@ -1031,8 +1031,8 @@ spec: type: string scanner: description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images stored in a cluster-local image repository. + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. properties: analyzer: description: Settings pertaining to the analyzer deployment, such @@ -1338,11 +1338,8 @@ spec: type: array type: object scannerComponent: - description: |- - If you do not want to deploy the Red Hat Advanced Cluster Security Scanner, you can disable it here - (not recommended). - If you do so, all the settings in this section will have no effect. - The default is: AutoSense. + description: 'Obsolete: The legacy Scanner has been removed. This + field is ignored.' enum: - AutoSense - Disabled @@ -1716,9 +1713,7 @@ spec: unless there is a Central resource in the same namespace. In that case typically a central Scanner V4 will be deployed as a component of Central. A value of "Disabled" means that Scanner V4 should not be installed. - If this field is not specified or set to "Default", the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: AutoSense. + The default is: AutoSense. enum: - Default - AutoSense diff --git a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml index 8012caafb1752..ec980c0ddc385 100644 --- a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml @@ -244,9 +244,7 @@ spec: path: scannerV4.monitoring.exposeEndpoint - description: |- Can be specified as "Enabled" or "Disabled". - If this field is not specified, the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: Enabled. + The default is: Enabled. displayName: Scanner V4 component path: scannerV4.scannerComponent - description: |- @@ -331,11 +329,6 @@ spec: All values are strings but are converted into appropriate type based on schema. displayName: Value path: overlays[0].patches[0].value - - description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images. - displayName: Scanner Component Settings - path: scanner - description: Settings pertaining to the analyzer deployment, such as for autoscaling. displayName: Analyzer path: scanner.analyzer @@ -951,6 +944,13 @@ spec: path: misc x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. + displayName: Scanner + path: scanner + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Allows you to specify additional trusted Root CAs. displayName: Additional CAs path: tls.additionalCAs @@ -1100,11 +1100,8 @@ spec: The default is: Enabled. displayName: Autoscaling path: scanner.analyzer.scaling.autoScaling - - description: |- - If you do not want to deploy the Red Hat Advanced Cluster Security Scanner, you can disable it here - (not recommended). - If you do so, all the settings in this section will have no effect. - The default is: AutoSense. + - description: 'Obsolete: The legacy Scanner has been removed. This field is + ignored.' displayName: Scanner Component path: scanner.scannerComponent - description: |- @@ -1145,9 +1142,7 @@ spec: unless there is a Central resource in the same namespace. In that case typically a central Scanner V4 will be deployed as a component of Central. A value of "Disabled" means that Scanner V4 should not be installed. - If this field is not specified or set to "Default", the following defaulting takes place: - * for upgrades to 4.8 from previous releases, the default is: Disabled; - * for new installations starting with ACS 4.8, the default is: AutoSense. + The default is: AutoSense. displayName: Scanner V4 component path: scannerV4.scannerComponent - description: Settings for the virtual machine scraper running in Sensor. @@ -1382,11 +1377,6 @@ spec: - description: Settings relating to process baselines. displayName: Process Baselines Settings path: processBaselines - - description: |- - Settings for the Scanner component, which is responsible for vulnerability scanning of container - images stored in a cluster-local image repository. - displayName: Scanner Component Settings - path: scanner - description: Settings for the Scanner V4 components, which can run in addition to the previously existing Scanner components displayName: Scanner V4 Component Settings @@ -1646,6 +1636,13 @@ spec: path: misc x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Obsolete field. This field will be removed in a future release. + The legacy Scanner has been removed. This field is ignored. + displayName: Scanner + path: scanner + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Allows you to specify additional trusted Root CAs. displayName: Additional CAs path: tls.additionalCAs diff --git a/operator/internal/central/defaults/scanner_v4.go b/operator/internal/central/defaults/scanner_v4.go index eb1dfc449200c..0fc0ed97f515d 100644 --- a/operator/internal/central/defaults/scanner_v4.go +++ b/operator/internal/central/defaults/scanner_v4.go @@ -1,11 +1,8 @@ package defaults import ( - "reflect" - "github.com/go-logr/logr" platform "github.com/stackrox/rox/operator/api/v1alpha1" - "github.com/stackrox/rox/operator/internal/common" ) var ( @@ -16,14 +13,11 @@ var ( ) // Only returns Enabled or Disabled. -// Derive component policy based on status Defaults and spec. -// This will be called from the preExtension to record the current setting. +// Scanner V4 is now always on (the legacy Scanner has been removed), so we default to +// Enabled unless the user explicitly set Disabled in the CR spec. // // Second return value is `true`, if defaulting has been applied due to lack of explicit setting. -func CentralScannerV4ComponentPolicy(logger logr.Logger, status *platform.CentralStatus, annotations map[string]string, spec *platform.ScannerV4Spec) (platform.ScannerV4ComponentPolicy, bool) { - defaultForUpgrades := platform.ScannerV4Disabled - defaultForNewInstallations := platform.ScannerV4Enabled - +func CentralScannerV4ComponentPolicy(logger logr.Logger, _ *platform.CentralStatus, _ map[string]string, spec *platform.ScannerV4Spec) (platform.ScannerV4ComponentPolicy, bool) { if spec != nil && spec.ScannerComponent != nil { comp := *spec.ScannerComponent if comp == platform.ScannerV4ComponentEnabled || comp == platform.ScannerV4ComponentDisabled { @@ -32,48 +26,7 @@ func CentralScannerV4ComponentPolicy(logger logr.Logger, status *platform.Centra } } - // User is relying on defaulting. - // This includes the case spec.ScannerComponent == "Default". - - // A default entry exists already, use recorded value. - recordedValue := platform.ScannerV4ComponentPolicy(annotations[common.FeatureDefaultKeyScannerV4]) - if recordedValue == platform.ScannerV4Enabled || recordedValue == platform.ScannerV4Disabled { - logger.Info("using previously recorded ScannerV4 componentPolicy", "componentPolicy", recordedValue) - return recordedValue, true - } - - // No or unexpected default set in the annotations. - - if isNewInstallation(status) { - logger.Info("Assuming new installation due to incomplete status.") - return defaultForNewInstallations, true - } - - // Upgrade. - logger.Info("assuming upgrade") - - if annotations[common.FeatureDefaultKeyScannerV4] == "" { - // No entry in the statusDefaults yet -> preserve defaulting behavior of versions which did not populate - // statusDefaults with a ScannerV4ComponentPolicy. - logger.Info("empty feature-default annotation, using default ScannerV4 componentPolicy for upgrades", - "componentPolicy", defaultForUpgrades) - return defaultForUpgrades, true - } - - // This should not happen, since we only store Enabled|Disabled, but just in case something unexpected happened - // and we need to make some decisions... - logger.Info("detected unexpected ScannerV4 componentPolicy in feature-default annotation, using default componentPolicy for upgrades", - "unexpectedComponentPolicy", recordedValue, - "componentPolicy", defaultForUpgrades) - return defaultForUpgrades, true -} - -// isNewInstallation checks if this is a new installation based on the status. -func isNewInstallation(status *platform.CentralStatus) bool { - // The ProductVersion is only set post installation. - return status == nil || - reflect.DeepEqual(status, &platform.CentralStatus{}) || - status.ProductVersion == "" + return platform.ScannerV4Enabled, true } func centralScannerV4Defaulting(logger logr.Logger, status *platform.CentralStatus, annotations map[string]string, spec *platform.CentralSpec, defaults *platform.CentralSpec) error { @@ -84,12 +37,8 @@ func centralScannerV4Defaulting(logger logr.Logger, status *platform.CentralStat return nil } - // User is relying on defaults. Set in-memory default and persist corresponding annotation. - - if annotations[common.FeatureDefaultKeyScannerV4] != string(componentPolicy) { - // Update feature default setting. - annotations[common.FeatureDefaultKeyScannerV4] = string(componentPolicy) - } + // User is relying on defaults. Set in-memory default only. + // We no longer persist the annotation to support downgrade scenarios. if defaults.ScannerV4 == nil { defaults.ScannerV4 = &platform.ScannerV4Spec{} diff --git a/operator/internal/central/defaults/static.go b/operator/internal/central/defaults/static.go index cb709e7174381..2617c971296b4 100644 --- a/operator/internal/central/defaults/static.go +++ b/operator/internal/central/defaults/static.go @@ -41,16 +41,6 @@ var staticDefaults = platform.CentralSpec{ Enabled: new(true), }, }, - Scanner: &platform.ScannerComponentSpec{ - Analyzer: &platform.ScannerAnalyzerComponent{ - Scaling: &platform.ScannerComponentScaling{ - AutoScaling: new(platform.ScannerAutoScalingEnabled), - Replicas: new(int32(3)), - MinReplicas: new(int32(2)), - MaxReplicas: new(int32(5)), - }, - }, - }, ScannerV4: &platform.ScannerV4Spec{ // ScannerComponent field is set using a dedicated defaulting flow. Indexer: &platform.ScannerV4Component{ diff --git a/operator/internal/central/extensions/reconcile_defaulting_test.go b/operator/internal/central/extensions/reconcile_defaulting_test.go index c715de8623e48..d5a6433747205 100644 --- a/operator/internal/central/extensions/reconcile_defaulting_test.go +++ b/operator/internal/central/extensions/reconcile_defaulting_test.go @@ -40,17 +40,11 @@ func TestReconcileScannerV4FeatureDefaultsExtension(t *testing.T) { Spec: platform.CentralSpec{}, Status: platform.CentralStatus{}, ExpectedDefault: &platform.ScannerV4Enabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentEnabled), - }, }, - "upgrade: disabled by default": { + "upgrade: enabled by default": { Spec: platform.CentralSpec{}, Status: postInstallStatus, - ExpectedDefault: &platform.ScannerV4Disabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, + ExpectedDefault: &platform.ScannerV4Enabled, }, "install: enabled explicitly": { Spec: platform.CentralSpec{ @@ -70,43 +64,24 @@ func TestReconcileScannerV4FeatureDefaultsExtension(t *testing.T) { Status: platform.CentralStatus{}, ExpectedDefault: nil, }, - "upgrade: pick up previously persisted default (Enabled)": { - Status: postInstallStatus, - Annotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentEnabled), - }, - ExpectedDefault: &platform.ScannerV4Enabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentEnabled), - }, - }, - "upgrade: pick up previously persisted default (Disabled)": { + "upgrade: previously persisted annotation is ignored": { Status: postInstallStatus, Annotations: map[string]string{ common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), }, - ExpectedDefault: &platform.ScannerV4Disabled, + ExpectedDefault: &platform.ScannerV4Enabled, ExpectedAnnotations: map[string]string{ common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), }, }, - "upgrade: ignoring bogus persisted default": { + "upgrade: bogus persisted annotation is ignored": { Status: postInstallStatus, Annotations: map[string]string{ common.FeatureDefaultKeyScannerV4: "foo", }, - ExpectedDefault: &platform.ScannerV4Disabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, - }, - "previously persisted default is picked up even if status is empty": { - Annotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, - ExpectedDefault: &platform.ScannerV4Disabled, + ExpectedDefault: &platform.ScannerV4Enabled, ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), + common.FeatureDefaultKeyScannerV4: "foo", }, }, } diff --git a/operator/internal/central/extensions/reconcile_scanner_db_password.go b/operator/internal/central/extensions/reconcile_scanner_db_password.go index 6993ea93c34c6..cf69923258aff 100644 --- a/operator/internal/central/extensions/reconcile_scanner_db_password.go +++ b/operator/internal/central/extensions/reconcile_scanner_db_password.go @@ -15,10 +15,6 @@ var ( _ commonExtensions.ScannerBearingCustomResource = (*platform.Central)(nil) ) -const ( - scannerDBPasswordKey = `password` -) - // ReconcileScannerDBPasswordExtension returns an extension that takes care of creating the scanner-db-password // secret ahead of time. func ReconcileScannerDBPasswordExtension(client ctrlClient.Client, direct ctrlClient.Reader) extensions.ReconcileExtension { diff --git a/operator/internal/central/extensions/reconcile_scanner_db_password_test.go b/operator/internal/central/extensions/reconcile_scanner_db_password_test.go index d39aa2d9e6bb3..582a812fbee11 100644 --- a/operator/internal/central/extensions/reconcile_scanner_db_password_test.go +++ b/operator/internal/central/extensions/reconcile_scanner_db_password_test.go @@ -2,19 +2,12 @@ package extensions import ( "testing" - "time" - "github.com/stackrox/rox/operator/internal/types" "github.com/stackrox/rox/operator/internal/utils/testutils" - "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func verifyScannerDBPassword(t *testing.T, data types.SecretDataMap, _ *time.Time) { - assert.NotEmpty(t, data[scannerDBPasswordKey]) -} - func TestReconcileScannerDBPassword(t *testing.T) { existingScannerDBPassword := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -37,52 +30,21 @@ func TestReconcileScannerDBPassword(t *testing.T) { } cases := map[string]secretReconciliationTestCase{ - "When no db password secret exists and scanner is disabled, no secrets should be created": { - Spec: basicSpecWithScanner(false, false), - ExpectedCreatedSecrets: nil, - }, - "When no db password secret exists and scanner is enabled, a managed secret should be created": { - Spec: basicSpecWithScanner(true, false), - ExpectedCreatedSecrets: map[string]secretVerifyFunc{ - "scanner-db-password": verifyScannerDBPassword, - }, - }, - "When no db password secret exists and scanner is enabled, and the CR is being deleted, no secrets should be created": { + "Scanner v2 is removed, no secrets should be created regardless of spec": { Spec: basicSpecWithScanner(true, false), - Deleted: true, ExpectedCreatedSecrets: nil, }, - "When an unmanaged db password secret exists and scanner is enabled, no secrets should be created or deleted": { - Spec: basicSpecWithScanner(true, false), - Existing: []*v1.Secret{existingScannerDBPassword}, - ExpectedCreatedSecrets: nil, - }, - "When an unmanaged db password secret exists and scanner is disabled, the secret should be left intact": { + "When no db password secret exists and scanner is disabled, no secrets should be created": { Spec: basicSpecWithScanner(false, false), - Existing: []*v1.Secret{existingScannerDBPassword}, ExpectedCreatedSecrets: nil, }, - "When an unmanaged db password secret exists and scanner is enabled, and the CR is being deleted, the secret should be left intact": { + "When an unmanaged db password secret exists, the secret should be left intact": { Spec: basicSpecWithScanner(true, false), - Deleted: true, Existing: []*v1.Secret{existingScannerDBPassword}, ExpectedCreatedSecrets: nil, }, - - // Malformed pre-existing secret - "When a malformed unmanaged secret exists, an error is expected": { - Spec: basicSpecWithScanner(true, false), - Existing: []*v1.Secret{existingMalformedScannerDBPassword}, - ExpectedError: "scanner-db-password secret must contain a non-empty", - }, - "When a malformed unmanaged secret exists, no error is expected": { - Spec: basicSpecWithScanner(false, false), - Existing: []*v1.Secret{existingMalformedScannerDBPassword}, - ExpectedCreatedSecrets: nil, - }, - "When a malformed unmanaged secret exists, and the CR is being deleted, no error is expected": { + "When a malformed unmanaged secret exists, no error is expected because scanner v2 is disabled": { Spec: basicSpecWithScanner(true, false), - Deleted: true, Existing: []*v1.Secret{existingMalformedScannerDBPassword}, ExpectedCreatedSecrets: nil, }, diff --git a/operator/internal/central/extensions/reconcile_tls.go b/operator/internal/central/extensions/reconcile_tls.go index ed383ac1754b9..03292619ec1ab 100644 --- a/operator/internal/central/extensions/reconcile_tls.go +++ b/operator/internal/central/extensions/reconcile_tls.go @@ -92,13 +92,6 @@ func (r *createCentralTLSExtensionRun) Execute(ctx context.Context) error { return errors.Wrap(err, "reconciling central-db-tls secret failed") } - if err := r.reconcileScannerTLSSecret(ctx); err != nil { - return errors.Wrap(err, "reconciling scanner-tls secret failed") - } - if err := r.reconcileScannerDBTLSSecret(ctx); err != nil { - return errors.Wrap(err, "reconciling scanner-db-tls secret failed") - } - if err := r.reconcileScannerV4IndexerTLSSecret(ctx); err != nil { return errors.Wrap(err, "reconciling scanner-v4-indexer-tls secret") } @@ -348,20 +341,6 @@ func (r *createCentralTLSExtensionRun) reconcileCentralDBTLSSecret(ctx context.C return r.DeleteSecret(ctx, "central-db-tls") } -func (r *createCentralTLSExtensionRun) reconcileScannerTLSSecret(ctx context.Context) error { - if r.centralObj.Spec.Scanner.IsEnabled() { - return r.EnsureSecret(ctx, "scanner-tls", r.validateScannerTLSData, r.generateScannerTLSData, commonLabels.TLSSecretLabels()) - } - return r.DeleteSecret(ctx, "scanner-tls") -} - -func (r *createCentralTLSExtensionRun) reconcileScannerDBTLSSecret(ctx context.Context) error { - if r.centralObj.Spec.Scanner.IsEnabled() { - return r.EnsureSecret(ctx, "scanner-db-tls", r.validateScannerDBTLSData, r.generateScannerDBTLSData, commonLabels.TLSSecretLabels()) - } - return r.DeleteSecret(ctx, "scanner-db-tls") -} - func (r *createCentralTLSExtensionRun) reconcileScannerV4IndexerTLSSecret(ctx context.Context) error { if r.centralObj.Spec.ScannerV4.IsEnabled() { return r.EnsureSecret(ctx, "scanner-v4-indexer-tls", r.validateScannerV4IndexerTLSData, r.generateScannerV4IndexerTLSData, commonLabels.TLSSecretLabels()) @@ -445,22 +424,6 @@ func (r *createCentralTLSExtensionRun) generateServiceTLSData(subj mtls.Subject, return nil } -func (r *createCentralTLSExtensionRun) validateScannerTLSData(fileMap types.SecretDataMap, _ bool) error { - return r.validateServiceTLSData(storage.ServiceType_SCANNER_SERVICE, "", fileMap) -} - -func (r *createCentralTLSExtensionRun) generateScannerTLSData(_ types.SecretDataMap) (types.SecretDataMap, error) { - fileMap := make(types.SecretDataMap, numServiceCertDataEntries) - if err := r.generateServiceTLSData(mtls.ScannerSubject, "", fileMap); err != nil { - return nil, err - } - return fileMap, nil -} - -func (r *createCentralTLSExtensionRun) validateScannerDBTLSData(fileMap types.SecretDataMap, _ bool) error { - return r.validateServiceTLSData(storage.ServiceType_SCANNER_DB_SERVICE, "", fileMap) -} - func (r *createCentralTLSExtensionRun) validateCentralDBTLSData(fileMap types.SecretDataMap, _ bool) error { return r.validateServiceTLSData(storage.ServiceType_CENTRAL_DB_SERVICE, "", fileMap) } @@ -477,14 +440,6 @@ func (r *createCentralTLSExtensionRun) validateScannerV4DBTLSData(fileMap types. return r.validateServiceTLSData(storage.ServiceType_SCANNER_V4_DB_SERVICE, "", fileMap) } -func (r *createCentralTLSExtensionRun) generateScannerDBTLSData(_ types.SecretDataMap) (types.SecretDataMap, error) { - fileMap := make(types.SecretDataMap, numServiceCertDataEntries) - if err := r.generateServiceTLSData(mtls.ScannerDBSubject, "", fileMap); err != nil { - return nil, err - } - return fileMap, nil -} - func (r *createCentralTLSExtensionRun) generateCentralDBTLSData(_ types.SecretDataMap) (types.SecretDataMap, error) { fileMap := make(types.SecretDataMap, numServiceCertDataEntries) if err := r.generateServiceTLSData(mtls.CentralDBSubject, "", fileMap); err != nil { diff --git a/operator/internal/central/extensions/reconcile_tls_ca_rotation_test.go b/operator/internal/central/extensions/reconcile_tls_ca_rotation_test.go index 83b1af3056051..64dc6938ac3ef 100644 --- a/operator/internal/central/extensions/reconcile_tls_ca_rotation_test.go +++ b/operator/internal/central/extensions/reconcile_tls_ca_rotation_test.go @@ -88,8 +88,6 @@ func TestCentralCARotation(t *testing.T) { commonSecrets := map[string]secretVerifyFunc{ "central-db-tls": verifyCentralServiceCert(storage.ServiceType_CENTRAL_DB_SERVICE), - "scanner-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_SERVICE), - "scanner-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_DB_SERVICE), "scanner-v4-indexer-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_INDEXER_SERVICE), "scanner-v4-matcher-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_MATCHER_SERVICE), "scanner-v4-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_DB_SERVICE), diff --git a/operator/internal/central/extensions/reconcile_tls_test.go b/operator/internal/central/extensions/reconcile_tls_test.go index 8bec27366c749..76e6594ae75ee 100644 --- a/operator/internal/central/extensions/reconcile_tls_test.go +++ b/operator/internal/central/extensions/reconcile_tls_test.go @@ -257,8 +257,6 @@ func TestCreateCentralTLS(t *testing.T) { ExpectedCreatedSecrets: map[string]secretVerifyFunc{ "central-tls": verifyCentralCert, "central-db-tls": verifyCentralServiceCert(storage.ServiceType_CENTRAL_DB_SERVICE), - "scanner-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_SERVICE), - "scanner-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_DB_SERVICE), "scanner-v4-indexer-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_INDEXER_SERVICE), "scanner-v4-matcher-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_MATCHER_SERVICE), "scanner-v4-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_DB_SERVICE), @@ -295,8 +293,6 @@ func TestCreateCentralTLS(t *testing.T) { Spec: basicSpecWithScanner(true, true), Existing: []*v1.Secret{existingCentral, existingCentralDB}, ExpectedCreatedSecrets: map[string]secretVerifyFunc{ - "scanner-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_SERVICE), - "scanner-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_DB_SERVICE), "scanner-v4-indexer-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_INDEXER_SERVICE), "scanner-v4-matcher-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_MATCHER_SERVICE), "scanner-v4-db-tls": verifyCentralServiceCert(storage.ServiceType_SCANNER_V4_DB_SERVICE), @@ -326,16 +322,6 @@ func TestCreateCentralTLS(t *testing.T) { InterceptedK8sAPICalls: creatingSecretFails("central-db-tls"), ExpectedError: "reconciling central-db-tls secret", }, - "When creating a new scanner-tls secret fails, an error should be returned": { - Spec: basicSpecWithScanner(true, false), - InterceptedK8sAPICalls: creatingSecretFails("scanner-tls"), - ExpectedError: "reconciling scanner-tls secret", - }, - "When creating a new scanner-db-tls secret fails, an error should be returned": { - Spec: basicSpecWithScanner(true, false), - InterceptedK8sAPICalls: creatingSecretFails("scanner-db-tls"), - ExpectedError: "reconciling scanner-db-tls secret", - }, "When creating a new scanner-v4-indexer-tls secret fails, an error should be returned": { Spec: basicSpecWithScanner(false, true), InterceptedK8sAPICalls: creatingSecretFails("scanner-v4-indexer-tls"), @@ -361,16 +347,6 @@ func TestCreateCentralTLS(t *testing.T) { InterceptedK8sAPICalls: gettingSecretFails("central-db-tls"), ExpectedError: "reconciling central-db-tls secret", }, - "When getting an existing scanner-tls secret fails with a non-404 error, an error should be returned": { - Spec: basicSpecWithScanner(true, false), - InterceptedK8sAPICalls: gettingSecretFails("scanner-tls"), - ExpectedError: "reconciling scanner-tls secret", - }, - "When getting an existing scanner-db-tls secret fails with a non-404 error, an error should be returned": { - Spec: basicSpecWithScanner(true, false), - InterceptedK8sAPICalls: gettingSecretFails("scanner-db-tls"), - ExpectedError: "reconciling scanner-db-tls secret", - }, "When getting an existing scanner-v4-indexer-tls secret fails with a non-404 error, an error should be returned": { Spec: basicSpecWithScanner(false, true), InterceptedK8sAPICalls: gettingSecretFails("scanner-v4-indexer-tls"), @@ -408,12 +384,6 @@ func TestCreateCentralTLS(t *testing.T) { ExistingManaged: []*v1.Secret{existingCentralDB}, InterceptedK8sAPICalls: secretIsAlreadyDeleted("central-db-tls"), }, - "When deleting an existing scanner-tls secret fails, an error should be returned": { - Deleted: true, - ExistingManaged: []*v1.Secret{existingScanner}, - InterceptedK8sAPICalls: deletingSecretFails("scanner-tls"), - ExpectedError: "reconciling scanner-tls secret", - }, "When deleting an existing scanner-v4-indexer-tls secret fails, an error should be returned": { Deleted: true, ExistingManaged: []*v1.Secret{existingScannerV4Indexer}, @@ -432,22 +402,6 @@ func TestCreateCentralTLS(t *testing.T) { InterceptedK8sAPICalls: deletingSecretFails("scanner-v4-db-tls"), ExpectedError: "reconciling scanner-v4-db-tls secret", }, - "When deleting an existing scanner-tls secret fails with a 404, an error should not be returned because the secret is likely to be already deleted": { - Deleted: true, - ExistingManaged: []*v1.Secret{existingScanner}, - InterceptedK8sAPICalls: secretIsAlreadyDeleted("scanner-tls"), - }, - "When deleting an existing scanner-db-tls secret fails, an error should be returned": { - Deleted: true, - ExistingManaged: []*v1.Secret{existingScannerDB}, - InterceptedK8sAPICalls: deletingSecretFails("scanner-db-tls"), - ExpectedError: "reconciling scanner-db-tls secret", - }, - "When deleting an existing scanner-db-tls secret fails with a 404, an error should not be returned because the secret is likely to be already deleted": { - Deleted: true, - ExistingManaged: []*v1.Secret{existingScannerDB}, - InterceptedK8sAPICalls: secretIsAlreadyDeleted("scanner-db-tls"), - }, } for name, c := range cases { @@ -1058,8 +1012,6 @@ func Test_createCentralTLSExtensionRun_validateServiceTLSData(t *testing.T) { } subjects := []mtls.Subject{ - mtls.ScannerSubject, - mtls.ScannerDBSubject, mtls.CentralDBSubject, } @@ -1209,10 +1161,6 @@ func Test_createCentralTLSExtensionRun_validateServiceTLSData(t *testing.T) { currentTime: time.Now(), } switch subject { - case mtls.ScannerSubject: - tt.assert(t, r.validateScannerTLSData(tt.fileMap, true)) - case mtls.ScannerDBSubject: - tt.assert(t, r.validateScannerDBTLSData(tt.fileMap, true)) case mtls.CentralDBSubject: tt.assert(t, r.validateCentralDBTLSData(tt.fileMap, true)) } diff --git a/operator/internal/central/values/translation/translation.go b/operator/internal/central/values/translation/translation.go index 637901c6ce3e9..165db9a81de50 100644 --- a/operator/internal/central/values/translation/translation.go +++ b/operator/internal/central/values/translation/translation.go @@ -113,7 +113,6 @@ func (t Translator) translate(ctx context.Context, c platform.Central) (chartuti } v.AddChild("central", central) - v.AddChild("scanner", getCentralScannerComponentValues(c.Spec.Scanner, deploymentDefaults)) v.AddChild("scannerV4", getCentralScannerV4ComponentValues(ctx, c.Spec.ScannerV4, c.GetNamespace(), t.client, deploymentDefaults)) v.AddChild("customize", &customize) @@ -376,23 +375,6 @@ func getDeclarativeConfigurationValues(c *platform.DeclarativeConfiguration) *tr return &declarativeConfig } -func getCentralScannerComponentValues(s *platform.ScannerComponentSpec, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { - if s == nil && !defaults.IsSet() { - return nil - } - if s == nil { - s = &platform.ScannerComponentSpec{} - } - - sv := translation.NewValuesBuilder() - translation.SetScannerComponentDisableValue(&sv, s.ScannerComponent) - translation.SetScannerAnalyzerValues(&sv, s.GetAnalyzer(), defaults) - translation.SetScannerDBValues(&sv, s.DB, defaults) - sv.SetBoolValue("exposeMonitoring", s.Monitoring.IsEnabled()) - - return &sv -} - func getCentralScannerV4ComponentValues(ctx context.Context, s *platform.ScannerV4Spec, namespace string, client ctrlClient.Client, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { if s == nil && !defaults.IsSet() { return nil diff --git a/operator/internal/central/values/translation/translation_deployment_defaults_test.go b/operator/internal/central/values/translation/translation_deployment_defaults_test.go index 7ff3b2a9b0914..5830c05c6a2bc 100644 --- a/operator/internal/central/values/translation/translation_deployment_defaults_test.go +++ b/operator/internal/central/values/translation/translation_deployment_defaults_test.go @@ -17,8 +17,6 @@ func TestDeploymentDefaults(t *testing.T) { componentPaths := []testingUtils.ComponentPath{ {Name: "central", NodeSelectorPath: "central.nodeSelector", TolerationsPath: "central.tolerations"}, {Name: "central-db", NodeSelectorPath: "central.db.nodeSelector", TolerationsPath: "central.db.tolerations"}, - {Name: "scanner", NodeSelectorPath: "scanner.nodeSelector", TolerationsPath: "scanner.tolerations"}, - {Name: "scanner-db", NodeSelectorPath: "scanner.dbNodeSelector", TolerationsPath: "scanner.dbTolerations"}, {Name: "scannerV4-indexer", NodeSelectorPath: "scannerV4.indexer.nodeSelector", TolerationsPath: "scannerV4.indexer.tolerations"}, {Name: "scannerV4-matcher", NodeSelectorPath: "scannerV4.matcher.nodeSelector", TolerationsPath: "scannerV4.matcher.tolerations"}, {Name: "scannerV4-db", NodeSelectorPath: "scannerV4.db.nodeSelector", TolerationsPath: "scannerV4.db.tolerations"}, diff --git a/operator/internal/central/values/translation/translation_test.go b/operator/internal/central/values/translation/translation_test.go index 3a90a8eeb72c5..b6f63d7f40e49 100644 --- a/operator/internal/central/values/translation/translation_test.go +++ b/operator/internal/central/values/translation/translation_test.go @@ -724,64 +724,6 @@ func TestTranslate(t *testing.T) { }, }, }, - "scanner": map[string]interface{}{ - "disable": false, - "replicas": int32(7), - "autoscaling": map[string]interface{}{ - "disable": false, - "minReplicas": int32(6), - "maxReplicas": int32(8), - }, - "nodeSelector": map[string]string{ - "scanner-node-selector-label1": "scanner-node-selector-value1", - "scanner-node-selector-label2": "scanner-node-selector-value2", - }, - "tolerations": []map[string]interface{}{ - { - "key": "node.stackrox.io", - "operator": "Equal", - "value": "false", - }, { - "key": "node-role.kubernetes.io/infra", - "operator": "Exists", - }, - }, - "dbNodeSelector": map[string]string{ - "scanner-db-node-selector-label1": "scanner-db-node-selector-value1", - "scanner-db-node-selector-label2": "scanner-db-node-selector-value2", - }, - "dbTolerations": []map[string]interface{}{ - { - "key": "node.stackrox.io", - "operator": "Equal", - "value": "false", - }, { - "key": "node-role.kubernetes.io/infra", - "operator": "Exists", - }, - }, - "resources": map[string]interface{}{ - "limits": map[string]interface{}{ - "cpu": "50", - "memory": "60", - }, - "requests": map[string]interface{}{ - "cpu": "70", - "memory": "80", - }, - }, - "dbResources": map[string]interface{}{ - "limits": map[string]interface{}{ - "cpu": "90", - "memory": "100", - }, - "requests": map[string]interface{}{ - "cpu": "110", - "memory": "120", - }, - }, - "exposeMonitoring": true, - }, "scannerV4": map[string]interface{}{ "disable": false, "indexer": map[string]interface{}{ @@ -1316,9 +1258,6 @@ func TestTranslate(t *testing.T) { }, }, }, - "scanner": map[string]interface{}{ - "exposeMonitoring": false, - }, }, }, diff --git a/operator/internal/common/defaulting_test_helpers/helpers.go b/operator/internal/common/defaulting_test_helpers/helpers.go index dc3de191a2537..2b07cb49c6201 100644 --- a/operator/internal/common/defaulting_test_helpers/helpers.go +++ b/operator/internal/common/defaulting_test_helpers/helpers.go @@ -53,6 +53,9 @@ func CheckStruct(t *testing.T, s any, schema chartutil.Values) { switch field.Type().Elem().Kind() { case reflect.Struct: if field.IsNil() { + if isObsoleteField(fieldSchema) { + t.Skipf("skipping obsolete field") + } // Operator code provides no defaults for this subtree. // Make sure the schema does not mention defaults either. checkObjectNoDefaults(t, fieldSchema) @@ -74,6 +77,15 @@ func CheckStruct(t *testing.T, s any, schema chartutil.Values) { } } +func isObsoleteField(schema chartutil.Values) bool { + desc, err := schema.PathValue("description") + if err != nil { + return false + } + s, ok := desc.(string) + return ok && strings.HasPrefix(s, "Obsolete field.") +} + func requireNoDefaultProperty(t *testing.T, schema chartutil.Values) { require.NotContainsf(t, schema, "default", "expected schema to not define a default value, but got %v", schema["default"]) } diff --git a/operator/internal/securedcluster/defaults/scanner_v4.go b/operator/internal/securedcluster/defaults/scanner_v4.go index bdef23f31fded..ba19b067cd591 100644 --- a/operator/internal/securedcluster/defaults/scanner_v4.go +++ b/operator/internal/securedcluster/defaults/scanner_v4.go @@ -3,7 +3,6 @@ package defaults import ( "github.com/go-logr/logr" platform "github.com/stackrox/rox/operator/api/v1alpha1" - "github.com/stackrox/rox/operator/internal/common" ) var ( @@ -14,14 +13,11 @@ var ( ) // Only returns AutoSense or Disabled. -// Derive component policy based on status Defaults and spec. -// This will be called from the preExtension to record the current setting. +// Scanner V4 is now always on (the legacy Scanner has been removed), so we default to +// AutoSense unless the user explicitly set Disabled in the CR spec. // // Second return value is `true`, if defaulting has been applied due to lack of explicit setting. -func SecuredClusterScannerV4ComponentPolicy(logger logr.Logger, status *platform.SecuredClusterStatus, annotations map[string]string, spec *platform.LocalScannerV4ComponentSpec) (platform.LocalScannerV4ComponentPolicy, bool) { - defaultForUpgrades := platform.LocalScannerV4Disabled - defaultForNewInstallations := platform.LocalScannerV4AutoSense - +func SecuredClusterScannerV4ComponentPolicy(logger logr.Logger, _ *platform.SecuredClusterStatus, _ map[string]string, spec *platform.LocalScannerV4ComponentSpec) (platform.LocalScannerV4ComponentPolicy, bool) { if spec != nil && spec.ScannerComponent != nil { comp := *spec.ScannerComponent if comp == platform.LocalScannerV4AutoSense || comp == platform.LocalScannerV4Disabled { @@ -30,41 +26,7 @@ func SecuredClusterScannerV4ComponentPolicy(logger logr.Logger, status *platform } } - // User is relying on defaulting. - // This includes the case spec.ScannerComponent == "Default". - - // A default entry exists already, use recorded value. - recordedValue := platform.LocalScannerV4ComponentPolicy(annotations[common.FeatureDefaultKeyScannerV4]) - if recordedValue == platform.LocalScannerV4AutoSense || recordedValue == platform.LocalScannerV4Disabled { - logger.Info("using previously recorded componentPolicy", "componentPolicy", recordedValue) - return recordedValue, true - } - - // No or unexpected default set in the annotations. - - if isNewInstallation(status) { - // Install / Green field. - logger.Info("assuming new installation due to empty status.") - return defaultForNewInstallations, true - } - - // Upgrade. - logger.Info("assuming upgrade") - - if annotations[common.FeatureDefaultKeyScannerV4] == "" { - // No entry in the statusDefaults yet -> preserve defaulting behavior of versions which did not populate - // statusDefaults with a ScannerV4ComponentPolicy. - logger.Info("empty feature-default annotation, using default componentPolicy for upgrades", - "componentPolicy", defaultForUpgrades) - return defaultForUpgrades, true - } - - // This should not happen, since we only store |Disabled, but just in case something unexpected happened - // and we need to make some decisions... - logger.Info("detected unexpected componentPolicy in feature-default annotation, using default componentPolicy for upgrades", - "unexpectedComponentPolicy", recordedValue, - "componentPolicy", defaultForUpgrades) - return defaultForUpgrades, true + return platform.LocalScannerV4AutoSense, true } func securedClusterScannerV4Defaulting(logger logr.Logger, status *platform.SecuredClusterStatus, annotations map[string]string, spec *platform.SecuredClusterSpec, defaults *platform.SecuredClusterSpec) error { @@ -75,12 +37,8 @@ func securedClusterScannerV4Defaulting(logger logr.Logger, status *platform.Secu return nil } - // User is relying on defaults. Set in-memory default and persist corresponding annotation. - - if annotations[common.FeatureDefaultKeyScannerV4] != string(componentPolicy) { - // Update feature default setting. - annotations[common.FeatureDefaultKeyScannerV4] = string(componentPolicy) - } + // User is relying on defaults. Set in-memory default only. + // We no longer persist the annotation to support downgrade scenarios. if defaults.ScannerV4 == nil { defaults.ScannerV4 = &platform.LocalScannerV4ComponentSpec{} diff --git a/operator/internal/securedcluster/defaults/static.go b/operator/internal/securedcluster/defaults/static.go index 4ec1a560d3122..99d4d8d215667 100644 --- a/operator/internal/securedcluster/defaults/static.go +++ b/operator/internal/securedcluster/defaults/static.go @@ -30,17 +30,6 @@ var staticDefaults = platform.SecuredClusterSpec{ ProcessBaselines: &platform.ProcessBaselinesSpec{ AutoLock: platform.ProcessBaselinesAutoLockModeDisabled.Pointer(), }, - Scanner: &platform.LocalScannerComponentSpec{ - ScannerComponent: platform.LocalScannerComponentAutoSense.Pointer(), - Analyzer: &platform.ScannerAnalyzerComponent{ - Scaling: &platform.ScannerComponentScaling{ - AutoScaling: new(platform.ScannerAutoScalingEnabled), - Replicas: new(int32(3)), - MinReplicas: new(int32(2)), - MaxReplicas: new(int32(5)), - }, - }, - }, ScannerV4: &platform.LocalScannerV4ComponentSpec{ // ScannerComponent field is set using a dedicated defaulting flow. Indexer: &platform.ScannerV4Component{ diff --git a/operator/internal/securedcluster/extensions/reconcile_defaulting_test.go b/operator/internal/securedcluster/extensions/reconcile_defaulting_test.go index 70ad6bffa17ce..bdc924c48f82f 100644 --- a/operator/internal/securedcluster/extensions/reconcile_defaulting_test.go +++ b/operator/internal/securedcluster/extensions/reconcile_defaulting_test.go @@ -292,17 +292,11 @@ func TestReconcileScannerV4FeatureDefaultsExtension(t *testing.T) { Spec: platform.SecuredClusterSpec{}, Status: platform.SecuredClusterStatus{}, ExpectedDefault: &platform.LocalScannerV4AutoSense, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.LocalScannerV4AutoSense), - }, }, - "upgrade: disabled by default": { + "upgrade: auto-sense by default": { Spec: platform.SecuredClusterSpec{}, Status: postInstallStatus, - ExpectedDefault: &platform.LocalScannerV4Disabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, + ExpectedDefault: &platform.LocalScannerV4AutoSense, }, "install: auto-sense explicitly": { Spec: platform.SecuredClusterSpec{ @@ -322,47 +316,26 @@ func TestReconcileScannerV4FeatureDefaultsExtension(t *testing.T) { Status: platform.SecuredClusterStatus{}, ExpectedDefault: nil, }, - "upgrade: pick up previously persisted default (AutoSense)": { - Spec: platform.SecuredClusterSpec{}, - Status: postInstallStatus, - Annotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.LocalScannerV4AutoSense), - }, - ExpectedDefault: &platform.LocalScannerV4AutoSense, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.LocalScannerV4AutoSense), - }, - }, - "upgrade: pick up previously persisted default (Disabled)": { + "upgrade: previously persisted annotation is ignored": { Spec: platform.SecuredClusterSpec{}, Status: postInstallStatus, Annotations: map[string]string{ common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), }, - ExpectedDefault: &platform.LocalScannerV4Disabled, + ExpectedDefault: &platform.LocalScannerV4AutoSense, ExpectedAnnotations: map[string]string{ common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), }, }, - "upgrade: ignoring bogus persisted default": { + "upgrade: bogus persisted annotation is ignored": { Spec: platform.SecuredClusterSpec{}, Status: postInstallStatus, Annotations: map[string]string{ common.FeatureDefaultKeyScannerV4: "foo", }, - ExpectedDefault: &platform.LocalScannerV4Disabled, - ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, - }, - "previously persisted default is picked up even if status is empty": { - Spec: platform.SecuredClusterSpec{}, - Annotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), - }, - ExpectedDefault: &platform.LocalScannerV4Disabled, + ExpectedDefault: &platform.LocalScannerV4AutoSense, ExpectedAnnotations: map[string]string{ - common.FeatureDefaultKeyScannerV4: string(platform.ScannerV4ComponentDisabled), + common.FeatureDefaultKeyScannerV4: "foo", }, }, } diff --git a/operator/internal/securedcluster/scanner/auto_sense.go b/operator/internal/securedcluster/scanner/auto_sense.go index 4c9a6b0c3250d..73f44b18ed12b 100644 --- a/operator/internal/securedcluster/scanner/auto_sense.go +++ b/operator/internal/securedcluster/scanner/auto_sense.go @@ -18,14 +18,11 @@ type AutoSenseResult struct { EnableLocalImageScanning bool } -// AutoSenseLocalScannerConfig detects whether the local scanner should be deployed and/or used by sensor. -// Takes into account the setting in provided SecuredCluster CR as well as the presence of a Central instance in the same namespace. -// Modifies the provided SecuredCluster object to set a default Spec.Scanner if missing. -func AutoSenseLocalScannerConfig(ctx context.Context, client ctrlClient.Client, s platform.SecuredCluster) (AutoSenseResult, error) { - SetScannerDefaults(&s.Spec) - scannerComponent := *s.Spec.Scanner.ScannerComponent - - return autoSenseScanner(ctx, client, scannerComponent, s.Namespace) +// AutoSenseLocalScannerConfig returns a disabled legacy Scanner result. +// The legacy Scanner has been removed; this function is kept for callers that +// gate scanner-db-password secret cleanup on the auto-sense result. +func AutoSenseLocalScannerConfig(_ context.Context, _ ctrlClient.Client, _ platform.SecuredCluster) (AutoSenseResult, error) { + return AutoSenseResult{}, nil } // AutoSenseLocalScannerV4Config detects whether the local Scanner V4 should be deployed and/or used by sensor. @@ -44,17 +41,6 @@ func AutoSenseLocalScannerV4Config(ctx context.Context, client ctrlClient.Client } -func autoSenseScanner(ctx context.Context, client ctrlClient.Client, scannerComponent platform.LocalScannerComponentPolicy, namespace string) (AutoSenseResult, error) { - switch scannerComponent { - case platform.LocalScannerComponentAutoSense: - return autoSense(ctx, client, namespace) - case platform.LocalScannerComponentDisabled: - return AutoSenseResult{}, nil - } - - return AutoSenseResult{}, errors.Errorf("invalid scannerComponent setting: %q", scannerComponent) -} - func autoSenseScannerV4(ctx context.Context, client ctrlClient.Client, deploymentPolicy platform.LocalScannerV4ComponentPolicy, namespace string) (AutoSenseResult, error) { switch deploymentPolicy { case platform.LocalScannerV4ComponentAutoSense: diff --git a/operator/internal/securedcluster/scanner/auto_sense_test.go b/operator/internal/securedcluster/scanner/auto_sense_test.go index c541b2f1db1e4..8e91ce05f964e 100644 --- a/operator/internal/securedcluster/scanner/auto_sense_test.go +++ b/operator/internal/securedcluster/scanner/auto_sense_test.go @@ -18,16 +18,16 @@ var securedCluster = platform.SecuredCluster{ }, } -func TestAutoSenseLocalScannerSupportShouldBeEnabled(t *testing.T) { +func TestAutoSenseLocalScannerAlwaysReturnsDisabled(t *testing.T) { client := testutils.NewFakeClientBuilder(t, testutils.ValidClusterVersion).Build() config, err := AutoSenseLocalScannerConfig(context.Background(), client, securedCluster) require.NoError(t, err) - assert.True(t, config.EnableLocalImageScanning) - assert.True(t, config.DeployScannerResources) + assert.False(t, config.EnableLocalImageScanning) + assert.False(t, config.DeployScannerResources) } -func TestAutoSenseIsDisabledWithCentralPresentShouldBeDisabled(t *testing.T) { +func TestAutoSenseLocalScannerIgnoresCentralPresence(t *testing.T) { client := testutils.NewFakeClientBuilder(t, testutils.ValidClusterVersion, &platform.Central{ ObjectMeta: metav1.ObjectMeta{ Namespace: testutils.TestNamespace, @@ -38,11 +38,11 @@ func TestAutoSenseIsDisabledWithCentralPresentShouldBeDisabled(t *testing.T) { config, err := AutoSenseLocalScannerConfig(context.Background(), client, securedCluster) require.NoError(t, err) - assert.False(t, config.DeployScannerResources, "Expected Scanner resource deployment to be disabled if Central is present") - assert.True(t, config.EnableLocalImageScanning, "Expected Local Image Scanning feature to be enabled.") + assert.False(t, config.DeployScannerResources) + assert.False(t, config.EnableLocalImageScanning) } -func TestAutoSenseIsEnabledWithCentralInADifferentNamespace(t *testing.T) { +func TestAutoSenseLocalScannerIgnoresCentralInDifferentNamespace(t *testing.T) { client := testutils.NewFakeClientBuilder(t, testutils.ValidClusterVersion, &platform.Central{ ObjectMeta: metav1.ObjectMeta{ Namespace: "another-namespace", @@ -53,6 +53,6 @@ func TestAutoSenseIsEnabledWithCentralInADifferentNamespace(t *testing.T) { config, err := AutoSenseLocalScannerConfig(context.Background(), client, securedCluster) require.NoError(t, err) - require.True(t, config.DeployScannerResources) - require.True(t, config.EnableLocalImageScanning) + assert.False(t, config.DeployScannerResources) + assert.False(t, config.EnableLocalImageScanning) } diff --git a/operator/internal/securedcluster/scanner/defaults.go b/operator/internal/securedcluster/scanner/defaults.go deleted file mode 100644 index a23cd9b60cffb..0000000000000 --- a/operator/internal/securedcluster/scanner/defaults.go +++ /dev/null @@ -1,13 +0,0 @@ -package scanner - -import platform "github.com/stackrox/rox/operator/api/v1alpha1" - -// SetScannerDefaults makes sure that spec.Scanner and spec.Scanner.ScannerComponent are not nil. -func SetScannerDefaults(spec *platform.SecuredClusterSpec) { - if spec.Scanner == nil { - spec.Scanner = &platform.LocalScannerComponentSpec{} - } - if spec.Scanner.ScannerComponent == nil { - spec.Scanner.ScannerComponent = platform.LocalScannerComponentAutoSense.Pointer() - } -} diff --git a/operator/internal/securedcluster/values/translation/translation.go b/operator/internal/securedcluster/values/translation/translation.go index 5f2515c699b24..75a9cd11b3c53 100644 --- a/operator/internal/securedcluster/values/translation/translation.go +++ b/operator/internal/securedcluster/values/translation/translation.go @@ -100,8 +100,6 @@ func (t Translator) translate(ctx context.Context, sc platform.SecuredCluster) ( if err := platform.MergeSecuredClusterDefaultsIntoSpec(&sc); err != nil { return nil, err } - scanner.SetScannerDefaults(&sc.Spec) - v := translation.NewValuesBuilder() if sc.Spec.ClusterName != nil { @@ -123,17 +121,12 @@ func (t Translator) translate(ctx context.Context, sc platform.SecuredCluster) ( return nil, err } - scannerAutoSenseConfig, err := scanner.AutoSenseLocalScannerConfig(ctx, t.client, sc) - if err != nil { - return nil, err - } - scannerV4AutoSenseConfig, err := scanner.AutoSenseLocalScannerV4Config(ctx, t.client, sc) if err != nil { return nil, err } - v.AddChild("sensor", t.getSensorValues(sc.Spec.Sensor, scannerAutoSenseConfig, scannerV4AutoSenseConfig, deploymentDefaults)) + v.AddChild("sensor", t.getSensorValues(sc.Spec.Sensor, scannerV4AutoSenseConfig, deploymentDefaults)) v.AddChild("admissionControl", t.getAdmissionControlValues(sc.Spec.AdmissionControl, deploymentDefaults)) if sc.Spec.AuditLogs != nil { @@ -144,7 +137,6 @@ func (t Translator) translate(ctx context.Context, sc platform.SecuredCluster) ( v.AddChild("collector", t.getCollectorValues(sc.Spec.PerNode)) } - v.AddChild("scanner", t.getLocalScannerComponentValues(sc, scannerAutoSenseConfig, deploymentDefaults)) v.AddChild("scannerV4", t.getLocalScannerV4ComponentValues(ctx, sc, scannerV4AutoSenseConfig, deploymentDefaults)) customize.AddAllFrom(translation.GetCustomize(sc.Spec.Customize)) @@ -303,10 +295,10 @@ func (t Translator) checkInitBundleSecret(ctx context.Context, sc platform.Secur return nil } -func (t Translator) getSensorValues(sensor *platform.SensorComponentSpec, scannerAutosense scanner.AutoSenseResult, scannerV4Autosense scanner.AutoSenseResult, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { +func (t Translator) getSensorValues(sensor *platform.SensorComponentSpec, scannerV4Autosense scanner.AutoSenseResult, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { sv := translation.NewValuesBuilder() - if scannerAutosense.EnableLocalImageScanning || scannerV4Autosense.EnableLocalImageScanning { + if scannerV4Autosense.EnableLocalImageScanning { sv.SetPathValue("localImageScanning.enabled", strconv.FormatBool(true)) } @@ -500,17 +492,6 @@ func (t Translator) getFAMContainerValues(famContainerSpec *platform.FAMContaine return &cv } -func (t Translator) getLocalScannerComponentValues(securedCluster platform.SecuredCluster, config scanner.AutoSenseResult, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { - sv := translation.NewValuesBuilder() - s := securedCluster.Spec.Scanner - - sv.SetBoolValue("disable", !config.DeployScannerResources) - translation.SetScannerAnalyzerValues(&sv, s.Analyzer, defaults) - translation.SetScannerDBValues(&sv, s.DB, defaults) - - return &sv -} - func (t Translator) getLocalScannerV4ComponentValues(ctx context.Context, securedCluster platform.SecuredCluster, config scanner.AutoSenseResult, defaults translation.SchedulingConstraints) *translation.ValuesBuilder { s := securedCluster.Spec.ScannerV4 if s == nil && !defaults.IsSet() { diff --git a/operator/internal/securedcluster/values/translation/translation_test.go b/operator/internal/securedcluster/values/translation/translation_test.go index 5f8d7524da55a..d28669872e6d7 100644 --- a/operator/internal/securedcluster/values/translation/translation_test.go +++ b/operator/internal/securedcluster/values/translation/translation_test.go @@ -149,9 +149,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, "scannerV4": map[string]interface{}{ "disable": false, "db": map[string]interface{}{ @@ -195,9 +192,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, "scannerV4": map[string]interface{}{ "disable": false, "db": map[string]interface{}{ @@ -242,17 +236,9 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, "scannerV4": map[string]interface{}{ "disable": true, }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -274,14 +260,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -309,9 +287,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": true, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -342,9 +317,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, "scannerV4": map[string]interface{}{ "disable": false, "db": map[string]interface{}{ @@ -379,14 +351,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -734,75 +698,6 @@ func (s *TranslationTestSuite) TestTranslate() { "auditLogs": map[string]interface{}{ "disableCollection": false, }, - "scanner": map[string]interface{}{ - "disable": false, - "replicas": int32(7), - "autoscaling": map[string]interface{}{ - "disable": false, - "minReplicas": int32(6), - "maxReplicas": int32(8), - }, - "nodeSelector": map[string]string{ - "scanner-node-selector-label1": "scanner-node-selector-value1", - "scanner-node-selector-label2": "scanner-node-selector-value2", - }, - "tolerations": []map[string]interface{}{ - { - "key": "node.stackrox.io", - "operator": "Equal", - "value": "false", - }, { - "key": "node-role.kubernetes.io/infra", - "operator": "Exists", - }, - }, - "hostAliases": []map[string]interface{}{ - { - "ip": "127.0.0.1", - "hostnames": []string{"scanner.com"}, - }, - }, - "dbNodeSelector": map[string]string{ - "scanner-db-node-selector-label1": "scanner-db-node-selector-value1", - "scanner-db-node-selector-label2": "scanner-db-node-selector-value2", - }, - "dbTolerations": []map[string]interface{}{ - { - "key": "node.stackrox.io", - "operator": "Equal", - "value": "false", - }, { - "key": "node-role.kubernetes.io/infra", - "operator": "Exists", - }, - }, - "dbHostAliases": []map[string]interface{}{ - { - "ip": "127.0.0.1", - "hostnames": []string{"scanner-db.com"}, - }, - }, - "resources": map[string]interface{}{ - "limits": map[string]interface{}{ - "cpu": "50", - "memory": "60", - }, - "requests": map[string]interface{}{ - "cpu": "70", - "memory": "80", - }, - }, - "dbResources": map[string]interface{}{ - "limits": map[string]interface{}{ - "cpu": "90", - "memory": "100", - }, - "requests": map[string]interface{}{ - "cpu": "110", - "memory": "120", - }, - }, - }, "scannerV4": map[string]interface{}{ "disable": false, "indexer": map[string]interface{}{ @@ -955,14 +850,6 @@ func (s *TranslationTestSuite) TestTranslate() { "forceCollectionMethod": true, "collectionMethod": "CORE_BPF", }, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -987,14 +874,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1022,14 +901,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1057,14 +928,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1090,14 +953,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1128,14 +983,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1171,14 +1018,6 @@ func (s *TranslationTestSuite) TestTranslate() { "clusterName": "test-cluster", "ca": map[string]string{"cert": "ca central content"}, "createSecrets": false, - "scanner": map[string]interface{}{ - "disable": false, - }, - "sensor": map[string]interface{}{ - "localImageScanning": map[string]string{ - "enabled": "true", - }, - }, "monitoring": map[string]interface{}{ "openshift": map[string]interface{}{ "enabled": true, @@ -1557,8 +1396,6 @@ func TestDeploymentDefaults(t *testing.T) { componentPaths := []testingUtils.ComponentPath{ {Name: "sensor", NodeSelectorPath: "sensor.nodeSelector", TolerationsPath: "sensor.tolerations"}, {Name: "admissionControl", NodeSelectorPath: "admissionControl.nodeSelector", TolerationsPath: "admissionControl.tolerations"}, - {Name: "scanner", NodeSelectorPath: "scanner.nodeSelector", TolerationsPath: "scanner.tolerations"}, - {Name: "scanner-db", NodeSelectorPath: "scanner.dbNodeSelector", TolerationsPath: "scanner.dbTolerations"}, {Name: "scannerV4-indexer", NodeSelectorPath: "scannerV4.indexer.nodeSelector", TolerationsPath: "scannerV4.indexer.tolerations"}, {Name: "scannerV4-db", NodeSelectorPath: "scannerV4.db.nodeSelector", TolerationsPath: "scannerV4.db.tolerations"}, } diff --git a/operator/tests/central/central-basic/100-assert.yaml b/operator/tests/central/central-basic/100-assert.yaml index 7fa834ae794e3..28c2acde04e7b 100644 --- a/operator/tests/central/central-basic/100-assert.yaml +++ b/operator/tests/central/central-basic/100-assert.yaml @@ -18,28 +18,6 @@ spec: --- apiVersion: apps/v1 kind: Deployment -metadata: - name: scanner -spec: - template: - spec: - hostAliases: - - ip: "127.0.0.1" - hostnames: ["scanner.com"] ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner-db -spec: - template: - spec: - hostAliases: - - ip: "127.0.0.1" - hostnames: ["scanner-db.com"] ---- -apiVersion: apps/v1 -kind: Deployment metadata: name: scanner-v4-matcher spec: diff --git a/operator/tests/central/central-basic/100-set-host-aliases.yaml b/operator/tests/central/central-basic/100-set-host-aliases.yaml index fa98030f67bfe..ae0c9ec653848 100644 --- a/operator/tests/central/central-basic/100-set-host-aliases.yaml +++ b/operator/tests/central/central-basic/100-set-host-aliases.yaml @@ -18,16 +18,6 @@ spec: hostAliases: - ip: "127.0.0.1" hostnames: ["central-db.com"] - scanner: - scannerComponent: Enabled - analyzer: - hostAliases: - - ip: "127.0.0.1" - hostnames: ["scanner.com"] - db: - hostAliases: - - ip: "127.0.0.1" - hostnames: [ "scanner-db.com" ] scannerV4: # Explicitly enable, scannerV4 is currenlty opt-in scannerComponent: Enabled diff --git a/operator/tests/central/central-basic/101-unset-host-aliases.yaml b/operator/tests/central/central-basic/101-unset-host-aliases.yaml index f046ee3e42440..e7a29cb5bebf1 100644 --- a/operator/tests/central/central-basic/101-unset-host-aliases.yaml +++ b/operator/tests/central/central-basic/101-unset-host-aliases.yaml @@ -14,11 +14,6 @@ spec: hostAliases: [] db: hostAliases: [] - scanner: - analyzer: - hostAliases: [] - db: - hostAliases: [] scannerV4: indexer: hostAliases: [] diff --git a/operator/tests/central/central-misc/010-central-cr.yaml b/operator/tests/central/central-misc/010-central-cr.yaml index 8531547de6f08..ba0832de471f9 100644 --- a/operator/tests/central/central-misc/010-central-cr.yaml +++ b/operator/tests/central/central-misc/010-central-cr.yaml @@ -58,23 +58,3 @@ spec: limits: cpu: "1000m" memory: "2500Mi" - scanner: - analyzer: - scaling: - autoScaling: Disabled - replicas: 1 - resources: - requests: - memory: 500Mi - cpu: 500m - limits: - memory: 2500Mi - cpu: 2000m - db: - resources: - requests: - cpu: 400m - memory: 512Mi - limits: - cpu: 2000m - memory: 4Gi diff --git a/operator/tests/central/central-misc/061-assert.yaml b/operator/tests/central/central-misc/061-assert.yaml index 566109da993d2..551e7b8c05a6a 100644 --- a/operator/tests/central/central-misc/061-assert.yaml +++ b/operator/tests/central/central-misc/061-assert.yaml @@ -41,38 +41,3 @@ spec: app: central policyTypes: - Ingress ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner -spec: - template: - spec: - containers: - - name: scanner - ports: - - containerPort: 8080 - name: https - protocol: TCP - - containerPort: 8443 - name: grpc - protocol: TCP - - containerPort: 9090 - name: monitoring - protocol: TCP ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: scanner-monitoring -spec: - ingress: - - ports: - - port: 9090 - protocol: TCP - podSelector: - matchLabels: - app: scanner - policyTypes: - - Ingress diff --git a/operator/tests/central/central-misc/061-set-expose-monitoring.yaml b/operator/tests/central/central-misc/061-set-expose-monitoring.yaml index 9ee4a4dd56ca3..82ab582f905fa 100644 --- a/operator/tests/central/central-misc/061-set-expose-monitoring.yaml +++ b/operator/tests/central/central-misc/061-set-expose-monitoring.yaml @@ -13,6 +13,3 @@ spec: central: monitoring: exposeEndpoint: Enabled - scanner: - monitoring: - exposeEndpoint: Enabled diff --git a/operator/tests/central/central-misc/063-assert.yaml b/operator/tests/central/central-misc/063-assert.yaml index 72f00717eb874..5e71983b56a93 100644 --- a/operator/tests/central/central-misc/063-assert.yaml +++ b/operator/tests/central/central-misc/063-assert.yaml @@ -13,20 +13,3 @@ spec: - containerPort: 8443 name: api protocol: TCP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner -spec: - template: - spec: - containers: - - name: scanner - ports: - - containerPort: 8080 - name: https - protocol: TCP - - containerPort: 8443 - name: grpc - protocol: TCP diff --git a/operator/tests/central/central-misc/063-unset-expose-monitoring.yaml b/operator/tests/central/central-misc/063-unset-expose-monitoring.yaml index f21f394a15f09..956f981b0ec99 100644 --- a/operator/tests/central/central-misc/063-unset-expose-monitoring.yaml +++ b/operator/tests/central/central-misc/063-unset-expose-monitoring.yaml @@ -13,9 +13,6 @@ spec: central: monitoring: exposeEndpoint: Disabled - scanner: - monitoring: - exposeEndpoint: Disabled monitoring: openshift: enabled: false diff --git a/operator/tests/central/central-misc/080-assert-non-openshift.yaml b/operator/tests/central/central-misc/080-assert-non-openshift.yaml index 042020db7ed22..c14a8f2a726c9 100644 --- a/operator/tests/central/central-misc/080-assert-non-openshift.yaml +++ b/operator/tests/central/central-misc/080-assert-non-openshift.yaml @@ -50,7 +50,7 @@ spec: - name: ROX_SCANNER_V4 value: "true" - name: ROX_LEGACY_SCANNER - value: "true" + value: "false" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/080-assert-openshift.yaml b/operator/tests/central/central-misc/080-assert-openshift.yaml index 6df9d7f8c3dba..cb70e23734560 100644 --- a/operator/tests/central/central-misc/080-assert-openshift.yaml +++ b/operator/tests/central/central-misc/080-assert-openshift.yaml @@ -54,7 +54,7 @@ spec: - name: ROX_SCANNER_V4 value: "true" - name: ROX_LEGACY_SCANNER - value: "true" + value: "false" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/081-assert-non-openshift.yaml b/operator/tests/central/central-misc/081-assert-non-openshift.yaml index fa1f691109048..3e1dd62db2461 100644 --- a/operator/tests/central/central-misc/081-assert-non-openshift.yaml +++ b/operator/tests/central/central-misc/081-assert-non-openshift.yaml @@ -46,7 +46,7 @@ spec: - name: ROX_SCANNER_V4 value: "true" - name: ROX_LEGACY_SCANNER - value: "true" + value: "false" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/081-assert-openshift.yaml b/operator/tests/central/central-misc/081-assert-openshift.yaml index 5aafe7bebc004..462ed4f144083 100644 --- a/operator/tests/central/central-misc/081-assert-openshift.yaml +++ b/operator/tests/central/central-misc/081-assert-openshift.yaml @@ -50,7 +50,7 @@ spec: - name: ROX_SCANNER_V4 value: "true" - name: ROX_LEGACY_SCANNER - value: "true" + value: "false" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/common/central-cr-assert.yaml b/operator/tests/common/central-cr-assert.yaml index d6f72187f13f8..29bc666be20d1 100644 --- a/operator/tests/common/central-cr-assert.yaml +++ b/operator/tests/common/central-cr-assert.yaml @@ -10,12 +10,6 @@ collectors: - type: pod selector: app=config-controller tail: -1 -- type: pod - selector: app=scanner - tail: -1 -- type: pod - selector: app=scanner-db - tail: -1 - type: pod selector: app=scanner-v4-indexer tail: -1 @@ -28,8 +22,6 @@ collectors: - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central-db - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=config-controller -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-db - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-indexer - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-matcher - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-db @@ -82,20 +74,6 @@ status: --- apiVersion: apps/v1 kind: Deployment -metadata: - name: scanner -status: - availableReplicas: 1 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner-db -status: - availableReplicas: 1 ---- -apiVersion: apps/v1 -kind: Deployment metadata: name: scanner-v4-indexer status: diff --git a/operator/tests/common/central-cr-with-scanner-v4-enabled-explicitly.yaml b/operator/tests/common/central-cr-with-scanner-v4-enabled-explicitly.yaml index bb115f6b6788a..7345fb54af22c 100644 --- a/operator/tests/common/central-cr-with-scanner-v4-enabled-explicitly.yaml +++ b/operator/tests/common/central-cr-with-scanner-v4-enabled-explicitly.yaml @@ -61,26 +61,6 @@ spec: limits: cpu: "1000m" memory: "2500Mi" - scanner: - analyzer: - scaling: - autoScaling: Disabled - replicas: 1 - resources: - requests: - memory: 500Mi - cpu: 500m - limits: - memory: 2500Mi - cpu: 2000m - db: - resources: - requests: - cpu: 400m - memory: 512Mi - limits: - cpu: 2000m - memory: 4Gi --- apiVersion: v1 kind: Secret diff --git a/operator/tests/common/central-cr-without-scanner-v4-assert.yaml b/operator/tests/common/central-cr-without-scanner-v4-assert.yaml index 7da05dab2f885..37851c7188821 100644 --- a/operator/tests/common/central-cr-without-scanner-v4-assert.yaml +++ b/operator/tests/common/central-cr-without-scanner-v4-assert.yaml @@ -7,16 +7,8 @@ collectors: - type: pod selector: app=central-db tail: -1 -- type: pod - selector: app=scanner - tail: -1 -- type: pod - selector: app=scanner-db - tail: -1 - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central-db -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-db timeout: 1500 resourceRefs: - apiVersion: platform.stackrox.io/v1alpha1 @@ -47,20 +39,6 @@ metadata: status: availableReplicas: 1 --- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner -status: - availableReplicas: 1 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner-db -status: - availableReplicas: 1 ---- apiVersion: v1 kind: PersistentVolumeClaim metadata: diff --git a/operator/tests/common/central-cr-without-scanner-v4.yaml b/operator/tests/common/central-cr-without-scanner-v4.yaml index b9fbf96557aa1..e95d6817cfed5 100644 --- a/operator/tests/common/central-cr-without-scanner-v4.yaml +++ b/operator/tests/common/central-cr-without-scanner-v4.yaml @@ -61,26 +61,6 @@ spec: limits: cpu: "1000m" memory: "2500Mi" - scanner: - analyzer: - scaling: - autoScaling: Disabled - replicas: 1 - resources: - requests: - memory: 500Mi - cpu: 500m - limits: - memory: 2500Mi - cpu: 2000m - db: - resources: - requests: - cpu: 400m - memory: 512Mi - limits: - cpu: 2000m - memory: 4Gi --- apiVersion: v1 kind: Secret diff --git a/operator/tests/common/central-cr.yaml b/operator/tests/common/central-cr.yaml index 53a1c9fc8978a..8f6db6ec98d8e 100644 --- a/operator/tests/common/central-cr.yaml +++ b/operator/tests/common/central-cr.yaml @@ -60,6 +60,13 @@ spec: limits: cpu: "1000m" memory: "2500Mi" + # NOTE: Unlike the other central-cr*.yaml copies, this file intentionally keeps spec.scanner, + # so the "keep central-misc/010-central-cr.yaml in sync" note above does not apply to it. + # TODO(ROX-36705): spec.scanner configures the removed Scanner V2. It is kept here (and only + # here) because the operator upgrade test reuses this file via a symlink as its pre-upgrade CR, + # where the previous released operator still deploys Scanner V2. It is an obsolete-but-tolerated + # field that the current operator ignores, so it is harmless for the non-upgrade suites that also + # use this file. Remove once the oldest upgrade-from operator version no longer deploys Scanner V2. scanner: analyzer: scaling: diff --git a/operator/tests/common/config-hash-assert.envsubst.yaml b/operator/tests/common/config-hash-assert.envsubst.yaml index 2bbc5377a8210..efec9277e8240 100644 --- a/operator/tests/common/config-hash-assert.envsubst.yaml +++ b/operator/tests/common/config-hash-assert.envsubst.yaml @@ -21,26 +21,6 @@ spec: --- apiVersion: apps/v1 kind: Deployment -metadata: - name: scanner -spec: - template: - metadata: - annotations: - app.stackrox.io/config-hash: ${CONFIG_HASH} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner-db -spec: - template: - metadata: - annotations: - app.stackrox.io/config-hash: ${CONFIG_HASH} ---- -apiVersion: apps/v1 -kind: Deployment metadata: name: scanner-v4-indexer spec: diff --git a/operator/tests/common/pods-debug.yaml b/operator/tests/common/pods-debug.yaml index 4b46ab2bf5ca0..bb60dc117b504 100644 --- a/operator/tests/common/pods-debug.yaml +++ b/operator/tests/common/pods-debug.yaml @@ -10,12 +10,6 @@ collectors: - type: pod selector: app=config-controller tail: -1 -- type: pod - selector: app=scanner - tail: -1 -- type: pod - selector: app=scanner-db - tail: -1 - type: pod selector: app=scanner-v4-indexer tail: -1 @@ -28,8 +22,6 @@ collectors: - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=central-db - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=config-controller -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner -- command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-db - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-indexer - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-matcher - command: retry-kubectl.sh describe pod -n $NAMESPACE -l app=scanner-v4-db diff --git a/operator/tests/common/secured-cluster-cr-assert-no-scanner-v4.yaml b/operator/tests/common/secured-cluster-cr-assert-no-scanner-v4.yaml index e020ddbb0827a..4a25bd5a43139 100644 --- a/operator/tests/common/secured-cluster-cr-assert-no-scanner-v4.yaml +++ b/operator/tests/common/secured-cluster-cr-assert-no-scanner-v4.yaml @@ -52,13 +52,6 @@ metadata: status: availableReplicas: 3 --- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scanner -status: - availableReplicas: 1 ---- apiVersion: v1 kind: Secret metadata: diff --git a/operator/tests/common/secured-cluster-cr-assert.yaml b/operator/tests/common/secured-cluster-cr-assert.yaml index 57d121a0e0e1b..8c9998ac28c85 100644 --- a/operator/tests/common/secured-cluster-cr-assert.yaml +++ b/operator/tests/common/secured-cluster-cr-assert.yaml @@ -61,13 +61,6 @@ status: --- apiVersion: apps/v1 kind: Deployment -metadata: - name: scanner -status: - availableReplicas: 1 ---- -apiVersion: apps/v1 -kind: Deployment metadata: name: scanner-v4-db status: diff --git a/operator/tests/securedcluster/sc-basic/012-assert-image-pull-secret-refs.yaml b/operator/tests/securedcluster/sc-basic/012-assert-image-pull-secret-refs.yaml index f934aa3c284ed..27f0c82281980 100644 --- a/operator/tests/securedcluster/sc-basic/012-assert-image-pull-secret-refs.yaml +++ b/operator/tests/securedcluster/sc-basic/012-assert-image-pull-secret-refs.yaml @@ -24,13 +24,6 @@ imagePullSecrets: --- apiVersion: v1 kind: ServiceAccount -metadata: - name: scanner -imagePullSecrets: -- name: "e2e-test-pull-secret" ---- -apiVersion: v1 -kind: ServiceAccount metadata: name: admission-control imagePullSecrets: diff --git a/operator/tests/securedcluster/sc-basic/013-assert-image-pull-secret-refs.yaml b/operator/tests/securedcluster/sc-basic/013-assert-image-pull-secret-refs.yaml index 72ede52e9ad34..41316a8b579d5 100644 --- a/operator/tests/securedcluster/sc-basic/013-assert-image-pull-secret-refs.yaml +++ b/operator/tests/securedcluster/sc-basic/013-assert-image-pull-secret-refs.yaml @@ -26,14 +26,6 @@ imagePullSecrets: --- apiVersion: v1 kind: ServiceAccount -metadata: - name: scanner -imagePullSecrets: -- name: "e2e-test-pull-secret" -- name: "stackrox" ---- -apiVersion: v1 -kind: ServiceAccount metadata: name: admission-control imagePullSecrets: diff --git a/operator/tests/securedcluster/sc-basic/014-assert-image-pull-secret-refs.yaml b/operator/tests/securedcluster/sc-basic/014-assert-image-pull-secret-refs.yaml index 292ce5bd9df10..af0f3ce0421bf 100644 --- a/operator/tests/securedcluster/sc-basic/014-assert-image-pull-secret-refs.yaml +++ b/operator/tests/securedcluster/sc-basic/014-assert-image-pull-secret-refs.yaml @@ -26,14 +26,6 @@ imagePullSecrets: --- apiVersion: v1 kind: ServiceAccount -metadata: - name: scanner -imagePullSecrets: -- name: "e2e-test-pull-secret" -- name: "stackrox" ---- -apiVersion: v1 -kind: ServiceAccount metadata: name: admission-control imagePullSecrets: diff --git a/operator/tests/securedcluster/sc-basic/015-assert-image-pull-secret-refs.yaml b/operator/tests/securedcluster/sc-basic/015-assert-image-pull-secret-refs.yaml index 1ee283d4d6ce1..c284c155b214e 100644 --- a/operator/tests/securedcluster/sc-basic/015-assert-image-pull-secret-refs.yaml +++ b/operator/tests/securedcluster/sc-basic/015-assert-image-pull-secret-refs.yaml @@ -28,15 +28,6 @@ imagePullSecrets: --- apiVersion: v1 kind: ServiceAccount -metadata: - name: scanner -imagePullSecrets: -- name: "e2e-test-pull-secret" -- name: "stackrox" -- name: "stackrox-scanner" ---- -apiVersion: v1 -kind: ServiceAccount metadata: name: admission-control imagePullSecrets: diff --git a/operator/tests/securedcluster/sc-basic/910-assert-openshift.yaml b/operator/tests/securedcluster/sc-basic/910-assert-openshift.yaml index 5e0d4033c8475..bd55d6a624b09 100644 --- a/operator/tests/securedcluster/sc-basic/910-assert-openshift.yaml +++ b/operator/tests/securedcluster/sc-basic/910-assert-openshift.yaml @@ -5,13 +5,15 @@ testRunSelector: matchLabels: openshift: "true" --- -# The central CR and its operands are gone in previous step. -# However, the SecuredCluster reconciler should notice Central disappearing and -# create a local scanner now that the central scanner is gone. +# The central CR and its operands are gone in the previous step. +# However, the SecuredCluster reconciler should notice Central disappearing and keep a +# local Scanner V4 indexer for delegated scanning now that the central scanner is gone. +# (The indexer is the Scanner V4 component that runs on the secured-cluster side; the +# matcher is Central-only.) apiVersion: apps/v1 kind: Deployment metadata: - name: scanner -# We do not assert anything about the status, since - with Central gone - scanner -# might not be able to become healthy. Even if it does, we are only interested -# in testing that the SecuredCluster reconciler was triggered correctly here. + name: scanner-v4-indexer +# We do not assert anything about the status, since - with Central gone - the indexer +# might not be able to become healthy. Even if it does, we are only interested in testing +# that the SecuredCluster reconciler was triggered correctly here. diff --git a/operator/tests/securedcluster/sc-basic/910-errors-non-openshift.yaml b/operator/tests/securedcluster/sc-basic/910-errors-non-openshift.yaml index bbcb364783614..349460e86c88b 100644 --- a/operator/tests/securedcluster/sc-basic/910-errors-non-openshift.yaml +++ b/operator/tests/securedcluster/sc-basic/910-errors-non-openshift.yaml @@ -5,11 +5,14 @@ testRunSelector: matchLabels: openshift: "false" --- -# The central CR and its operands are gone in previous step. -# On platforms where local scanner is not supported (i.e. on non-OpenShift ones) -# the reconciler should NOT create a local scanner now that the central scanner is gone. -# So we have this errors file to make sure it is gone and not re-created. +# The central CR and its operands are gone in the previous step. +# On platforms where local (delegated) scanning is not supported (i.e. non-OpenShift ones) +# the reconciler should NOT create a local Scanner V4 indexer now that the central scanner +# is gone. So we have this errors file to make sure it is gone and not re-created. +# +# TODO(ROX-36705): Need to re-evaluate this, given that delegated scanning is not restricted +# to OpenShift. apiVersion: apps/v1 kind: Deployment metadata: - name: scanner + name: scanner-v4-indexer diff --git a/operator/tests/securedcluster/sc-basic/910-errors.yaml b/operator/tests/securedcluster/sc-basic/910-errors.yaml new file mode 100644 index 0000000000000..26c908add1fe7 --- /dev/null +++ b/operator/tests/securedcluster/sc-basic/910-errors.yaml @@ -0,0 +1,18 @@ +# The central CR and its operands are gone in the previous step. +# Scanner V2 - including the secured-cluster "local scanner" that the reconciler used to +# create on OpenShift once the central scanner was gone - has been removed, so the +# SecuredCluster reconciler must NOT create a "scanner" deployment on any platform. +# The Scanner V4 matcher only ever runs on the Central side (the indexer is the +# delegated-scanning component on the secured-cluster side, checked by the +# 910-assert-openshift / 910-errors-non-openshift files), so it must not appear on the +# secured-cluster side either. +# This errors file makes sure neither is present or re-created, on any platform. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: scanner +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: scanner-v4-matcher diff --git a/operator/tests/upgrade/upgrade/010-assert-central-cr-with-pre-4-9-admission-controller-config.yaml b/operator/tests/upgrade/upgrade/010-assert-central-cr-with-pre-4-9-admission-controller-config.yaml index 3d8dcd5f73bb9..86775486af31c 100644 --- a/operator/tests/upgrade/upgrade/010-assert-central-cr-with-pre-4-9-admission-controller-config.yaml +++ b/operator/tests/upgrade/upgrade/010-assert-central-cr-with-pre-4-9-admission-controller-config.yaml @@ -42,6 +42,11 @@ metadata: status: availableReplicas: 3 --- +# Scanner V2 (the "scanner" deployment) is still expected here because this step runs against +# the *previous released* operator, before the upgrade in step 020. The current operator no +# longer deploys Scanner V2. +# TODO(ROX-36705): Remove this Scanner V2 deployment assertion once the oldest supported +# upgrade-from operator version (see hack/upgrade-autoselect.sh) no longer deploys Scanner V2. apiVersion: apps/v1 kind: Deployment metadata: diff --git a/operator/tests/upgrade/upgrade/033-errors.yaml b/operator/tests/upgrade/upgrade/033-errors.yaml new file mode 100644 index 0000000000000..abf0dc24af83f --- /dev/null +++ b/operator/tests/upgrade/upgrade/033-errors.yaml @@ -0,0 +1,21 @@ +# After upgrading to an operator that no longer ships Scanner V2, the leftover Scanner V2 +# operands from the pre-upgrade installation must be cleaned up. Verify that the scanner and +# scanner-db Deployments and the scanner-db-password Secret are gone. +# +# Note: scanner-tls / scanner-db-tls are intentionally NOT checked here. Those secrets are +# extension-managed and only removed when the Central CR is deleted (see reconcile_tls.go and +# the delete-central-errors step), not during an upgrade. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: scanner +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: scanner-db +--- +apiVersion: v1 +kind: Secret +metadata: + name: scanner-db-password diff --git a/pkg/certgen/scanner.go b/pkg/certgen/scanner.go index 546777fc3b1be..0d136202c456e 100644 --- a/pkg/certgen/scanner.go +++ b/pkg/certgen/scanner.go @@ -8,6 +8,7 @@ import ( // The issued cert and key are added to the passed in fileMap. // It is extracted out to avoid duplicating the generating code and the file names between central and roxctl, // and is not intended to be more generally reusable. +// TODO(ROX-36705): To remove. func IssueScannerCerts(fileMap map[string][]byte, ca mtls.CA, opts ...mtls.IssueCertOption) error { return IssueOtherServiceCerts(fileMap, ca, []mtls.Subject{mtls.ScannerSubject, mtls.ScannerDBSubject}, opts...) } diff --git a/pkg/env/node_scan.go b/pkg/env/node_scan.go index 582bcfd03c69d..9de0722e86daa 100644 --- a/pkg/env/node_scan.go +++ b/pkg/env/node_scan.go @@ -19,7 +19,7 @@ var ( NodeScanningMaxInitialWait = registerDurationSetting("ROX_NODE_SCANNING_MAX_INITIAL_WAIT", 5*time.Minute) // NodeInventoryContainerEnabled gates whether compliance connects to the node-inventory container. - NodeInventoryContainerEnabled = RegisterBooleanSetting("ROX_CALL_NODE_INVENTORY_ENABLED", false) + NodeInventoryContainerEnabled = RegisterPermanentBooleanSetting("ROX_CALL_NODE_INVENTORY_ENABLED", false) // NodeAnalysisDeadline is a time in which node-inventory component should reply to compliance NodeAnalysisDeadline = registerDurationSetting("ROX_NODE_SCANNING_DEADLINE", 30*time.Second) diff --git a/pkg/features/list.go b/pkg/features/list.go index eabcfa09eba5d..4d4d02fb66097 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -149,8 +149,8 @@ var ( // UISecretsPageMigration enables the secrets list page under the Risk section UISecretsPageMigration = registerFeature("Display secrets list page under Risk section", "ROX_UI_SECRETS_PAGE_MIGRATION") - // LegacyScanner enables the legacy scanner (Scanner V2) integration. - LegacyScanner = registerFeature("Enable legacy scanner (Scanner V2) integration", "ROX_LEGACY_SCANNER", enabled) + // LegacyScanner enables Scanner V2 integration. Off by default; locked in release builds. + LegacyScanner = registerFeature("Enable legacy scanner (Scanner V2) integration", "ROX_LEGACY_SCANNER", unchangeableInProd) // ACMAccessControlDelegation enables AuthProviders with role lookup delegation to ACM access control information. ACMAccessControlDelegation = registerFeature("Enable ACS access control integration with Red Hat Advanced Cluster Management", "ROX_ACM_ACCESS_CONTROL_DELEGATION") diff --git a/pkg/helm/charts/tests/centralservices/flavor/flavor_test.go b/pkg/helm/charts/tests/centralservices/flavor/flavor_test.go index b105a1066645f..20835227cb42e 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/flavor_test.go +++ b/pkg/helm/charts/tests/centralservices/flavor/flavor_test.go @@ -47,7 +47,7 @@ func TestOverriddenTagsAreRenderedInTheChart(t *testing.T) { helmChartTestUtils.RunHelmTestSuite(t, testDir, image.CentralServicesChartPrefix, helmChartTestUtils.RunHelmTestSuiteOpts{ MetaValuesOverridesFunc: func(values *charts.MetaValues) { values.ImageTag = "custom-main" - values.ScannerImageTag = "custom-scanner" + values.ScannerV4ImageTag = "custom-scanner" }, HelmTestOpts: []helmTest.LoaderOpt{helmTest.WithAdditionalTestDirs(path.Join(testDir, "override"))}, }) diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/custom/custom.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/custom/custom.test.yaml index c22fa09bf02b5..360caf1b98a81 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/custom/custom.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/custom/custom.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("example.io/custom-main:1.2.3") - assertScanner("example.io/custom-scanner:3.2.1") - assertScannerDB("example.io/custom-scanner-db:3.2.1") + assertScanner("example.io/custom-scanner-v4:4.2.1") + assertScannerDB("example.io/custom-scanner-v4-db:4.2.1") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml index ebdce7fe2a282..93ca4d84c139d 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("quay.io/rhacs-eng/main:3.0.99.0") - assertScanner("quay.io/rhacs-eng/scanner:99.9.9") - assertScannerDB("quay.io/rhacs-eng/scanner-db:99.9.9") + assertScanner("quay.io/rhacs-eng/scanner-v4:3.0.99.0") + assertScannerDB("quay.io/rhacs-eng/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml index fef3a240e82fa..93ca4d84c139d 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("quay.io/rhacs-eng/main:3.0.99.0") - assertScanner("quay.io/rhacs-eng/scanner:3.0.99.0") - assertScannerDB("quay.io/rhacs-eng/scanner-db:3.0.99.0") + assertScanner("quay.io/rhacs-eng/scanner-v4:3.0.99.0") + assertScannerDB("quay.io/rhacs-eng/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml index 7ec503546df38..441b3125a4a7c 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("quay.io/stackrox-io/main:3.0.99.0") - assertScanner("quay.io/stackrox-io/scanner:99.9.9") - assertScannerDB("quay.io/stackrox-io/scanner-db:99.9.9") + assertScanner("quay.io/stackrox-io/scanner-v4:3.0.99.0") + assertScannerDB("quay.io/stackrox-io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml index 57b4950e7242e..441b3125a4a7c 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("quay.io/stackrox-io/main:3.0.99.0") - assertScanner("quay.io/stackrox-io/scanner:3.0.99.0") - assertScannerDB("quay.io/stackrox-io/scanner-db:3.0.99.0") + assertScanner("quay.io/stackrox-io/scanner-v4:3.0.99.0") + assertScannerDB("quay.io/stackrox-io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/override/override.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/override/override.test.yaml index 8d83e17d83abb..a7e0d3863b808 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/override/override.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/override/override.test.yaml @@ -2,5 +2,5 @@ tests: - name: should use custom tag expect: | assertCentral("test.registry/main:custom-main") - assertScanner("test.registry/scanner:custom-scanner") - assertScannerDB("test.registry/scanner-db:custom-scanner") + assertScanner("test.registry/scanner-v4:custom-scanner") + assertScannerDB("test.registry/scanner-v4-db:custom-scanner") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml index bcbd11a72a0a2..9d944dfb68803 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("registry.redhat.io/advanced-cluster-security/rhacs-main-rhel9:3.0.99.0") - assertScanner("registry.redhat.io/advanced-cluster-security/rhacs-scanner-rhel9:3.0.99.0") - assertScannerDB("registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9:3.0.99.0") + assertScanner("registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-rhel9:3.0.99.0") + assertScannerDB("registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-db-rhel9:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml index 5ad1abca7c817..5ecbd5071070d 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml @@ -2,5 +2,5 @@ tests: - name: validate image defaults expect: | assertCentral("stackrox.io/main:3.0.99.0") - assertScanner("stackrox.io/scanner:3.0.99.0") - assertScannerDB("stackrox.io/scanner-db:3.0.99.0") + assertScanner("stackrox.io/scanner-v4:3.0.99.0") + assertScannerDB("stackrox.io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/suite.yaml b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/suite.yaml index df86ede27ba52..c036f28423f5d 100644 --- a/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/suite.yaml +++ b/pkg/helm/charts/tests/centralservices/flavor/testdata/helmtest/suite.yaml @@ -6,10 +6,13 @@ defs: | container(.deployments.central; "central") | assertThat(.image == name); def assertScanner(name): - container(.deployments.scanner; "scanner") | assertThat(.image == name); + [ + (container(.deployments["scanner-v4-indexer"]; "indexer") | assertThat(.image == name)), + (container(.deployments["scanner-v4-matcher"]; "matcher") | assertThat(.image == name)) + ][]; def assertScannerDB(name): - container(.deployments["scanner-db"]; "db") | assertThat(.image == name); + container(.deployments["scanner-v4-db"]; "db") | assertThat(.image == name); server: visibleSchemas: @@ -40,13 +43,6 @@ values: serviceTLS: cert: "Central DB cert" key: "Central DB key" - scanner: - serviceTLS: - cert: "Scanner cert" - key: "Scanner key" - dbServiceTLS: - cert: "ScannerDB cert" - key: "ScannerDB key" scannerV4: # Setting certificate and key to dummy values, otherwise an attempt is made to generate the certificates, which would fail. indexer: diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest-truncation/chart-label-truncation.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest-truncation/chart-label-truncation.test.yaml index ead8e88ddbb0c..8c3b204814e22 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest-truncation/chart-label-truncation.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest-truncation/chart-label-truncation.test.yaml @@ -23,12 +23,12 @@ tests: chartLabel(.services.central) | length | assertThat(. <= 63) chartLabel(.services.central) | assertThat(test("[a-zA-Z0-9]$")) -- name: Scanner deployment has valid truncated chart label +- name: Scanner V4 indexer deployment has valid truncated chart label expect: | - chartLabel(.deployments.scanner) | length | assertThat(. <= 63) - chartLabel(.deployments.scanner) | assertThat(test("[a-zA-Z0-9]$")) + chartLabel(.deployments["scanner-v4-indexer"]) | length | assertThat(. <= 63) + chartLabel(.deployments["scanner-v4-indexer"]) | assertThat(test("[a-zA-Z0-9]$")) -- name: Scanner service has valid truncated chart label +- name: Scanner V4 indexer service has valid truncated chart label expect: | - chartLabel(.services.scanner) | length | assertThat(. <= 63) - chartLabel(.services.scanner) | assertThat(test("[a-zA-Z0-9]$")) + chartLabel(.services["scanner-v4-indexer"]) | length | assertThat(. <= 63) + chartLabel(.services["scanner-v4-indexer"]) | assertThat(test("[a-zA-Z0-9]$")) diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/central.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central.test.yaml index b3d7a0e5a6d35..a64123372839c 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/central.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central.test.yaml @@ -111,21 +111,21 @@ tests: expect: | envVars(.deployments.central; "central") | assertThat(has("ROX_TENANT_ID") == false) -- name: ROX_LEGACY_SCANNER should be true by default +- name: ROX_LEGACY_SCANNER should be false expect: | - envVars(.deployments.central; "central")["ROX_LEGACY_SCANNER"] | assertThat(. == "true") + envVars(.deployments.central; "central")["ROX_LEGACY_SCANNER"] | assertThat(. == "false") -- name: ROX_LEGACY_SCANNER should be false when scanner is disabled +- name: ROX_LEGACY_SCANNER should be false even with scanner.disable false set: - scanner.disable: true + scanner.disable: false expect: | envVars(.deployments.central; "central")["ROX_LEGACY_SCANNER"] | assertThat(. == "false") -- name: ROX_LEGACY_SCANNER should be true when scanner is explicitly enabled +- name: ROX_LEGACY_SCANNER should be false with scanner.disable true set: - scanner.disable: false + scanner.disable: true expect: | - envVars(.deployments.central; "central")["ROX_LEGACY_SCANNER"] | assertThat(. == "true") + envVars(.deployments.central; "central")["ROX_LEGACY_SCANNER"] | assertThat(. == "false") - name: Central deployment has secure container security context expect: | diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/default-labels.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/default-labels.test.yaml index fd73a69f9a464..aa004c255c590 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/default-labels.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/default-labels.test.yaml @@ -14,9 +14,9 @@ tests: labels(.deployments.central) | ."app.kubernetes.io/name" | assertThat(. == "stackrox") labels(.deployments.central) | ."app.kubernetes.io/component" | assertThat(. == "central") -- name: Scanner +- name: Scanner V4 expect: | - labels(.services.scanner) | ."app.kubernetes.io/name" | assertThat(. == "stackrox") - labels(.services.scanner) | ."app.kubernetes.io/component" | assertThat(. == "scanner") - labels(.deployments.scanner) | ."app.kubernetes.io/name" | assertThat(. == "stackrox") - labels(.deployments.scanner) | ."app.kubernetes.io/component" | assertThat(. == "scanner") + labels(.services["scanner-v4-indexer"]) | ."app.kubernetes.io/name" | assertThat(. == "stackrox") + labels(.services["scanner-v4-indexer"]) | ."app.kubernetes.io/component" | assertThat(. == "scanner-v4") + labels(.deployments["scanner-v4-indexer"]) | ."app.kubernetes.io/name" | assertThat(. == "stackrox") + labels(.deployments["scanner-v4-indexer"]) | ."app.kubernetes.io/component" | assertThat(. == "scanner-v4") diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/host-aliases.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/host-aliases.test.yaml index c97bcd5bd3171..f1f63734f9ebe 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/host-aliases.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/host-aliases.test.yaml @@ -3,15 +3,10 @@ values: allowNone: true tests: - name: "with default settings" - values: - scannerV4: - disable: false expect: | .deployments.central.spec.template.spec.hostAliases | assertThat(. == null) .deployments["config-controller"].spec.template.spec.hostAliases | assertThat(. == null) .deployments["central-db"].spec.template.spec.hostAliases | assertThat(. == null) - .deployments.scanner.spec.template.spec.hostAliases | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.hostAliases | assertThat(. == null) .deployments["scanner-v4-indexer"].spec.template.spec.hostAliases | assertThat(. == null) .deployments["scanner-v4-matcher"].spec.template.spec.hostAliases | assertThat(. == null) .deployments["scanner-v4-db"].spec.template.spec.hostAliases | assertThat(. == null) @@ -53,35 +48,9 @@ tests: .deployments["config-controller"].spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) .deployments["config-controller"].spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") .deployments["config-controller"].spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") -- name: "with scanner host aliases" - values: - scanner: - hostAliases: - - ip: "127.0.0.1" - hostnames: ["foo.local", "bar.local"] - expect: | - .deployments.scanner.spec.template.spec.hostAliases | assertThat(length == 1) - .deployments.scanner.spec.template.spec.hostAliases[0].ip | assertThat(. == "127.0.0.1") - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") -- name: "with scanner db host aliases" - values: - scanner: - dbHostAliases: - - ip: "127.0.0.1" - hostnames: [ "foo.local", "bar.local" ] - expect: | - .deployments.scanner.spec.template.spec.hostAliases | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.hostAliases | assertThat(length == 1) - .deployments["scanner-db"].spec.template.spec.hostAliases[0].ip | assertThat(. == "127.0.0.1") - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") - name: "with scannerV4 indexer host aliases" values: scannerV4: - disable: false indexer: hostAliases: - ip: "127.0.0.1" @@ -95,7 +64,6 @@ tests: - name: "with scannerV4 matcher host aliases" values: scannerV4: - disable: false matcher: hostAliases: - ip: "127.0.0.1" @@ -109,7 +77,6 @@ tests: - name: "with scannerV4 db host aliases" values: scannerV4: - disable: false db: hostAliases: - ip: "127.0.0.1" diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/image-pull-secrets-legacy.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/image-pull-secrets-legacy.test.yaml index f14bd7e8f8b6b..40618de43f354 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/image-pull-secrets-legacy.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/image-pull-secrets-legacy.test.yaml @@ -37,7 +37,6 @@ expect: | # Ensure that default service accounts are always referenced in the correct fashion in the non-error case assumeThat(.error == null) | .serviceaccounts["central"] | saRefersTo(["stackrox", "stackrox-scanner"]) assumeThat(.error == null) | .serviceaccounts["central-db"] | saRefersTo(["stackrox", "stackrox-scanner"]) - assumeThat(.error == null) | .serviceaccounts["scanner"] | saRefersTo(["stackrox", "stackrox-scanner"]) tests: - name: "with no image pull secret creation" @@ -50,7 +49,6 @@ tests: expect: | .serviceaccounts["central"] | saOnlyRefersTo(["stackrox", "stackrox-scanner"]) .serviceaccounts["central-db"] | saOnlyRefersTo(["stackrox", "stackrox-scanner"]) - .serviceaccounts["scanner"] | saOnlyRefersTo(["stackrox", "stackrox-scanner"]) - name: "with default setting of allowNone=false" tests: - name: "should fail with no extra secrets" diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/monitoring.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/monitoring.test.yaml index bb6031e7ef67e..1c3ee7ea26aa5 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/monitoring.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/monitoring.test.yaml @@ -1,8 +1,6 @@ values: imagePullSecrets: allowNone: true - scannerV4: - disable: false tests: - name: Central monitoring should not be exposed by default expect: | @@ -22,24 +20,6 @@ tests: ] .networkpolicys["central-monitoring"] | assertThat(. != null) -- name: Scanner monitoring should not be exposed by default - expect: | - [.deployments.scanner.spec.template.spec.containers[0].ports[] | select(.containerPort == 9090)] | assertThat(length == 0) - [.services.scanner.spec.ports[] | select(.port == 9090)] | assertThat(length == 0) - .networkpolicys["scanner-monitoring"] | assertThat(. == null) - -- name: Scanner monitoring should be exposed when enabled - set: - scanner.exposeMonitoring: true - expect: | - .deployments.scanner.spec.template.spec.containers[0].ports[] | select(.containerPort == 9090) - | assertThat(.name == "monitoring") - .services.scanner.spec.ports[] | select(.port == 9090) | [ - assertThat(.targetPort == "monitoring"), - assertThat(.name == "monitoring") - ] - .networkpolicys["scanner-monitoring"] | assertThat(. != null) - - name: Scanner V4 monitoring should not be exposed by default expect: | [.deployments["scanner-v4-indexer"].spec.template.spec.containers[0].ports[] | select(.containerPort == 9090)] | assertThat(length == 0) diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/node-selector.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/node-selector.test.yaml index 10227e04b10df..0de0f6d0119d5 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/node-selector.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/node-selector.test.yaml @@ -6,8 +6,6 @@ tests: expect: | .deployments.central.spec.template.spec.nodeSelector | assertThat(. == null) .deployments["config-controller"].spec.template.spec.nodeSelector | assertThat(. == null) - .deployments.scanner.spec.template.spec.nodeSelector | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.nodeSelector | assertThat(. == null) - name: "with empty string settings" values: @@ -15,14 +13,9 @@ tests: nodeSelector: "" configController: nodeSelector: "" - scanner: - nodeSelector: "" - dbNodeSelector: "" expect: | .deployments.central.spec.template.spec.nodeSelector | assertThat(. == null) .deployments["config-controller"].spec.template.spec.nodeSelector | assertThat(. == null) - .deployments.scanner.spec.template.spec.nodeSelector | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.nodeSelector | assertThat(. == null) - name: "with empty dict settings" values: @@ -30,22 +23,15 @@ tests: nodeSelector: {} configController: nodeSelector: {} - scanner: - nodeSelector: {} - dbNodeSelector: {} expect: | .deployments.central.spec.template.spec.nodeSelector | assertThat(length == 0) .deployments["config-controller"].spec.template.spec.nodeSelector | assertThat(length == 0) - .deployments.scanner.spec.template.spec.nodeSelector | assertThat(length == 0) - .deployments["scanner-db"].spec.template.spec.nodeSelector | assertThat(length == 0) - name: "with populated settings" expect: | .deployments.central.spec.template.spec.nodeSelector | assertThat(.role == "central") .deployments["config-controller"].spec.template.spec.nodeSelector | assertThat(.role == "config-controller") - .deployments.scanner.spec.template.spec.nodeSelector | assertThat(.role == "scanner") - .deployments["scanner-db"].spec.template.spec.nodeSelector | assertThat(.role == "scanner-db") - [.deployments.central, .deployments["config-controller"], .deployments.scanner, .deployments["scanner-db"]][] | + [.deployments.central, .deployments["config-controller"]][] | .spec.template.spec.nodeSelector | assertThat(.foo == "bar") tests: @@ -59,13 +45,6 @@ tests: nodeSelector: foo: bar role: "config-controller" - scanner: - nodeSelector: - foo: bar - role: scanner - dbNodeSelector: - foo: bar - role: "scanner-db" - name: "via strings" values: central: @@ -76,10 +55,3 @@ tests: nodeSelector: | foo: bar role: "config-controller" - scanner: - nodeSelector: | - foo: bar - role: scanner - dbNodeSelector: | - foo: bar - role: "scanner-db" diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/priority-class-name.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/priority-class-name.test.yaml index ca907d8af3554..32b4217062519 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/priority-class-name.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/priority-class-name.test.yaml @@ -7,8 +7,6 @@ tests: .deployments.central.spec.template.spec.priorityClassName | assertThat(. == null) .deployments["central-db"].spec.template.spec.priorityClassName | assertThat(. == null) .deployments["config-controller"].spec.template.spec.priorityClassName | assertThat(. == null) - .deployments.scanner.spec.template.spec.priorityClassName | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.priorityClassName | assertThat(. == null) - name: "with empty string settings" values: @@ -18,15 +16,10 @@ tests: priorityClassName: "" configController: priorityClassName: "" - scanner: - priorityClassName: "" - dbPriorityClassName: "" expect: | .deployments.central.spec.template.spec.priorityClassName | assertThat(. == null) .deployments["central-db"].spec.template.spec.priorityClassName | assertThat(. == null) .deployments["config-controller"].spec.template.spec.priorityClassName | assertThat(. == null) - .deployments.scanner.spec.template.spec.priorityClassName | assertThat(. == null) - .deployments["scanner-db"].spec.template.spec.priorityClassName | assertThat(. == null) - name: "with populated settings" values: @@ -36,12 +29,7 @@ tests: priorityClassName: "central-db" configController: priorityClassName: "config-controller" - scanner: - priorityClassName: "scanner" - dbPriorityClassName: "scanner-db" expect: | .deployments.central.spec.template.spec.priorityClassName | assertThat(. == "central") .deployments["central-db"].spec.template.spec.priorityClassName | assertThat(. == "central-db") .deployments["config-controller"].spec.template.spec.priorityClassName | assertThat(. == "config-controller") - .deployments.scanner.spec.template.spec.priorityClassName | assertThat(. == "scanner") - .deployments["scanner-db"].spec.template.spec.priorityClassName | assertThat(. == "scanner-db") diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner-v4.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner-v4.test.yaml index e34ebf1c84dd0..d0ef5623f7292 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner-v4.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner-v4.test.yaml @@ -105,13 +105,6 @@ tests: expect: | .notes | assertThat(match("Scanner V4: +enabled")) .notes | assertThat(match("Scanner V4 is enabled and the following Scanner V4 components will be deployed: indexer and matcher")) -# This test can be deleted once the scanner V2 is removed. -- name: "enabling scanner V4 keeps scanner V2 enabled" - set: - scannerV4.disable: false - expect: - .deployments["scanner"] | assertThat(. != null) - - name: "scanner with default settings" set: scannerV4.disable: false @@ -256,17 +249,9 @@ tests: .deployments["scanner-v4-matcher"] | assertThat(. != null) .deployments["scanner-v4-db"] | assertThat(. != null) tests: - - name: "on new installation with scanner V2 defaults" - release: { "isInstall": true, "isUpgrade": false } - - name: "on new installation with scanner V2 enabled explicitly" - release: { "isInstall": true, "isUpgrade": false } - set: - scannerV2.disable: false - - name: "on new installation with scanner V2 disabled explicitly" + - name: "on new installation" release: { "isInstall": true, "isUpgrade": false } - set: - scanner.disable: true - - name: "on upgrade from >= 4.8" + - name: "on upgrade" release: { "isInstall": false, "isUpgrade": true } server: objects: @@ -298,45 +283,14 @@ tests: ca.pem: "" cert.pem: "" key.pem: "" - tests: - - name: "from 4.8" - server: - objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: stackrox-central-helm - namespace: stackrox - data: - installXYVersion: "4.8" - - name: "from 4.9" - server: - objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: stackrox-central-helm - namespace: stackrox - data: - installXYVersion: "4.9" - - name: "not installed" - expect: | - .deployments["scanner-v4-indexer"] | assertThat(. == null) - .deployments["scanner-v4-matcher"] | assertThat(. == null) - .deployments["scanner-v4-db"] | assertThat(. == null) - tests: - - name: "on upgrade from pre-4.8" - release: { "isInstall": false, "isUpgrade": true } -- name: "Scanner V4 can be disabled without impacting Scanner V2" +- name: "Scanner V4 can be disabled" set: scannerV4.disable: true expect: | .deployments["scanner-v4-indexer"] | assertThat(. == null) .deployments["scanner-v4-matcher"] | assertThat(. == null) .deployments["scanner-v4-db"] | assertThat(. == null) - .deployments["scanner"] | assertThat(. != null) - .deployments["scanner-db"] | assertThat(. != null) - name: "indexer should allow ingress from central, sensor, and matcher when deployed as a central-service" set: diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner.test.yaml index 18b29c6312380..855ba6189d06b 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/scanner.test.yaml @@ -4,146 +4,24 @@ values: key: "" imagePullSecrets: allowNone: true - scanner: - serviceTLS: - cert: "" - key: "" - dbServiceTLS: - cert: "" - key: "" monitoring: openshift: enabled: false tests: -- name: "scanner with default settings" - expect: | - .serviceaccounts["scanner"] | assertThat(. != null) - .secrets["scanner-db-password"].stringData.password | assertThat(length != 0) - .configmaps["scanner-config"].data.["config.yaml"] | assertThat(length != 0) - .deployments["scanner-db"].spec.template.spec.affinity.nodeAffinity | .preferredDuringSchedulingIgnoredDuringExecution | assertThat(length == 4) - .deployments["scanner"].spec.template.spec.affinity.nodeAffinity | .preferredDuringSchedulingIgnoredDuringExecution | assertThat(length == 3) - verifyNodeAffinities(.deployments["scanner"]) - verifyNodeAffinities(.deployments["scanner-db"]) - .networkpolicys["scanner"] | assertThat(. != null) - .networkpolicys["scanner-db"] | assertThat(. != null) - .deployments["scanner"] | assertThat(. != null) - .deployments["scanner-db"] | assertThat(. != null) - .services["scanner"] | assertThat(. != null) - .services["scanner-db"] | assertThat(. != null) - .deployments["scanner"].spec.template.spec.volumes[] | select(.name == "additional-ca-volume") | assertThat(.secret.secretName == "additional-ca") - tests: - - name: "in full mode" - expect: | - .secrets["scanner-tls"].stringData | assertThat(.["cert.pem"] | contains("BEGIN CERTIFICATE")) - .secrets["scanner-db-tls"].stringData | assertThat(.["key.pem"] | contains("BEGIN RSA PRIVATE KEY")) - .deployments["scanner"].spec.template.spec.containers[0].image | assertThat(contains("slim") == false) - .deployments["scanner-db"].spec.template.spec.containers[0].image | assertThat(contains("slim") == false) - tests: - - name: "as default" - - name: "with explicit setting" - set: - scanner.mode: full - - name: "with empty setting" - set: - scanner.mode: "" - -- name: "scanner with deprecated PodSecurityPolicies disabled" - values: - system: - enablePodSecurityPolicies: false - expect: | - .podsecuritypolicys["stackrox-scanner"] | assertThat(. == null) - .rolebindings["stackrox-scanner-psp"] | assertThat(. == null) - .clusterroles["stackrox-scanner-psp"] | assertThat(. == null) - -- name: "scanner with deprecated PodSecurityPolicies enabled" - values: - system: - enablePodSecurityPolicies: true - expect: | - .podsecuritypolicys["stackrox-scanner"] | assertThat(. != null) - .rolebindings["stackrox-scanner-psp"] | assertThat(. != null) - .clusterroles["stackrox-scanner-psp"] | assertThat(. != null) - -- name: "scanner with network policies enabled" - set: - network.enableNetworkPolicies: true - expect: | - .networkpolicys["scanner"] | assertThat(. != null) - .networkpolicys["scanner-db"] | assertThat(. != null) - -- name: "scanner with network policies disabled" - set: - network.enableNetworkPolicies: false +- name: "scanner v2 is not deployed" expect: | + .deployments["scanner"] | assertThat(. == null) + .deployments["scanner-db"] | assertThat(. == null) + .services["scanner"] | assertThat(. == null) + .services["scanner-db"] | assertThat(. == null) + .serviceaccounts["scanner"] | assertThat(. == null) .networkpolicys["scanner"] | assertThat(. == null) .networkpolicys["scanner-db"] | assertThat(. == null) - -- name: "configured scanner" - values: - scanner: - replicas: 5 - autoscaling: - minReplicas: 50 - maxReplicas: 100 - affinity: - nodeAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 50 - preference: - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - expect: | - .deployments["scanner"].spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution | assertThat(length == 1) - .horizontalpodautoscalers["scanner"].spec.minReplicas | assertThat(. == 50) - .horizontalpodautoscalers["scanner"].spec.maxReplicas | assertThat(. == 100) - -- name: "disable scanner cert generation" - set: - scanner.serviceTLS.generate: false - scanner.dbServiceTLS.generate: false - expect: | - .secrets["scanner-tls"].stringData | assertThat(.["cert.pem"] | length == 0) - .secrets["scanner-db-tls"].stringData | assertThat(.["key.pem"] | length == 0) - -- name: "scanner with OpenShift 4 and default SCCs" - server: - visibleSchemas: - - openshift-4.12 - availableSchemas: - - openshift-4.12 - values: - env: - openshift: 4 - expect: | - .roles["use-scanner-scc"] | .rules[0].resourceNames[0] | assertThat(contains("nonroot-v2")) - .rolebindings["scanner-use-scc"] | assertThat(. != null) - .deployments["scanner-db"].spec.template.spec.affinity.nodeAffinity | .preferredDuringSchedulingIgnoredDuringExecution | assertThat(length == 4) - .deployments["scanner"].spec.template.spec.affinity.nodeAffinity | .preferredDuringSchedulingIgnoredDuringExecution | assertThat(length == 3) - verifyNodeAffinities(.deployments["scanner"]) - verifyNodeAffinities(.deployments["scanner-db"]) - .networkpolicys["scanner"].spec.ingress | assertThat(length == 2) - .networkpolicys["scanner"].spec.ingress[0] | .ports | assertThat(length == 2) - .networkpolicys["scanner"].spec.ingress[1] | .from[0].podSelector.matchLabels.app | assertThat(. == "sensor") - .networkpolicys["scanner"].spec.ingress[1] | .ports | assertThat(length == 2) - -- name: "Installation fails with slim mode" - values: - scanner: - mode: slim - expectError: true - expect: | - .error | assertThat(contains("set to full mode: scanner.mode=full")) - -- name: "Scanner deployment has secure container security context" - expect: | - .deployments["scanner"].spec.template.spec.containers[] | select(.name == "scanner") | .securityContext.allowPrivilegeEscalation | assertThat(. == false) - -- name: "Scanner DB has secure container security context" - expect: | - .deployments["scanner-db"].spec.template.spec.containers[] | select(.name == "db") | .securityContext.allowPrivilegeEscalation | assertThat(. == false) - -- name: "Scanner DB init container has secure container security context" - expect: | - .deployments["scanner-db"].spec.template.spec.initContainers[] | select(.name == "init-db") | .securityContext.allowPrivilegeEscalation | assertThat(. == false) + tests: + - name: "with default settings" + - name: "when scanner.disable is explicitly set to false" + set: + scanner.disable: false + - name: "when scanner.disable is explicitly set to true" + set: + scanner.disable: true diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/tolerations.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/tolerations.test.yaml index 310e9110378aa..ff0eb91391c63 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/tolerations.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/tolerations.test.yaml @@ -13,9 +13,6 @@ tests: tolerations: "" configController: tolerations: "" - scanner: - tolerations: "" - dbTolerations: "" expect: | .deployments.central.spec.template.spec.tolerations | assertThat(. == null) .deployments["config-controller"].spec.template.spec.tolerations | assertThat(. == null) @@ -26,14 +23,9 @@ tests: tolerations: {} configController: tolerations: {} - scanner: - tolerations: {} - dbTolerations: {} expect: | .deployments.central.spec.template.spec.tolerations | assertThat(length == 0) .deployments["config-controller"].spec.template.spec.tolerations | assertThat(length == 0) - .deployments.scanner.spec.template.spec.tolerations | assertThat(length == 0) - .deployments["scanner-db"].spec.template.spec.tolerations | assertThat(length == 0) - name: "with populated settings via dictionary" values: @@ -49,19 +41,6 @@ tests: operator: "Exists" - key: "node-role.kubernetes.io/control-plane" operator: "Exists" - scanner: - tolerations: - - key: "node-role.kubernetes.io/infra" - operator: "Exists" - - key: "node-role.kubernetes.io/control-plane" - operator: "Exists" - dbTolerations: - - key: "node-role.kubernetes.io/infra" - operator: "Exists" - - key: "node-role.kubernetes.io/control-plane" - operator: "Exists" expect: | .deployments.central.spec.template.spec.tolerations | assertThat(length == 2) .deployments["config-controller"].spec.template.spec.tolerations | assertThat(length == 2) - .deployments.scanner.spec.template.spec.tolerations | assertThat(length == 2) - .deployments["scanner-db"].spec.template.spec.tolerations | assertThat(length == 2) diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/flavor_test.go b/pkg/helm/charts/tests/securedclusterservices/flavor/flavor_test.go index 1159f0b54004f..8d64d1232e0de 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/flavor_test.go +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/flavor_test.go @@ -22,7 +22,7 @@ func TestOverriddenTagsAreRenderedInTheChart(t *testing.T) { values.ClusterName = "test" values.ImageTag = "custom-main" values.CollectorImageTag = "custom-collector" - values.ScannerImageTag = "custom-scanner" + values.ScannerV4ImageTag = "custom-scanner" }, HelmTestOpts: []helmTest.LoaderOpt{helmTest.WithAdditionalTestDirs(path.Join(testDir, "override"))}, }) diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml index 3817dff108f7d..2e6ebea995edc 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-non-release/development_build.test.yaml @@ -17,7 +17,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("quay.io/rhacs-eng/scanner-slim:99.9.9") - assertScannerDBIs("quay.io/rhacs-eng/scanner-db-slim:99.9.9") + assertScannerIs("quay.io/rhacs-eng/scanner-v4:3.0.99.0") + assertScannerDBIs("quay.io/rhacs-eng/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml index 779cd36600b16..ceacb4bad117a 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/development_build-release/development_build.test.yaml @@ -11,7 +11,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("quay.io/rhacs-eng/scanner-slim:3.0.99.0") - assertScannerDBIs("quay.io/rhacs-eng/scanner-db-slim:3.0.99.0") + assertScannerIs("quay.io/rhacs-eng/scanner-v4:3.0.99.0") + assertScannerDBIs("quay.io/rhacs-eng/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml index 07fb6e0f41eaa..4cad35e21e70f 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-non-release/opensource.test.yaml @@ -11,7 +11,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("quay.io/stackrox-io/scanner-slim:99.9.9") - assertScannerDBIs("quay.io/stackrox-io/scanner-db-slim:99.9.9") + assertScannerIs("quay.io/stackrox-io/scanner-v4:3.0.99.0") + assertScannerDBIs("quay.io/stackrox-io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml index 8ddf26e966cfd..9b7e3c7b42b23 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/opensource-release/opensource.test.yaml @@ -11,7 +11,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("quay.io/stackrox-io/scanner-slim:3.0.99.0") - assertScannerDBIs("quay.io/stackrox-io/scanner-db-slim:3.0.99.0") + assertScannerIs("quay.io/stackrox-io/scanner-v4:3.0.99.0") + assertScannerDBIs("quay.io/stackrox-io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/override/override.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/override/override.test.yaml index fef5e5bec62ff..a2cc77ac74ca5 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/override/override.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/override/override.test.yaml @@ -11,8 +11,8 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - .deployments["scanner-db"] | toyaml | print - assertScannerIs("test.registry/scanner-slim:custom-scanner") - assertScannerDBIs("test.registry/scanner-db-slim:custom-scanner") + .deployments["scanner-v4-db"] | toyaml | print + assertScannerIs("test.registry/scanner-v4:custom-scanner") + assertScannerDBIs("test.registry/scanner-v4-db:custom-scanner") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml index 196f45525d3da..4ba06c0bba403 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/rhacs/rhacs.test.yaml @@ -11,7 +11,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("registry.redhat.io/advanced-cluster-security/rhacs-scanner-slim-rhel9:3.0.99.0") - assertScannerDBIs("registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-slim-rhel9:3.0.99.0") + assertScannerIs("registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-rhel9:3.0.99.0") + assertScannerDBIs("registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-db-rhel9:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml index 613f3a5ea1a1b..f7beadb61b806 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/stackrox/stackrox.test.yaml @@ -11,7 +11,7 @@ tests: availableSchemas: - openshift-4.12 set: - scanner.disable: false + scannerV4.disable: false expect: | - assertScannerIs("stackrox.io/scanner-slim:3.0.99.0") - assertScannerDBIs("stackrox.io/scanner-db-slim:3.0.99.0") + assertScannerIs("stackrox.io/scanner-v4:3.0.99.0") + assertScannerDBIs("stackrox.io/scanner-v4-db:3.0.99.0") diff --git a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/suite.yaml b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/suite.yaml index 4eccacda06282..9eddcbc390b34 100644 --- a/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/suite.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/flavor/testdata/helmtest/suite.yaml @@ -14,10 +14,10 @@ defs: | container(.daemonsets.collector; "collector") | assertThat(.image == name); def assertScannerIs(name): - container(.deployments.scanner; "scanner") | assertThat(.image == name); + container(.deployments["scanner-v4-indexer"]; "indexer") | assertThat(.image == name); def assertScannerDBIs(name): - container(.deployments["scanner-db"]; "db") | assertThat(.image == name); + container(.deployments["scanner-v4-db"]; "db") | assertThat(.image == name); server: visibleSchemas: - kubernetes-1.20.2 diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml index f0974bcff94af..f9fb72c1d42f2 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/collector-compliance.test.yaml @@ -31,14 +31,6 @@ tests: envVars(.daemonsets.collector; "compliance")["ROX_NODE_INDEX_HOST_PATH"] | assertThat(. == "/hostindex") [container(.daemonsets.collector; "compliance") | .volumeMounts[] | select(.mountPath == "/hostindex/usr/share")] | length | assertThat(. == 1) [container(.daemonsets.collector; "compliance") | .volumeMounts[] | select(.mountPath == "/hostindex/usr/lib")] | length | assertThat(. == 1) -- name: "customize.envVars can re-enable Scanner V2 node inventory calls" - values: - customize: - envVars: - ROX_CALL_NODE_INVENTORY_ENABLED: "true" - expect: | - envVars(.daemonsets.collector; "compliance")["ROX_CALL_NODE_INVENTORY_ENABLED"] | assertThat(. == "true") - [container(.daemonsets.collector; "compliance") | .env[] | select(.name == "ROX_CALL_NODE_INVENTORY_ENABLED")] | length | assertThat(. == 1) - name: "test securityContext" tests: - name: "by default seLinuxOptions are enabled and use container_runtime_t as container runtime" diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/host-aliases.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/host-aliases.test.yaml index 3ed742cf3690f..22c2d79fe237d 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/host-aliases.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/host-aliases.test.yaml @@ -41,32 +41,6 @@ tests: .deployments["admission-control"].spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) .deployments["admission-control"].spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") .deployments["admission-control"].spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") -- name: "with scanner host aliases" - values: - scanner: - disable: false - hostAliases: - - ip: "127.0.0.1" - hostnames: [ "foo.local", "bar.local" ] - expect: | - .deployments.scanner.spec.template.spec.hostAliases | assertThat(length == 1) - .deployments.scanner.spec.template.spec.hostAliases[0].ip | assertThat(. == "127.0.0.1") - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") - .deployments.scanner.spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") -- name: "with scanner db host aliases" - values: - scanner: - disable: false - dbHostAliases: - - ip: "127.0.0.1" - hostnames: [ "foo.local", "bar.local" ] - expect: | - .deployments["scanner-db"].spec.template.spec.hostAliases | assertThat(length == 1) - .deployments["scanner-db"].spec.template.spec.hostAliases[0].ip | assertThat(. == "127.0.0.1") - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames | assertThat(length == 2) - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames[0] | assertThat(. == "foo.local") - .deployments["scanner-db"].spec.template.spec.hostAliases[0].hostnames[1] | assertThat(. == "bar.local") - name: "with scanner V4 db host aliases" values: scannerV4: diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/image-pull-secrets.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/image-pull-secrets.test.yaml index f7818b8765444..8bf544cb9fb31 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/image-pull-secrets.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/image-pull-secrets.test.yaml @@ -19,29 +19,13 @@ expect: | assumeThat(.error == null) | .serviceaccounts["collector"] | saNotRefersTo(["stackrox", "stackrox-scanner", "collector-stackrox"]) assumeThat(.error == null) | .serviceaccounts["sensor"] | saNotRefersTo(["stackrox", "stackrox-scanner", "collector-stackrox"]) assumeThat(.error == null) | .serviceaccounts["admission-control"] | saNotRefersTo(["stackrox", "stackrox-scanner", "collector-stackrox"]) - assumeThat(.error == null) | .serviceaccounts["scanner"] | saNotRefersTo(["stackrox", "stackrox-scanner", "collector-stackrox"]) # Ensure that sensor and admission-control secrets do not reference the collector secrets in the non-error case. assumeThat(.error == null) | .serviceaccounts["sensor"] | saNotRefersTo(["secured-cluster-services-collector", "collector-stackrox"]) assumeThat(.error == null) | .serviceaccounts["admission-control"] | saNotRefersTo(["secured-cluster-services-collector", "collector-stackrox"]) - assumeThat(.error == null) | .serviceaccounts["scanner"] | saNotRefersTo(["secured-cluster-services-collector", "collector-stackrox"]) tests: -- name: "scanner service account references image pull secrets if scanner V4 is disabled" - values: - scanner: - disable: false - scannerV4: - disable: true - tests: - - name: "if image pull secrets are specified" - values: - imagePullSecrets: - username: euler - password: "0.57721" - expect: | - .serviceaccounts["scanner"] | saRefersTo(["secured-cluster-services-main"]) - name: "with no image pull secret creation" expect: | .secrets?["secured-cluster-services-main"]? | assertThat(. == null) diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-slim.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-slim.test.yaml index 337d30dec6a90..cbf66a721e85d 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-slim.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-slim.test.yaml @@ -8,26 +8,23 @@ server: availableSchemas: - openshift-4.12 tests: -- name: "scanner with default settings is in slim mode" +- name: "scanner v2 is not deployed" set: imagePullSecrets.allowNone: true - scanner.disable: false expect: | - container(.deployments["scanner"]; "scanner").image | contains("slim") - container(.deployments["scanner-db"]; "db").image | contains("slim") - envVars(.deployments.sensor; "sensor")["ROX_LOCAL_IMAGE_SCANNING_ENABLED"] | assertThat(. == "true") - .deployments.["scanner"].spec.template.spec.volumes[] | select(.name == "additional-ca-volume") | assertThat(.secret.secretName == "additional-ca-sensor") + .deployments["scanner"] | assertThat(. == null) + .deployments["scanner-db"] | assertThat(. == null) + .serviceaccounts["scanner"] | assertThat(. == null) tests: - - name: "on openshift 4" - -- name: "scanner is enabled by default" - set: - imagePullSecrets.allowNone: true - expect: | - .deployments["scanner"] | assertThat(. != null) - .deployments["scanner-db"] | assertThat(. != null) - .serviceaccounts["scanner"] | assertThat(. != null) + - name: "with default settings" + - name: "when scanner.disable is explicitly set to false" + set: + scanner.disable: false + - name: "when scanner.disable is explicitly set to true" + set: + scanner.disable: true +# TODO(ROX-36705): Reorganize tests: move V4 related tests to dedicated file. - name: "scanner V4 is enabled by default" set: imagePullSecrets.allowNone: true @@ -36,122 +33,10 @@ tests: .deployments["scanner-v4-db"] | assertThat(. != null) .serviceaccounts["scanner-v4"] | assertThat(. != null) - -- name: "scanner can override image config" - set: - imagePullSecrets.allowNone: true - scanner.disable: false - expect: | - container(.deployments["scanner"]; "scanner") | assertThat(.image == "custom.io/custom-scanner:1.2.3") - container(.deployments["scanner-db"]; "db") | assertThat(.image == "custom.io/custom-scanner-db:1.2.3") - tests: - - name: "with fullref" - values: - image: - scanner: - fullRef: "custom.io/custom-scanner:1.2.3" - scannerDb: - fullRef: "custom.io/custom-scanner-db:1.2.3" - - name: "with separate parts" - values: - image: - scanner: - tag: 1.2.3 - registry: custom.io - name: custom-scanner - scannerDb: - tag: 1.2.3 - registry: custom.io - name: custom-scanner-db - -- name: "scanner accepts connections from sensor when scanner slim is enabled" - set: - imagePullSecrets.allowNone: true - scanner.disable: false - scanner.mode: "slim" - network.enableNetworkPolicies: true - expect: | - .networkpolicys["scanner"].spec.ingress | assertThat(length == 2) - .networkpolicys["scanner"].spec.ingress[1] | .from[0].podSelector.matchLabels.app | assertThat(. == "sensor") - -- name: "Network policies are disabled" - set: - imagePullSecrets.allowNone: true - scanner.disable: false - scanner.mode: "slim" - network.enableNetworkPolicies: false - expect: | - .networkpolicys["scanner"] | assertThat(. == null) - -- name: "scanner slim service account can access image pull secrets" - server: - objects: - - apiVersion: v1 - kind: Secret - metadata: - name: stackrox - namespace: stackrox - - apiVersion: v1 - kind: Secret - metadata: - name: stackrox-scanner - namespace: stackrox - - apiVersion: v1 - kind: Secret - metadata: - name: collector-stackrox - namespace: stackrox - - apiVersion: v1 - kind: Secret - metadata: - name: secured-cluster-services-main - namespace: stackrox - - apiVersion: v1 - kind: Secret - metadata: - name: secured-cluster-services-collector - namespace: stackrox - set: - scanner.disable: false - scanner.mode: "slim" - expect: | - assumeThat(.error == null) | .serviceaccounts["scanner"] | saRefersTo(["secured-cluster-services-main", "stackrox", "stackrox-scanner"]) - assumeThat(.error == null) | .serviceaccounts["scanner"] | saNotRefersTo(["secured-cluster-services-collector", "collector-stackrox"]) - tests: - - name: "when authenticating in image registry with user and password" - set: - imagePullSecrets.username: "imagePullUser" - imagePullSecrets.password: "imagePullPassword" - expect: | - .serviceaccounts["scanner"] | saOnlyRefersTo(["secured-cluster-services-main", "stackrox", "stackrox-scanner"]) - .secrets["secured-cluster-services-main"] | assertThat(. != null) - - name: "when username/password are not provided, no secret is created" - expect: .secrets["secured-cluster-services-main"] | assertThat(. == null) - tests: - - name: "by default (allowNone is false), chart fails" - expectError: true - expect: | - .error | assertThat(contains("You have not specified any image pull secrets, and no existing image pull secrets were automatically inferred.")) - - name: "when allowNone is true" - set: - imagePullSecrets.allowNone: true - expect: .serviceaccounts["scanner"] | saOnlyRefersTo(["secured-cluster-services-main", "stackrox", "stackrox-scanner"]) - - name: "when using existing secrets" - set: - imagePullSecrets.useExisting: "existing-secret1; existing-secret2" - expect: | - .serviceaccounts["scanner"] | saOnlyRefersTo([ - "secured-cluster-services-main", "stackrox", "stackrox-scanner", "existing-secret1", "existing-secret2"]) - - name: "sensor only connects to local scanner when it is enabled" set: imagePullSecrets.allowNone: true tests: - - name: "local scanner enabled" - set: - scanner.disable: false - expect: | - envVars(.deployments.sensor; "sensor")["ROX_LOCAL_IMAGE_SCANNING_ENABLED"] | assertThat(. == "true") - name: "local scanner enabled" set: scannerV4.disable: false @@ -159,41 +44,6 @@ tests: envVars(.deployments.sensor; "sensor")["ROX_LOCAL_IMAGE_SCANNING_ENABLED"] | assertThat(. == "true") - name: "local scanner disabled" set: - scanner.disable: true scannerV4.disable: true expect: | envVars(.deployments.sensor; "sensor")| assertThat(has("ROX_LOCAL_IMAGE_SCANNING_ENABLED") == false) - -- name: "sensor connects to local scanner using the correct GRPC endpoint" - release: - namespace: custom-ns - set: - imagePullSecrets.allowNone: true - allowNonstandardNamespace: true - scanner.disable: false - expect: | - .deployments["sensor"].spec.template.spec.containers[0].env[] | - select(.name == "ROX_SCANNER_GRPC_ENDPOINT") | assertThat(.value == "scanner.custom-ns.svc:8443") - -- name: "Test customize for local scanner" - values: - imagePullSecrets: - allowNone: true - scanner: - disable: false - customize: - envVars: - FOO: bar - expect: | - envVars(.deployments.scanner; "scanner")["FOO"] | assertThat(. == "bar") - -- name: "Installation fails in full mode" - values: - imagePullSecrets: - allowNone: true - scanner: - disable: false - mode: full - expectError: true - expect: | - .error | assertThat(contains("set to slim mode: scanner.mode=slim")) diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-v4.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-v4.test.yaml index 9004d4c8b9b09..d886a13aa4ee0 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-v4.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/scanner-v4.test.yaml @@ -23,7 +23,6 @@ tests: - name: "scanner v4 with default settings with indexer enabled" set: imagePullSecrets.allowNone: true - scanner.disable: false scannerV4.disable: false expect: | container(.deployments["scanner-v4-indexer"]; "indexer") | assertThat (. != null) @@ -41,58 +40,13 @@ tests: .deployments["scanner-v4-indexer"] | assertThat(. != null) .deployments["scanner-v4-db"] | assertThat(. != null) -- name: "scanner V4 should not be installed by default for upgrades from < 4.8" +- name: "scanner V4 should be installed by default for upgrades" release: { "isInstall": false, "isUpgrade": true} set: imagePullSecrets.allowNone: true expect: | - .deployments["scanner-v4-indexer"] | assertThat(. == null) - .deployments["scanner-v4-db"] | assertThat(. == null) - -- name: "scanner V2 should be installed by default for new installations" - release: { "isInstall": true, "isUpgrade": false } - set: - imagePullSecrets.allowNone: true - expect: | - .deployments["scanner"] | assertThat(. != null) - .deployments["scanner-db"] | assertThat(. != null) - -- name: "scanner V2 should be not installed by default for upgrades from < 4.8" - release: { "isInstall": false, "isUpgrade": true} - set: - imagePullSecrets.allowNone: true - expect: | - .deployments["scanner"] | assertThat(. == null) - .deployments["scanner-db"] | assertThat(. == null) - -- name: "scanner V2 should be installed by default for upgrades from >= 4.8" - release: { "isInstall": false, "isUpgrade": true} - set: - imagePullSecrets.allowNone: true - expect: | - .deployments["scanner"] | assertThat(. != null) - .deployments["scanner-db"] | assertThat(. != null) - tests: - - name: "from 4.8" - server: - objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: stackrox-secured-cluster-helm - namespace: stackrox - data: - installXYVersion: "4.8" - - name: "from 4.9" - server: - objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: stackrox-secured-cluster-helm - namespace: stackrox - data: - installXYVersion: "4.9" + .deployments["scanner-v4-indexer"] | assertThat(. != null) + .deployments["scanner-v4-db"] | assertThat(. != null) - name: "scanner V4 enabled should only deploy indexer and db" set: @@ -188,7 +142,6 @@ tests: namespace: stackrox set: scannerV4.disable: false - scanner.disable: true expect: | assumeThat(.error == null) | .serviceaccounts["scanner-v4"] | saRefersTo(["secured-cluster-services-main", "stackrox", "stackrox-scanner-v4"]) assumeThat(.error == null) | .serviceaccounts["scanner-v4"] | saNotRefersTo(["secured-cluster-services-collector", "collector-stackrox"]) @@ -240,28 +193,18 @@ tests: expect: | envVars(.deployments.sensor; "sensor")["ROX_LOCAL_IMAGE_SCANNING_ENABLED"] | assertThat(. == "true") tests: - - name: "if only StackRox Scanner is enabled" + - name: "if Scanner V4 is enabled" set: - scanner.disable: false - scannerV4.disable: true - - name: "if both scanners are enabled" - set: - scanner.disable: false scannerV4.disable: false - name: "if localImageScanningScanner is enabled" set: sensor.localImageScanning.enabled: true - - name: "if StackRox Scanner is disabled and Scanner V4 is enabled" - set: - scanner.disable: true - scannerV4.disable: false - name: "disabled" expect: | envVars(.deployments.sensor; "sensor")["ROX_LOCAL_IMAGE_SCANNING_ENABLED"] | assertThat(. != "true") tests: - - name: "if both scanners are disabled" + - name: "if Scanner V4 is disabled" set: - scanner.disable: true scannerV4.disable: true - name: "sensor connects to local scanner using the correct gRPC endpoint" release: @@ -286,21 +229,10 @@ tests: expect: | envVars(.deployments["scanner-v4-indexer"]; "indexer")["FOO"] | assertThat(. == "bar") -- name: "Disabling scanner V2 does not automatically enable scanner V4 as well" - values: - scanner: - disable: true - imagePullSecrets: - allowNone: true - expect: | - .deployments["scanner-v4-indexer"] | assertThat(. != null) - -- name: "Enabling scanner v2 does not enable scanner v4 if it has been explicitly disabled" +- name: "Scanner V4 can be explicitly disabled" values: scannerV4: disable: true - scanner: - disable: false imagePullSecrets: allowNone: true expect: | @@ -435,8 +367,6 @@ tests: values: imagePullSecrets: allowNone: true - scanner: - disable: false tests: - name: "reference to Scanner V4 if Scanner V4 is not disabled" expect: | diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/sensor-netpol.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/sensor-netpol.test.yaml index 1681e1c5421a3..16ece6cbcefee 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/sensor-netpol.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/sensor-netpol.test.yaml @@ -7,7 +7,6 @@ values: tests: - name: sensor should not allow any scanner traffic if scanner is disabled set: - scanner.disable: true scannerV4.disable: true expect: | .networkpolicys["sensor"].spec.ingress | assertThat(length == 2) @@ -16,29 +15,17 @@ tests: .networkpolicys["sensor"].spec.ingress[0] | .from[1].podSelector.matchLabels.service | assertThat(. == "collector") .networkpolicys["sensor"].spec.ingress[0] | .from[2].podSelector.matchLabels.app | assertThat(. == "admission-control") -- name: sensor should allow scanner traffic when enabled - set: - sensor.localImageScanning.enabled: true - expect: | - .networkpolicys["sensor"].spec.ingress | assertThat(length == 2) - .networkpolicys["sensor"].spec.ingress[0].from | assertThat(length == 5) - .networkpolicys["sensor"].spec.ingress[0] | .from[0].podSelector.matchLabels.app | assertThat(. == "collector") - .networkpolicys["sensor"].spec.ingress[0] | .from[1].podSelector.matchLabels.service | assertThat(. == "collector") - .networkpolicys["sensor"].spec.ingress[0] | .from[2].podSelector.matchLabels.app | assertThat(. == "admission-control") - .networkpolicys["sensor"].spec.ingress[0] | .from[3].podSelector.matchLabels.app | assertThat(. == "scanner") - - name: sensor should allow scanner v4 traffic when enabled set: sensor.localImageScanning.enabled: true scannerV4.disable: false expect: | .networkpolicys["sensor"].spec.ingress | assertThat(length == 2) - .networkpolicys["sensor"].spec.ingress[0].from | assertThat(length == 5) + .networkpolicys["sensor"].spec.ingress[0].from | assertThat(length == 4) .networkpolicys["sensor"].spec.ingress[0] | .from[0].podSelector.matchLabels.app | assertThat(. == "collector") .networkpolicys["sensor"].spec.ingress[0] | .from[1].podSelector.matchLabels.service | assertThat(. == "collector") .networkpolicys["sensor"].spec.ingress[0] | .from[2].podSelector.matchLabels.app | assertThat(. == "admission-control") - .networkpolicys["sensor"].spec.ingress[0] | .from[3].podSelector.matchLabels.app | assertThat(. == "scanner") - .networkpolicys["sensor"].spec.ingress[0] | .from[4].podSelector.matchLabels.app | assertThat(. == "scanner-v4-indexer") + .networkpolicys["sensor"].spec.ingress[0] | .from[3].podSelector.matchLabels.app | assertThat(. == "scanner-v4-indexer") - name: "network policies enabled" set: diff --git a/pkg/images/enricher/enricher_impl.go b/pkg/images/enricher/enricher_impl.go index 925955b8fc6ee..6f747496609c2 100644 --- a/pkg/images/enricher/enricher_impl.go +++ b/pkg/images/enricher/enricher_impl.go @@ -118,7 +118,7 @@ func (e *enricherImpl) EnrichWithVulnerabilities(image *storage.Image, component return EnrichmentResult{ ScanResult: ScanNotDone, - }, errors.New("no image vulnerability retrievers are integrated") + }, noMatchingScannerErr(components.ScannerType()) } func (e *enricherImpl) enrichWithVulnerabilities(scannerName string, dataSource *storage.DataSource, scanner scannerTypes.ImageVulnerabilityGetter, diff --git a/pkg/images/enricher/enricher_v2_impl.go b/pkg/images/enricher/enricher_v2_impl.go index 68bf9ab291614..55f7034b45ccc 100644 --- a/pkg/images/enricher/enricher_v2_impl.go +++ b/pkg/images/enricher/enricher_v2_impl.go @@ -108,7 +108,26 @@ func (e *enricherV2Impl) EnrichWithVulnerabilities(imageV2 *storage.ImageV2, com return EnrichmentResult{ ScanResult: ScanNotDone, - }, errors.New("no image vulnerability retrievers are integrated") + }, noMatchingScannerErr(components.ScannerType()) +} + +func noMatchingScannerErr(requiredType string) error { + return errors.Errorf( + "this scan request was produced by %s, but no scanner of that type is integrated", + scannerTypeDescription(requiredType)) +} + +// scannerTypeDescription returns a human-friendly name for a scanner type +// string +func scannerTypeDescription(scannerType string) string { + switch scannerType { + case scannerTypes.Clairify: + return "the legacy StackRox Scanner" + case scannerTypes.ScannerV4: + return "Scanner V4" + default: + return fmt.Sprintf("a scanner of type %q", scannerType) + } } func (e *enricherV2Impl) enrichWithVulnerabilities(scannerName string, dataSource *storage.DataSource, scanner scannerTypes.ImageVulnerabilityGetter, diff --git a/pkg/images/enricher/enricher_v2_impl_test.go b/pkg/images/enricher/enricher_v2_impl_test.go index aaf767f3203cc..ec24990660c35 100644 --- a/pkg/images/enricher/enricher_v2_impl_test.go +++ b/pkg/images/enricher/enricher_v2_impl_test.go @@ -1552,6 +1552,105 @@ func TestForceRefetchMetadataOnly_Predicates(t *testing.T) { "ForceRefetchMetadataOnly must not force refetch of cached DB values") } +func TestScannerTypeDescription(t *testing.T) { + cases := map[string]struct { + scannerType string + expected string + }{ + "legacy StackRox Scanner": { + scannerType: scannertypes.Clairify, + expected: "the legacy StackRox Scanner", + }, + "Scanner V4": { + scannerType: scannertypes.ScannerV4, + expected: "Scanner V4", + }, + "unknown scanner type": { + scannerType: "some-future-scanner", + expected: `a scanner of type "some-future-scanner"`, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + got := scannerTypeDescription(c.scannerType) + assert.Equal(t, c.expected, got) + // Internal-only names must never leak into user-facing text. + assert.NotContains(t, got, "V2") + }) + } +} + +func TestNoMatchingScannerErr(t *testing.T) { + cases := map[string]struct { + requiredType string + // mustContain lists substrings the error message must include. + mustContain []string + }{ + "legacy scan but no matching scanner integrated": { + requiredType: scannertypes.Clairify, + mustContain: []string{ + "the legacy StackRox Scanner", + "integrated", + }, + }, + "Scanner V4 scan but no matching scanner integrated": { + requiredType: scannertypes.ScannerV4, + mustContain: []string{ + "Scanner V4", + "integrated", + }, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + err := noMatchingScannerErr(c.requiredType) + require.Error(t, err) + + for _, substr := range c.mustContain { + assert.Contains(t, err.Error(), substr) + } + // Internal-only names must never leak into user-facing text. + assert.NotContains(t, err.Error(), "V2") + }) + } +} + +// TestEnrichWithVulnerabilitiesNoMatchingScannerV2 verifies the reachable case +// where scanners are integrated (so scanners.IsEmpty() is false) but none matches +// the scanner type that produced the scan (fakeScanner has type "type", not the +// clairify type the scan components indicate). +func TestEnrichWithVulnerabilitiesNoMatchingScannerV2(t *testing.T) { + ctrl := gomock.NewController(t) + + fsr := newFakeRegistryScanner(opts{}) + scannerSet := scannerMocks.NewMockSet(ctrl) + scannerSet.EXPECT().IsEmpty().Return(false).AnyTimes() + scannerSet.EXPECT().GetAll().Return([]scannertypes.ImageScannerWithDataSource{fsr}).AnyTimes() + + set := mocks.NewMockSet(ctrl) + set.EXPECT().ScannerSet().Return(scannerSet).AnyTimes() + + mockReporter := reporterMocks.NewMockReporter(ctrl) + enricher := newEnricherV2(set, mockReporter) + + img := &storage.ImageV2{ + Id: utils.NewImageV2ID(&storage.ImageName{Registry: "reg", FullName: "reg"}, "sha"), + Digest: "sha", + Name: &storage.ImageName{Registry: "reg", FullName: "reg"}, + } + // Empty indexer version => the components indicate the legacy (clairify) scanner. + components := scannertypes.NewScanComponents("", nil, nil) + + result, err := enricher.EnrichWithVulnerabilities(img, components, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "the legacy StackRox Scanner") + assert.Contains(t, err.Error(), "integrated") + assert.Equal(t, ScanNotDone, result.ScanResult) + assert.False(t, result.ImageUpdated) +} + func newEnricherV2(set *mocks.MockSet, mockReporter *reporterMocks.MockReporter) ImageEnricherV2 { return NewV2(&fakeCVESuppressorV2{}, nil, set, pkgMetrics.CentralSubsystem, newCache(), diff --git a/pkg/renderer/kubernetes.go b/pkg/renderer/kubernetes.go index b5927c398fc02..74badd5b0c6e5 100644 --- a/pkg/renderer/kubernetes.go +++ b/pkg/renderer/kubernetes.go @@ -32,12 +32,14 @@ const ( // renderAll renders all objects (central+scanner). renderAll mode = iota // scannerOnly renders only the scanner. + // TODO(ROX-36705): remove after full scanner v2 cleanup. scannerOnly // centralTLSOnly renders only the central tls secret. centralTLSOnly // centralDBTLSOnly renders only the central-db tls secret centralDBTLSOnly // scannerTLSOnly renders only the scanner tls secret + // TODO(ROX-36705): remove after full scanner v2 cleanup. scannerTLSOnly // centralDBOnly renders only the central db centralDBOnly diff --git a/pkg/renderer/readme.go b/pkg/renderer/readme.go index 8d9236370ef00..061c12dd2df12 100644 --- a/pkg/renderer/readme.go +++ b/pkg/renderer/readme.go @@ -44,11 +44,7 @@ the login page, and log in with username "admin" and the password found in the ` kubectlScannerTemplate = ` - - Deploy Scanner components - - Run scanner/scripts/setup.sh - - Run {{.K8sConfig.Command}} create -R -f scanner - - - Optional: Deploy Scanner V4 components + - Deploy Scanner V4 components - Run scanner-v4/scripts/setup.sh - Run {{.K8sConfig.Command}} create -R -f scanner-v4 ` diff --git a/pkg/renderer/readme_test.go b/pkg/renderer/readme_test.go index b8fd82fc1de0a..b426430fbe935 100644 --- a/pkg/renderer/readme_test.go +++ b/pkg/renderer/readme_test.go @@ -25,7 +25,7 @@ func TestReadme(t *testing.T) { deploymentFormat: v1.DeploymentFormat_KUBECTL, mode: renderAll, - mustContain: []string{"kubectl create -R -f scanner", "kubectl create -R -f scanner-v4", "kubectl create -R -f central"}, + mustContain: []string{"kubectl create -R -f scanner-v4", "kubectl create -R -f central"}, mustNotContain: []string{"kubectl create -R -f monitoring", "helm install"}, mustContainInstructionSuffixAndPrefix: true, }, @@ -34,7 +34,7 @@ func TestReadme(t *testing.T) { deploymentFormat: v1.DeploymentFormat_KUBECTL, mode: scannerOnly, - mustContain: []string{"kubectl create -R -f scanner", "kubectl create -R -f scanner-v4"}, + mustContain: []string{"kubectl create -R -f scanner-v4"}, mustNotContain: []string{"kubectl create -R -f central", "kubectl create -R -f monitoring", "helm install"}, }, { @@ -42,8 +42,8 @@ func TestReadme(t *testing.T) { deploymentFormat: v1.DeploymentFormat_KUBECTL, mode: scannerOnly, - mustContain: []string{"oc create -R -f scanner", "oc create -R -f scanner-v4"}, - mustNotContain: []string{"oc create -R -f central", "oc create -R -f monitoring", "helm install", "oc create -R -f scannerv2"}, + mustContain: []string{"oc create -R -f scanner-v4"}, + mustNotContain: []string{"oc create -R -f central", "oc create -R -f monitoring", "helm install"}, }, { orchCommand: "kubectl", diff --git a/pkg/renderer/render_new.go b/pkg/renderer/render_new.go index 264b3c5831f6a..515c553cf3be7 100644 --- a/pkg/renderer/render_new.go +++ b/pkg/renderer/render_new.go @@ -13,7 +13,6 @@ import ( "github.com/stackrox/rox/pkg/zip" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" - "sigs.k8s.io/yaml" ) var ( @@ -24,7 +23,6 @@ var ( } kubectlScannerScriptMap = FileNameMap{ - "common/setup-scanner.sh": "scanner/scripts/setup.sh", "common/setup-scanner-v4.sh": "scanner-v4/scripts/setup.sh", } @@ -71,8 +69,6 @@ func renderHelmChart(chartFiles []*loader.BufferedFile, mode mode, valuesFiles [ subDir := "central" if strings.HasPrefix(path.Base(fileName), "02-scanner-v4-") { subDir = "scanner-v4" - } else if strings.HasPrefix(path.Base(fileName), "02-scanner-") { - subDir = "scanner" } renderedFiles = append(renderedFiles, zip.NewFile(path.Join(subDir, path.Base(fileName)), []byte(contents), 0)) } @@ -135,23 +131,6 @@ func renderNewBasicFiles(c Config, mode mode, imageFlavor defaults.ImageFlavor) return nil, errors.Errorf("unsupported deployment format %v", c.K8sConfig.DeploymentFormat) } - // For rendering out deploymend bundles we need to activate Scanner V4, otherwise - // no Scanner V4 manifests will be contained in the bundle. Therefore we make sure - // here to flip scannerV4.disable to false. To have precedence we need to prepend - // the Scanner V4 activation switch to the list of Helm values files. - // - // This can be removed once Scanner V4 is activated by default. - activateScannerV4 := map[string]interface{}{ - "scannerV4": map[string]interface{}{ - "disable": false, - }, - } - activateScannerV4Bytes, err := yaml.Marshal(activateScannerV4) - if err != nil { - return nil, errors.Wrap(err, "marshalling Scanner V4 activation switch") - } - valuesFiles = append([]*zip.File{zip.NewFile("activate-scanner-v4.yaml", activateScannerV4Bytes, 0)}, valuesFiles...) - renderedFiles, err := renderHelmChart(chartFiles, mode, valuesFiles) if err != nil { return nil, errors.Wrap(err, "rendering helm chart") @@ -200,12 +179,10 @@ func renderAuxiliaryFiles(c Config, mode mode) ([]*zip.File, error) { if c.K8sConfig.DeploymentFormat == v1.DeploymentFormat_KUBECTL { if mode == centralDBOnly { auxFiles = append(auxFiles, withPrefix("scripts", assets)...) - } else { - auxFiles = append(auxFiles, withPrefix("scanner/scripts", assets)...) - } - if mode == renderAll { + } else if mode == renderAll { auxFiles = append(auxFiles, withPrefix("central/scripts", assets)...) } + // Other modes (scannerOnly): no assets needed — scanner-v4's setup.sh does not use docker-auth.sh. } else { auxFiles = append(auxFiles, withPrefix("scripts", assets)...) } diff --git a/pkg/renderer/render_test.go b/pkg/renderer/render_test.go index a1130a5d5069c..3eb5304e04084 100644 --- a/pkg/renderer/render_test.go +++ b/pkg/renderer/render_test.go @@ -23,10 +23,6 @@ func TestRenderTLSSecretsOnly(t *testing.T) { "ca-key.pem": []byte("CAKey"), "cert.pem": []byte("CentralCert"), "key.pem": []byte("CentralKey"), - "scanner-cert.pem": []byte("ScannerCert"), - "scanner-key.pem": []byte("ScannerKey"), - "scanner-db-cert.pem": []byte("ScannerDBCert"), - "scanner-db-key.pem": []byte("ScannerDBKey"), "scanner-v4-indexer-cert.pem": []byte("ScannerV4IndexerCert"), "scanner-v4-indexer-key.pem": []byte("ScannerV4IndexerKey"), "scanner-v4-matcher-cert.pem": []byte("ScannerV4MatcherCert"), @@ -46,7 +42,6 @@ func TestRenderTLSSecretsOnly(t *testing.T) { numObjsExpected int }{ {"central tls", RenderCentralTLSSecretOnly, 1}, - {"scanner tls", RenderScannerTLSSecretOnly, 2}, {"scanner v4 tls", RenderScannerV4TLSSecretOnly, 3}, } @@ -71,10 +66,6 @@ func TestRenderScannerOnly(t *testing.T) { "ca-key.pem": []byte("CAKey"), "cert.pem": []byte("CentralCert"), "key.pem": []byte("CentralKey"), - "scanner-cert.pem": []byte("ScannerCert"), - "scanner-key.pem": []byte("ScannerKey"), - "scanner-db-cert.pem": []byte("ScannerDBCert"), - "scanner-db-key.pem": []byte("ScannerDBKey"), "scanner-v4-indexer-cert.pem": []byte("ScannerV4IndexerCert"), "scanner-v4-indexer-key.pem": []byte("ScannerV4IndexerKey"), "scanner-v4-matcher-cert.pem": []byte("ScannerV4MatcherCert"), diff --git a/pkg/renderer/templates/private_values.yaml.tpl b/pkg/renderer/templates/private_values.yaml.tpl index 903205a383100..e5a4b4ef471c4 100644 --- a/pkg/renderer/templates/private_values.yaml.tpl +++ b/pkg/renderer/templates/private_values.yaml.tpl @@ -56,33 +56,6 @@ central: {{- index .SecretsBase64Map "default-tls.key" | b64dec | nindent 6 }} {{- end }} -scanner: - {{- if ne (index .SecretsBase64Map "scanner-db-password") "" }} - # Password for securing the communication between Scanner and its DB. - # This password is not relevant to the user (unless for debugging purposes); - # it merely acts as a pre-shared, random secret for securing the connection. - dbPassword: - value: {{ index .SecretsBase64Map "scanner-db-password" | b64dec }} - {{- end }} - - {{- if ne (index .SecretsBase64Map "scanner-cert.pem") "" }} - # Internal "scanner.stackrox.svc" service TLS certificate. - serviceTLS: - cert: | - {{- index .SecretsBase64Map "scanner-cert.pem" | b64dec | nindent 6 }} - key: | - {{- index .SecretsBase64Map "scanner-key.pem" | b64dec | nindent 6 }} - {{- end }} - - {{- if ne (index .SecretsBase64Map "scanner-db-cert.pem") "" }} - # Internal "scanner-db.stackrox" service TLS certificate. - dbServiceTLS: - cert: | - {{- index .SecretsBase64Map "scanner-db-cert.pem" | b64dec | nindent 6 }} - key: | - {{- index .SecretsBase64Map "scanner-db-key.pem" | b64dec | nindent 6 }} - {{- end }} - scannerV4: # Password for securing the communication between Scanner and its DB. # This password is not relevant to the user (unless for debugging purposes); diff --git a/pkg/renderer/templates/private_values_postgres.yaml.tpl b/pkg/renderer/templates/private_values_postgres.yaml.tpl index c9f7980dd6bde..16c1f171293e7 100644 --- a/pkg/renderer/templates/private_values_postgres.yaml.tpl +++ b/pkg/renderer/templates/private_values_postgres.yaml.tpl @@ -71,33 +71,6 @@ central: {{- index .SecretsBase64Map "default-tls.key" | b64dec | nindent 6 }} {{- end }} -scanner: - {{- if ne (index .SecretsBase64Map "scanner-db-password") "" }} - # Password for securing the communication between Scanner and its DB. - # This password is not relevant to the user (unless for debugging purposes); - # it merely acts as a pre-shared, random secret for securing the connection. - dbPassword: - value: {{ index .SecretsBase64Map "scanner-db-password" | b64dec }} - {{- end }} - - {{- if ne (index .SecretsBase64Map "scanner-cert.pem") "" }} - # Internal "scanner.stackrox.svc" service TLS certificate. - serviceTLS: - cert: | - {{- index .SecretsBase64Map "scanner-cert.pem" | b64dec | nindent 6 }} - key: | - {{- index .SecretsBase64Map "scanner-key.pem" | b64dec | nindent 6 }} - {{- end }} - - {{- if ne (index .SecretsBase64Map "scanner-db-cert.pem") "" }} - # Internal "scanner-db.stackrox" service TLS certificate. - dbServiceTLS: - cert: | - {{- index .SecretsBase64Map "scanner-db-cert.pem" | b64dec | nindent 6 }} - key: | - {{- index .SecretsBase64Map "scanner-db-key.pem" | b64dec | nindent 6 }} - {{- end }} - scannerV4: # Password for securing the communication between Scanner and its DB. # This password is not relevant to the user (unless for debugging purposes); diff --git a/pkg/renderer/templates/public_values.yaml.tpl b/pkg/renderer/templates/public_values.yaml.tpl index 3e58be82b414a..e30ddff852591 100644 --- a/pkg/renderer/templates/public_values.yaml.tpl +++ b/pkg/renderer/templates/public_values.yaml.tpl @@ -121,48 +121,12 @@ central: none: true {{- end }} -scanner: - # IMPORTANT: If you do not wish to run StackRox Scanner, change the value on the following - # line to "true". - disable: false - - {{- if .K8sConfig.ImageOverrides.Scanner }} - image: - {{- if .K8sConfig.ImageOverrides.Scanner.Registry }} - registry: {{ .K8sConfig.ImageOverrides.Scanner.Registry }} - {{- end }} - {{- if .K8sConfig.ImageOverrides.Scanner.Name }} - name: {{ .K8sConfig.ImageOverrides.Scanner.Name }} - {{- end }} - {{- if .K8sConfig.ImageOverrides.Scanner.Tag }} - # WARNING: You are using a non-default Scanner image tag. Upgrades via 'helm upgrade' - # will not work as expected. To ensure a smooth upgrade experience, make sure - # StackRox images are mirrored with the same tags as in the quay.io/stackrox-io registry. - tag: {{ .K8sConfig.ImageOverrides.Scanner.Tag }} - {{- end }} - {{- end }} - - {{- if .K8sConfig.ImageOverrides.ScannerDB }} - dbImage: - {{- if .K8sConfig.ImageOverrides.ScannerDB.Registry }} - registry: {{ .K8sConfig.ImageOverrides.ScannerDB.Registry }} - {{- end }} - {{- if .K8sConfig.ImageOverrides.ScannerDB.Name }} - name: {{ .K8sConfig.ImageOverrides.ScannerDB.Name }} - {{- end }} - {{- if .K8sConfig.ImageOverrides.ScannerDB.Tag }} - # WARNING: You are using a non-default Scanner DB image tag. Upgrades via - # 'helm upgrade' will not work as expected. To ensure a smooth upgrade experience, - # make sure StackRox images are mirrored with the same tags as in the quay.io/stackrox-io - # registry. - tag: {{ .K8sConfig.ImageOverrides.ScannerDB.Tag }} - {{- end }} - {{- end }} - scannerV4: - disable: true {{- if .K8sConfig.ImageOverrides.ScannerV4 }} image: + {{- if .K8sConfig.ImageOverrides.ScannerV4.Registry }} + registry: {{ .K8sConfig.ImageOverrides.ScannerV4.Registry }} + {{- end }} {{- if .K8sConfig.ImageOverrides.ScannerV4.Name }} name: {{ .K8sConfig.ImageOverrides.ScannerV4.Name }} {{- end }} @@ -173,6 +137,9 @@ scannerV4: db: {{- if .K8sConfig.ImageOverrides.ScannerV4DB }} image: + {{- if .K8sConfig.ImageOverrides.ScannerV4DB.Registry }} + registry: {{ .K8sConfig.ImageOverrides.ScannerV4DB.Registry }} + {{- end }} {{- if .K8sConfig.ImageOverrides.ScannerV4DB.Name }} name: {{ .K8sConfig.ImageOverrides.ScannerV4DB.Name }} {{- end }} diff --git a/pkg/securedcluster/constants.go b/pkg/securedcluster/constants.go index 45da4687d2c12..d1b2ba6d600b3 100644 --- a/pkg/securedcluster/constants.go +++ b/pkg/securedcluster/constants.go @@ -15,13 +15,13 @@ const ( ) // AllTLSSecretNames contains all TLS secret names used by SecuredCluster services. -// Keep in sync with the constants above. +// Scanner V2 (ScannerTLSSecretName / ScannerDbTLSSecretName) is intentionally excluded: it has +// been removed and sensor no longer creates those secrets, so the operator must not require them +// when verifying TLS-secret CA consistency (otherwise the SecuredCluster stays Irreconcilable). var AllTLSSecretNames = []string{ SensorTLSSecretName, CollectorTLSSecretName, AdmissionControlTLSSecretName, - ScannerTLSSecretName, - ScannerDbTLSSecretName, ScannerV4IndexerTLSSecretName, ScannerV4DbTLSSecretName, } diff --git a/qa-tests-backend/scripts/run-compatibility.sh b/qa-tests-backend/scripts/run-compatibility.sh index 625d37dab1466..e05476edd38c5 100755 --- a/qa-tests-backend/scripts/run-compatibility.sh +++ b/qa-tests-backend/scripts/run-compatibility.sh @@ -63,8 +63,8 @@ _compatibility_test() { deploy_stackrox_with_custom_central_and_sensor_versions "${central_version}" "${sensor_version}" kubectl -n stackrox get deploy,ds -o wide - # Wait for scanner v2 deployment to be ready so that it can register its - # Clairify integration with Central before CertExpiryTest runs. + # Old versions under test may still deploy Scanner V2. Wait only when that + # workload exists; HEAD installs Scanner V4 instead. # TODO(https://github.com/stackrox/roxie/issues/269): replace with --early-readiness=false roxie flag, # once we no longer need to deploy 4.9 if retrying_kubectl /dev/null 2>&1; then diff --git a/qa-tests-backend/src/main/groovy/services/CredentialExpiryService.groovy b/qa-tests-backend/src/main/groovy/services/CredentialExpiryService.groovy index fa220d6fb890f..961cbbc5c0d38 100644 --- a/qa-tests-backend/src/main/groovy/services/CredentialExpiryService.groovy +++ b/qa-tests-backend/src/main/groovy/services/CredentialExpiryService.groovy @@ -17,10 +17,4 @@ class CredentialExpiryService extends BaseService { GetCertExpiry.Request.newBuilder().setComponent(GetCertExpiry.Component.CENTRAL).build() ).getExpiry() } - - static Timestamp getScannerCertExpiry() { - return getCredentialExpiryServiceClient().getCertExpiry( - GetCertExpiry.Request.newBuilder().setComponent(GetCertExpiry.Component.SCANNER).build() - ).getExpiry() - } } diff --git a/qa-tests-backend/src/main/groovy/services/ImageIntegrationService.groovy b/qa-tests-backend/src/main/groovy/services/ImageIntegrationService.groovy index aabba2f08ae02..bb3596aed6049 100644 --- a/qa-tests-backend/src/main/groovy/services/ImageIntegrationService.groovy +++ b/qa-tests-backend/src/main/groovy/services/ImageIntegrationService.groovy @@ -158,6 +158,10 @@ class ImageIntegrationService extends BaseService { } static String addStackroxScannerIntegration() { + if (FeatureFlagService.isFeatureFlagEnabled("ROX_SCANNER_V4")) { + log.debug("Not adding ${Constants.AUTO_REGISTERED_STACKROX_SCANNER_INTEGRATION}; Scanner V4 is enabled") + return "" + } ImageIntegrationOuterClass.ImageIntegration existing = getImageIntegrationByName(Constants.AUTO_REGISTERED_STACKROX_SCANNER_INTEGRATION) if (existing) { diff --git a/qa-tests-backend/src/test/groovy/CertExpiryTest.groovy b/qa-tests-backend/src/test/groovy/CertExpiryTest.groovy index d17a33314a52d..cffd3030d5b94 100644 --- a/qa-tests-backend/src/test/groovy/CertExpiryTest.groovy +++ b/qa-tests-backend/src/test/groovy/CertExpiryTest.groovy @@ -1,12 +1,9 @@ -import static util.Helpers.shellCmdExitValue - import services.CredentialExpiryService import util.Cert import spock.lang.Tag import spock.lang.IgnoreIf import util.Env -import util.Helpers @Tag("BAT") @Tag("COMPATIBILITY") @@ -28,22 +25,4 @@ class CertExpiryTest extends BaseSpecification { assert Cert.loadBase64EncodedCert(centralTLSSecret.data["cert.pem"]).notAfter == centralCertExpiryFromCentral } - def "Test Scanner cert expiry"() { - when: - "Fetch the current scanner-tls secret, and the scanner cert expiry as returned by Central" - assert shellCmdExitValue("./scripts/ci/is-scanner-v2-available.sh stackrox") == 0 - def scannerTLSSecret = orchestrator.getSecret("scanner-tls", "stackrox") - assert scannerTLSSecret - // Retry since scanner integration registration happens asynchronously. - def scannerCertExpiryFromCentral = Helpers.evaluateWithRetry(5, 5) { - return new Date(CredentialExpiryService.getScannerCertExpiry().getSeconds() * 1000) - } - assert scannerCertExpiryFromCentral - - then: - "Make sure they match" - assert Cert.loadBase64EncodedCert(scannerTLSSecret.data["cert.pem"]).notAfter == scannerCertExpiryFromCentral - } - } - diff --git a/qa-tests-backend/src/test/groovy/CertRotationTest.groovy b/qa-tests-backend/src/test/groovy/CertRotationTest.groovy index 536e1b99801da..af58ddeec1de7 100644 --- a/qa-tests-backend/src/test/groovy/CertRotationTest.groovy +++ b/qa-tests-backend/src/test/groovy/CertRotationTest.groovy @@ -109,23 +109,6 @@ class CertRotationTest extends BaseSpecification { "cert.pem", "key.pem", "CENTRAL_SERVICE", start) } - def "Test Scanner cert rotation"() { - when: - "Fetch the current scanner-tls and scanner-db-tls secrets, and regenerate new certs" - def scannerTLSSecret = orchestrator.getSecret("scanner-tls", "stackrox") - assert scannerTLSSecret - def scannerDBTLSSecret = orchestrator.getSecret("scanner-db-tls", "stackrox") - assert scannerDBTLSSecret - def start = System.currentTimeMillis() - def regeneratedSecrets = generateCerts("api/extensions/certgen/scanner", "scanner-tls.yaml") - - then: - testMatchingSecretFoundWithExpectedProperties(regeneratedSecrets, scannerTLSSecret, - "cert.pem", "key.pem", "SCANNER_SERVICE", start) - testMatchingSecretFoundWithExpectedProperties(regeneratedSecrets, scannerDBTLSSecret, - "cert.pem", "key.pem", "SCANNER_DB_SERVICE", start) - } - def "Test sensor cert rotation"() { when: "Fetch the current sensor-tls, collector-tls and admission-control-tls secrets, and regenerate certs" diff --git a/qa-tests-backend/src/test/groovy/GraphQLResourcePaginationTest.groovy b/qa-tests-backend/src/test/groovy/GraphQLResourcePaginationTest.groovy index 400e862051fe5..9f388c0d8501e 100644 --- a/qa-tests-backend/src/test/groovy/GraphQLResourcePaginationTest.groovy +++ b/qa-tests-backend/src/test/groovy/GraphQLResourcePaginationTest.groovy @@ -63,7 +63,7 @@ class GraphQLResourcePaginationTest extends BaseSpecification { // TODO: re-activate once fixed against postgres //"image" | "Image:main" | null | "Sort(null)" | "deployments" - "secret" | "Secret:scanner-db-password" | null | "Sort(null)" | "deployments" + "secret" | "Secret:central-tls" | null | "Sort(null)" | "deployments" "subject" | "Subject:kubelet" | null | "Sort(null)" | "k8sRoles" diff --git a/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy b/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy index bfda3507ebc5b..c709e80c1498b 100644 --- a/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy +++ b/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy @@ -158,7 +158,9 @@ class ImageScanningTest extends BaseSpecification { def cleanupSpec() { orchestrator.deleteNamespace(TEST_NAMESPACE) - ImageIntegrationService.addStackroxScannerIntegration() + if (!scannerV4Enabled) { + ImageIntegrationService.addStackroxScannerIntegration() + } addGCRImagePullSecret(orchestrator) for (Policy policy : policiesScopedForTest) { @@ -480,7 +482,9 @@ class ImageScanningTest extends BaseSpecification { @Tag("BAT") @Tag("Integration") def "Verify Scan Results from Registries - #registry.name() - #component:#version - #image - #cve - #idx"() { - ImageIntegrationService.addStackroxScannerIntegration() + if (!scannerV4Enabled) { + ImageIntegrationService.addStackroxScannerIntegration() + } when: "Add scanner" @@ -509,7 +513,9 @@ class ImageScanningTest extends BaseSpecification { vuln != null cleanup: - deleteStackroxScanner = true + if (!scannerV4Enabled) { + deleteStackroxScanner = true + } imageToCleanup = image where: @@ -661,7 +667,7 @@ class ImageScanningTest extends BaseSpecification { { -> AzureRegistryIntegration.createDefaultIntegration() } "acr-config-only" | "acr" | true | /^acr$/ | { -> AzureRegistryIntegration.createDefaultIntegration() } - "quay-auto" | "quay" | false | source(".*.quay.io") | null + "quay-auto" | "quay" | false | source(".*quay\\.io.*") | null // ROX-29720 - disable gcr.io until tests are fixed for metadata failures after migration to artifacts registry //"gcr-auto" | "gcr" | false | source(".*.gcr.io") | null } @@ -674,8 +680,10 @@ class ImageScanningTest extends BaseSpecification { @Unroll @Tag("Integration") def "Image scanning test to check if scan time is not null #image from stackrox"() { + Assume.assumeTrue(StackroxScannerIntegration.isTestable()) + when: - "Add Stackrox scanner" + "Add scanner" String integrationId = StackroxScannerIntegration.createDefaultIntegration() assert integrationId integrationIds.add(integrationId) diff --git a/qa-tests-backend/src/test/groovy/IntegrationHealthTest.groovy b/qa-tests-backend/src/test/groovy/IntegrationHealthTest.groovy index 119740b9b1ba5..92f1cb1793354 100644 --- a/qa-tests-backend/src/test/groovy/IntegrationHealthTest.groovy +++ b/qa-tests-backend/src/test/groovy/IntegrationHealthTest.groovy @@ -1,5 +1,7 @@ +import objects.StackroxScannerIntegration import services.IntegrationHealthService +import org.junit.Assume import spock.lang.Tag import spock.lang.Unroll @@ -10,6 +12,7 @@ class IntegrationHealthTest extends BaseSpecification { def "Verify vulnerability definitions information is available"() { when: "Vulnerability definition is requested" + Assume.assumeTrue("Clairify integration is present", StackroxScannerIntegration.isTestable()) def vulnDefInfo = IntegrationHealthService.getVulnDefinitionsInfo() then: diff --git a/roxctl/central/generate/interactive.go b/roxctl/central/generate/interactive.go index ab71755c2d954..fe544dca69b66 100644 --- a/roxctl/central/generate/interactive.go +++ b/roxctl/central/generate/interactive.go @@ -26,7 +26,7 @@ const ( ) var ( - orderedFlagGroupNames = []string{"central", "central-db", "scanner", "scanner-v4"} + orderedFlagGroupNames = []string{"central", "central-db", "scanner-v4"} ) func readUserInput(prompt string) (string, error) { @@ -318,8 +318,6 @@ func processFlagWraps(argSlice *argSlice, fws []flagWrap) { // set default values for image-{main,scanner,scanner-db,scanner-v4,scanner-v4-db} flags if fw.Flag.Name == flags.FlagNameMainImage || - fw.Flag.Name == flags.FlagNameScannerImage || - fw.Flag.Name == flags.FlagNameScannerDBImage || fw.Flag.Name == flags.FlagNameScannerV4Image || fw.Flag.Name == flags.FlagNameScannerV4DBImage || fw.Flag.Name == flags.FlagNameCentralDBImage { @@ -336,14 +334,6 @@ func processFlagWraps(argSlice *argSlice, fws []flagWrap) { if fw.Flag.DefValue == "" { fw.Flag.DefValue = flavor.MainImage() } - case flags.FlagNameScannerImage: - if fw.Flag.DefValue == "" { - fw.Flag.DefValue = flavor.ScannerImage() - } - case flags.FlagNameScannerDBImage: - if fw.Flag.DefValue == "" { - fw.Flag.DefValue = flavor.ScannerDBImage() - } case flags.FlagNameScannerV4Image: if fw.Flag.DefValue == "" { fw.Flag.DefValue = flavor.ScannerV4Image() diff --git a/roxctl/central/generate/k8s.go b/roxctl/central/generate/k8s.go index 5162f708ced31..9b0a27b7827ee 100644 --- a/roxctl/central/generate/k8s.go +++ b/roxctl/central/generate/k8s.go @@ -112,8 +112,6 @@ func k8sBasedOrchestrator(cliEnvironment environment.Environment, k8sConfig *ren flagWrap.StringVarP(&k8sConfig.MainImage, flags.FlagNameMainImage, "i", "", "The main image to use"+defaultImageHelp, "central") flagWrap.BoolVar(&k8sConfig.OfflineMode, "offline", false, "Whether to run StackRox in offline mode, which avoids reaching out to the Internet.", "central") flagWrap.StringVar(&k8sConfig.CentralDBImage, flags.FlagNameCentralDBImage, "", "The central-db image to use"+defaultImageHelp, "central") - flagWrap.StringVar(&k8sConfig.ScannerImage, flags.FlagNameScannerImage, "", "The scanner image to use"+defaultImageHelp, "scanner") - flagWrap.StringVar(&k8sConfig.ScannerDBImage, flags.FlagNameScannerDBImage, "", "The scanner-db image to use"+defaultImageHelp, "scanner") flagWrap.StringVar(&k8sConfig.ScannerV4Image, flags.FlagNameScannerV4Image, "", "The scanner-v4 image to use"+defaultImageHelp, "scanner-v4") flagWrap.StringVar(&k8sConfig.ScannerV4DBImage, flags.FlagNameScannerV4DBImage, "", "The scanner-v4-db image to use"+defaultImageHelp, "scanner-v4") flagWrap.BoolVar(&k8sConfig.Telemetry.Enabled, "enable-telemetry", version.IsReleaseVersion(), "Whether to enable telemetry.", "central") diff --git a/roxctl/common/flags/imageFlavor.go b/roxctl/common/flags/imageFlavor.go index f9ba2f7478912..020ef6df7b340 100644 --- a/roxctl/common/flags/imageFlavor.go +++ b/roxctl/common/flags/imageFlavor.go @@ -16,10 +16,6 @@ const ( FlagNameMainImage = "main-image" // FlagNameCentralDBImage is a shared constant for --central-db-image command line flag. FlagNameCentralDBImage = "central-db-image" - // FlagNameScannerImage is a shared constant for --scanner-image command line flag. - FlagNameScannerImage = "scanner-image" - // FlagNameScannerDBImage is a shared constant for --scanner-db-image command line flag. - FlagNameScannerDBImage = "scanner-db-image" // FlagNameScannerV4Image is a shared constant for --scanner-v4-image command line flag. FlagNameScannerV4Image = "scanner-v4-image" // FlagNameScannerV4DBImage is a shared constant for --scanner-v4-db-image command line flag. diff --git a/roxctl/maincommand/command_tree_debug.yaml b/roxctl/maincommand/command_tree_debug.yaml index d2bf21b69860b..3b15e497cdea3 100644 --- a/roxctl/maincommand/command_tree_debug.yaml +++ b/roxctl/maincommand/command_tree_debug.yaml @@ -521,8 +521,6 @@ central: - offline - output-dir - output-format - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image INHERITED_FLAGS: @@ -583,8 +581,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -622,8 +618,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -665,8 +659,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -688,8 +680,6 @@ central: - openshift-version - output-dir - output-format - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image INHERITED_FLAGS: @@ -752,8 +742,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -793,8 +781,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -838,8 +824,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -2029,7 +2013,6 @@ scanner: LOCAL_FLAGS: - istio-support - output-dir - - scanner-image - scanner-v4-db-image - scanner-v4-image PERSISTENT_FLAGS: diff --git a/roxctl/maincommand/command_tree_release.yaml b/roxctl/maincommand/command_tree_release.yaml index ce063deaf2e2b..f68e511aa3d70 100644 --- a/roxctl/maincommand/command_tree_release.yaml +++ b/roxctl/maincommand/command_tree_release.yaml @@ -517,8 +517,6 @@ central: - offline - output-dir - output-format - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image INHERITED_FLAGS: @@ -575,8 +573,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -612,8 +608,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -653,8 +647,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -676,8 +668,6 @@ central: - openshift-version - output-dir - output-format - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image INHERITED_FLAGS: @@ -736,8 +726,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -775,8 +763,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -818,8 +804,6 @@ central: - password - plaintext - plaintext-endpoints - - scanner-db-image - - scanner-image - scanner-v4-db-image - scanner-v4-image - server-name @@ -1990,7 +1974,6 @@ scanner: LOCAL_FLAGS: - istio-support - output-dir - - scanner-image - scanner-v4-db-image - scanner-v4-image PERSISTENT_FLAGS: diff --git a/roxctl/scanner/generate/generate.go b/roxctl/scanner/generate/generate.go index c2a7f276e6066..0b6f291de0b06 100644 --- a/roxctl/scanner/generate/generate.go +++ b/roxctl/scanner/generate/generate.go @@ -73,7 +73,7 @@ func Command(cliEnvironment environment.Environment) *cobra.Command { c := &cobra.Command{ Use: "generate", - Short: "Generate the required YAML configuration files to deploy StackRox Scanner and Scanner V4", + Short: "Generate the required YAML configuration files to deploy Scanner V4", Args: cobra.NoArgs, RunE: func(c *cobra.Command, args []string) error { scannerGenerateCmd.construct(c) @@ -89,7 +89,6 @@ func Command(cliEnvironment environment.Environment) *cobra.Command { c.PersistentFlags().Var(clustertype.Value(storage.ClusterType_KUBERNETES_CLUSTER), "cluster-type", "Type of cluster the scanner will run on (k8s, openshift).") c.Flags().StringVar(&scannerGenerateCmd.outputDir, "output-dir", "", "Output directory for scanner bundle (leave blank for default).") - c.Flags().StringVar(&scannerGenerateCmd.apiParams.ScannerImage, flags.FlagNameScannerImage, "", "Scanner image to use (leave blank to use server default).") c.Flags().StringVar(&scannerGenerateCmd.apiParams.ScannerV4Image, flags.FlagNameScannerV4Image, "", "Scanner V4 image to use (leave blank to use server default).") c.Flags().StringVar(&scannerGenerateCmd.apiParams.ScannerV4DBImage, flags.FlagNameScannerV4DBImage, "", "Scanner V4 DB image to use (leave blank to use server default).") c.Flags().StringVar(&scannerGenerateCmd.apiParams.IstioVersion, istioSupportArg, "", diff --git a/scripts/ci/is-scanner-v2-available.sh b/scripts/ci/is-scanner-v2-available.sh deleted file mode 100755 index f6656b18a651b..0000000000000 --- a/scripts/ci/is-scanner-v2-available.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Asserts that scanner v2 is running and ready in the supplied namespace - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" -# shellcheck source=../../tests/e2e/lib.sh -source "$ROOT/tests/e2e/lib.sh" - -verify_scannerV2_deployed_and_ready() { - if [[ "$#" -ne 1 ]]; then - die "missing arg. usage: verify_scannerV2_deployed_and_ready " - fi - local namespace=${1:-stackrox} - info "Waiting for Scanner V2 deployment to appear in namespace ${namespace}..." - wait_for_object_to_appear "$namespace" deploy/scanner-db 600 - wait_for_object_to_appear "$namespace" deploy/scanner 300 - info "** Scanner V2 is deployed in namespace ${namespace}" - kubectl -n "${namespace}" wait --for=condition=ready pod -l app=scanner --timeout 5m -} - -verify_scannerV2_deployed_and_ready "$@" diff --git a/scripts/ci/jobs/gke_nongroovy_e2e_tests.py b/scripts/ci/jobs/gke_nongroovy_e2e_tests.py index f173c23e3291a..cfe1e152aeece 100755 --- a/scripts/ci/jobs/gke_nongroovy_e2e_tests.py +++ b/scripts/ci/jobs/gke_nongroovy_e2e_tests.py @@ -19,7 +19,7 @@ os.environ["SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST"] = "rhel-vex,stackrox-rhel-csaf,manual,epss,nvd" # delegated scanning support in the secured cluster -os.environ["SENSOR_SCANNER_SUPPORT"] = "true" +os.environ["SENSOR_SCANNER_V4_SUPPORT"] = "true" # Enable new CRS-based flow for registering secured clusters os.environ["ROX_DEPLOY_SENSOR_WITH_CRS"] = "true" diff --git a/scripts/ci/jobs/ocp_nongroovy_e2e_tests.py b/scripts/ci/jobs/ocp_nongroovy_e2e_tests.py index bc2f86b865102..685984e5b8237 100755 --- a/scripts/ci/jobs/ocp_nongroovy_e2e_tests.py +++ b/scripts/ci/jobs/ocp_nongroovy_e2e_tests.py @@ -16,7 +16,7 @@ os.environ["KUBERNETES_PROVIDER"] = "ocp" # delegated scanning support in the secured cluster -os.environ["SENSOR_SCANNER_SUPPORT"] = "true" +os.environ["SENSOR_SCANNER_V4_SUPPORT"] = "true" # Enable new CRS-based flow for registering secured clusters os.environ["ROX_DEPLOY_SENSOR_WITH_CRS"] = "true" diff --git a/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init.go b/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init.go index 838872925cd82..fba435a8f8c9d 100644 --- a/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init.go +++ b/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init.go @@ -18,8 +18,6 @@ func NewServiceCertificatesRepo(ownerReference metav1.OwnerReference, namespace storage.ServiceType_SENSOR_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.SensorTLSSecretName), storage.ServiceType_COLLECTOR_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.CollectorTLSSecretName), storage.ServiceType_ADMISSION_CONTROL_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.AdmissionControlTLSSecretName), - storage.ServiceType_SCANNER_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.ScannerTLSSecretName), - storage.ServiceType_SCANNER_DB_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.ScannerDbTLSSecretName), storage.ServiceType_SCANNER_V4_INDEXER_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.ScannerV4IndexerTLSSecretName), storage.ServiceType_SCANNER_V4_DB_SERVICE: certrepo.NewServiceCertSecretSpec(securedcluster.ScannerV4DbTLSSecretName), } diff --git a/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init_test.go b/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init_test.go index f3ac390b6485b..cd10a0d06ae85 100644 --- a/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init_test.go +++ b/sensor/kubernetes/certrefresh/securedcluster/cert_repo_init_test.go @@ -27,8 +27,6 @@ var ( createServiceCertificate(storage.ServiceType_SENSOR_SERVICE), createServiceCertificate(storage.ServiceType_ADMISSION_CONTROL_SERVICE), createServiceCertificate(storage.ServiceType_COLLECTOR_SERVICE), - createServiceCertificate(storage.ServiceType_SCANNER_SERVICE), - createServiceCertificate(storage.ServiceType_SCANNER_DB_SERVICE), createServiceCertificate(storage.ServiceType_SCANNER_V4_INDEXER_SERVICE), createServiceCertificate(storage.ServiceType_SCANNER_V4_DB_SERVICE), }, @@ -50,8 +48,6 @@ func TestEnsureServiceCertificates(t *testing.T) { securedcluster.SensorTLSSecretName, securedcluster.CollectorTLSSecretName, securedcluster.AdmissionControlTLSSecretName, - securedcluster.ScannerTLSSecretName, - securedcluster.ScannerDbTLSSecretName, securedcluster.ScannerV4IndexerTLSSecretName, securedcluster.ScannerV4DbTLSSecretName, } diff --git a/sensor/kubernetes/certrefresh/securedcluster_tls_issuer_test.go b/sensor/kubernetes/certrefresh/securedcluster_tls_issuer_test.go index ac083120f992f..576357e1f0021 100644 --- a/sensor/kubernetes/certrefresh/securedcluster_tls_issuer_test.go +++ b/sensor/kubernetes/certrefresh/securedcluster_tls_issuer_test.go @@ -43,8 +43,6 @@ const ( sensorSecretName = securedcluster.SensorTLSSecretName collectorSecretName = securedcluster.CollectorTLSSecretName admissionControlSecretName = securedcluster.AdmissionControlTLSSecretName - scannerSecretName = securedcluster.ScannerTLSSecretName - scannerDbSecretName = securedcluster.ScannerDbTLSSecretName scannerV4IndexerSecretName = securedcluster.ScannerV4IndexerTLSSecretName scannerV4DbSecretName = securedcluster.ScannerV4DbTLSSecretName ) @@ -397,19 +395,12 @@ func (s *securedClusterTLSIssuerIntegrationTests) TestSuccessfulRefresh() { secretsData: map[string]map[string][]byte{sensorSecretName: nil}, }, }, - "corrupted data in scanner DB secret": { - k8sClientConfig: fakeK8sClientConfig{ - secretsData: map[string]map[string][]byte{scannerDbSecretName: nil}, - }, - }, "corrupted data in all secured cluster secrets": { k8sClientConfig: fakeK8sClientConfig{ secretsData: map[string]map[string][]byte{ sensorSecretName: nil, collectorSecretName: nil, admissionControlSecretName: nil, - scannerSecretName: nil, - scannerDbSecretName: nil, scannerV4IndexerSecretName: nil, scannerV4DbSecretName: nil, }, @@ -573,14 +564,11 @@ func (s *securedClusterTLSIssuerIntegrationTests) TestUnexpectedOwnerStop() { testCases := map[string]struct { secretNames []string }{ - "wrong owner for sensor secret": {secretNames: []string{sensorSecretName}}, - "wrong owner for collector secret": {secretNames: []string{collectorSecretName}}, - "wrong owner for admission controller secret": {secretNames: []string{admissionControlSecretName}}, - "wrong owner for scanner secret": {secretNames: []string{scannerSecretName}}, - "wrong owner for scanner db secret": {secretNames: []string{scannerDbSecretName}}, - "wrong owner for scanner v4 indexer secret": {secretNames: []string{scannerV4IndexerSecretName}}, - "wrong owner for scanner v4 db secret": {secretNames: []string{scannerV4DbSecretName}}, - "wrong owner for scanner and scanner db secrets": {secretNames: []string{scannerSecretName, scannerDbSecretName}}, + "wrong owner for sensor secret": {secretNames: []string{sensorSecretName}}, + "wrong owner for collector secret": {secretNames: []string{collectorSecretName}}, + "wrong owner for admission controller secret": {secretNames: []string{admissionControlSecretName}}, + "wrong owner for scanner v4 indexer secret": {secretNames: []string{scannerV4IndexerSecretName}}, + "wrong owner for scanner v4 db secret": {secretNames: []string{scannerV4DbSecretName}}, } for tcName, tc := range testCases { s.Run(tcName, func() { @@ -801,8 +789,6 @@ func getAllSecuredClusterCertificates(t require.TestingT) map[string]*mtls.Issue sensorSecretName: getCertificate(t, storage.ServiceType_SENSOR_SERVICE), collectorSecretName: getCertificate(t, storage.ServiceType_COLLECTOR_SERVICE), admissionControlSecretName: getCertificate(t, storage.ServiceType_ADMISSION_CONTROL_SERVICE), - scannerSecretName: getCertificate(t, storage.ServiceType_SCANNER_SERVICE), - scannerDbSecretName: getCertificate(t, storage.ServiceType_SCANNER_DB_SERVICE), scannerV4IndexerSecretName: getCertificate(t, storage.ServiceType_SCANNER_V4_INDEXER_SERVICE), scannerV4DbSecretName: getCertificate(t, storage.ServiceType_SCANNER_V4_DB_SERVICE), } @@ -829,8 +815,6 @@ func getSecuredClusterIssueCertsSuccessResponse( sensorSecretName: storage.ServiceType_SENSOR_SERVICE, collectorSecretName: storage.ServiceType_COLLECTOR_SERVICE, admissionControlSecretName: storage.ServiceType_ADMISSION_CONTROL_SERVICE, - scannerSecretName: storage.ServiceType_SCANNER_SERVICE, - scannerDbSecretName: storage.ServiceType_SCANNER_DB_SERVICE, scannerV4IndexerSecretName: storage.ServiceType_SCANNER_V4_INDEXER_SERVICE, scannerV4DbSecretName: storage.ServiceType_SCANNER_V4_DB_SERVICE, } diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index df72fd80814f4..a61a322db363c 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -177,8 +177,9 @@ roxie_config_from_environment_compat() { ) info "Configuring scanner V4..." - handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" - handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" + handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" "Enabled" + handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" "AutoSense" + handle_scanner_v4_vuln_readiness "$config_file" info "Configuring declarative configuration..." handle_declarative_configuration "$config_file" @@ -222,14 +223,18 @@ handle_pod_security_policies() { ci_export POD_SECURITY_POLICIES "$POD_SECURITY_POLICIES" } +# handle_scanner_v4_setting patches scannerV4.scannerComponent. enabled_value is +# the CRD enum when V4 is on: Central uses Enabled, SecuredCluster uses AutoSense. handle_scanner_v4_setting() { local config_file="$1" local path="$2" - local rox_scanner_v4="${ROX_SCANNER_V4:-false}" # To match the previous defaulting + local enabled_value="$3" + # Scanner V4 is the default scanner. Jobs that must omit it set ROX_SCANNER_V4=false. + local rox_scanner_v4="${ROX_SCANNER_V4:-true}" case "$rox_scanner_v4" in true) - patch_yaml "$config_file" "${path} = \"Enabled\"" + patch_yaml "$config_file" "${path} = \"${enabled_value}\"" ;; false) patch_yaml "$config_file" "${path} = \"Disabled\"" @@ -240,6 +245,36 @@ handle_scanner_v4_setting() { esac } +# handle_scanner_v4_vuln_readiness gates the scanner-v4-matcher pod's readiness +# probe on the vulnerability bundle being fully loaded. The matcher's default +# readiness is "database" (ready as soon as Postgres is reachable), so without +# this the deployment reports ready while vulns are still importing and tests +# would scan before any vuln data exists. Setting +# SCANNER_V4_MATCHER_READINESS=vulnerability keeps the matcher unready until the +# initial load completes, so the deploy step waits for it. The env vars go into +# the Central CR's customize.envVars, which the operator propagates to +# scanner-v4-matcher. +handle_scanner_v4_vuln_readiness() { + local config_file="$1" + + # Only meaningful when Scanner V4 is enabled. + if [[ "${ROX_SCANNER_V4:-true}" == "false" ]]; then + return + fi + + # export_test_environment establishes the default (true); jobs that don't + # need gating (e.g. install-only) set SCANNER_V4_VULN_READINESS=false. + if [[ "${SCANNER_V4_VULN_READINESS:-false}" == "true" ]]; then + info " gating scanner-v4-matcher readiness on vulnerability load" + set_custom_env "$config_file" "central" "SCANNER_V4_MATCHER_READINESS" "vulnerability" + fi + + if [[ -n "${SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST:-}" ]]; then + info " restricting vuln bundle sources to ${SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST}" + set_custom_env "$config_file" "central" "SCANNER_V4_MATCHER_VULN_BUNDLE_ALLOWLIST" "${SCANNER_V4_CI_VULN_BUNDLE_ALLOWLIST}" + fi +} + handle_trusted_ca_file() { local config_file="$1" local trusted_ca_file="${TRUSTED_CA_FILE:-}" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 76be09180b6e5..c6b371965f700 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -39,9 +39,6 @@ POD_CONTAINERS_MAP["pod: central - container: central"]="central-[A-Za-z0-9]+-[A POD_CONTAINERS_MAP["pod: central-db - container: init-db"]="central-db-[A-Za-z0-9]+-[A-Za-z0-9]+-init-db-previous.log" POD_CONTAINERS_MAP["pod: central-db - container: central-db"]="central-db-[A-Za-z0-9]+-[A-Za-z0-9]+-central-db-previous.log" POD_CONTAINERS_MAP["pod: config-controller - container: manager"]="config-controller-[A-Za-z0-9]+-[A-Za-z0-9]+-manager-previous.log" -POD_CONTAINERS_MAP["pod: scanner - container: scanner"]="scanner-[A-Za-z0-9]+-[A-Za-z0-9]+-scanner-previous.log" -POD_CONTAINERS_MAP["pod: scanner-db - container: init-db"]="scanner-db-[A-Za-z0-9]+-[A-Za-z0-9]+-init-db-previous.log" -POD_CONTAINERS_MAP["pod: scanner-db - container: db"]="scanner-db-[A-Za-z0-9]+-[A-Za-z0-9]+-db-previous.log" # Matcher and indexer are separate deployments: scanner-v4-matcher-*, scanner-v4-indexer-*. POD_CONTAINERS_MAP["pod: scanner-v4-matcher - container: matcher"]="scanner-v4-matcher-[A-Za-z0-9]+-[A-Za-z0-9]+-matcher-previous.log" POD_CONTAINERS_MAP["pod: scanner-v4-indexer - container: indexer"]="scanner-v4-indexer-[A-Za-z0-9]+-[A-Za-z0-9]+-indexer-previous.log" @@ -51,7 +48,6 @@ POD_CONTAINERS_MAP["pod: sensor - container: sensor"]="sensor-[A-Za-z0-9]+-[A-Za POD_CONTAINERS_MAP["pod: admission-control - container: admission-control"]="admission-control-[A-Za-z0-9]+-[A-Za-z0-9]+-admission-control-previous.log" POD_CONTAINERS_MAP["pod: collector - container: collector"]="collector-[A-Za-z0-9]+-collector-previous.log" POD_CONTAINERS_MAP["pod: collector - container: compliance"]="collector-[A-Za-z0-9]+-compliance-previous.log" -POD_CONTAINERS_MAP["pod: collector - container: node-inventory"]="collector-[A-Za-z0-9]+-node-inventory-previous.log" # Note: the caller must make sure to redirect stdin to /dev/null where needed. retrying_kubectl() { @@ -742,10 +738,6 @@ deploy_sensor_via_operator() { --output -' \ | retrying_kubectl -n "${sensor_namespace}" apply -f - - if [[ "${SENSOR_SCANNER_SUPPORT:-}" == "true" ]]; then - scanner_component_setting="AutoSense" - fi - local secured_cluster_yaml_path="tests/e2e/yaml/secured-cluster-cr.envsubst.yaml" if [[ "${ROX_SCANNER_V4:-false}" == "true" ]]; then secured_cluster_yaml_path="tests/e2e/yaml/secured-cluster-cr-with-scanner-v4.envsubst.yaml" diff --git a/tests/e2e/run-scanner-v4-install.bats b/tests/e2e/run-scanner-v4-install.bats index 90d25fd9016dc..4d8ab27e02314 100755 --- a/tests/e2e/run-scanner-v4-install.bats +++ b/tests/e2e/run-scanner-v4-install.bats @@ -462,7 +462,6 @@ EOT ) _begin "verify-scanners-are-deployed" - verify_scannerV2_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_scannerV4_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_CENTRAL_NAMESPACE" "central" @@ -471,7 +470,8 @@ EOT --reuse-values _begin "verify-scanners-are-deployed" - verify_scannerV2_deployed "$CUSTOM_CENTRAL_NAMESPACE" + # TODO(ROX-36705): Remove when 5.1 is cut. + verify_no_scannerV2_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_scannerV4_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_CENTRAL_NAMESPACE" "central" @@ -484,7 +484,6 @@ EOT "$secured_cluster_name" "$ROX_ADMIN_PASSWORD" "$central_endpoint" _begin "verify-scanners-are-deployed" - verify_scannerV2_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_scannerV4_indexer_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_SENSOR_NAMESPACE" "sensor" @@ -492,7 +491,8 @@ EOT deploy_sensor_with_helm "$CUSTOM_CENTRAL_NAMESPACE" "$CUSTOM_SENSOR_NAMESPACE" "" "" "" "" "" _begin "verify-scanners-are-deployed" - verify_scannerV2_deployed "$CUSTOM_SENSOR_NAMESPACE" + # TODO(ROX-36705): Remove when 5.1 is cut. + verify_no_scannerV2_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_scannerV4_indexer_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_SENSOR_NAMESPACE" "sensor" @@ -537,13 +537,11 @@ EOT ###################### _begin "verifying-central-scanners-deployed" - verify_scannerV2_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_scannerV4_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_CENTRAL_NAMESPACE" "central" ###################### _begin "verifying-sensor-scanners-deployed" - verify_scannerV2_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_scannerV4_indexer_deployed "$CUSTOM_SENSOR_NAMESPACE" run verify_deployment_scannerV4_env_var_set "$CUSTOM_SENSOR_NAMESPACE" "sensor" @@ -564,14 +562,12 @@ EOT ###################### _begin "verifying-central-scanners-deployed" info "Verifying that scanners are still installed" - verify_scannerV2_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_scannerV4_deployed "$CUSTOM_CENTRAL_NAMESPACE" verify_deployment_scannerV4_env_var_set "$CUSTOM_CENTRAL_NAMESPACE" "central" ###################### _begin "verifying-sensor-scanners-deployed" info "Verifying that scanners are still installed" - verify_scannerV2_deployed "$CUSTOM_SENSOR_NAMESPACE" verify_scannerV4_indexer_deployed "$CUSTOM_SENSOR_NAMESPACE" run verify_deployment_scannerV4_env_var_set "$CUSTOM_SENSOR_NAMESPACE" "sensor" @@ -629,7 +625,6 @@ EOT ###################### _begin "verifying-central-scanners-deployed" info "Verifying that scanners are deployed" - verify_scannerV2_deployed "$namespace" verify_scannerV4_deployed "$namespace" info "Verifying that scanner V4 is enabled for sensor" verify_deployment_scannerV4_env_var_set "$namespace" "central" @@ -656,7 +651,6 @@ EOT ###################### _begin "verifying-central-scanners-deployed" info "Verifying that scanners are still installed" - verify_scannerV2_deployed "$namespace" verify_scannerV4_deployed "$namespace" verify_deployment_scannerV4_env_var_set "$namespace" "central" @@ -704,12 +698,9 @@ EOT # shellcheck disable=SC2030,SC2031 export OUTPUT_FORMAT=helm # shellcheck disable=SC2030,SC2031 - export SENSOR_SCANNER_SUPPORT=true - # shellcheck disable=SC2030,SC2031 export SENSOR_SCANNER_V4_SUPPORT=true _deploy_stackrox "" "$central_namespace" "$sensor_namespace" - verify_scannerV2_deployed "$central_namespace" verify_scannerV4_deployed "$central_namespace" verify_deployment_scannerV4_env_var_set "$central_namespace" "central" verify_scannerV4_indexer_deployed "$sensor_namespace" @@ -744,7 +735,6 @@ EOT _begin "verify" - verify_scannerV2_deployed verify_no_scannerV4_deployed run ! verify_deployment_scannerV4_env_var_set "stackrox" "central" @@ -779,8 +769,6 @@ EOT export ROX_SCANNER_V4="" # Scanner V4 enabled by default. # shellcheck disable=SC2030,SC2031 export DEPLOY_STACKROX_VIA_OPERATOR="true" - # shellcheck disable=SC2030,SC2031 - export SENSOR_SCANNER_SUPPORT=true _begin "deploy-stackrox" @@ -789,7 +777,6 @@ EOT _begin "verify" - verify_scannerV2_deployed "stackrox" verify_scannerV4_deployed "stackrox" verify_deployment_scannerV4_env_var_set "stackrox" "central" verify_deployment_scannerV4_env_var_set "stackrox" "sensor" @@ -816,8 +803,6 @@ EOT export ROX_SCANNER_V4="" # Scanner V4 enabled by default. # shellcheck disable=SC2030,SC2031 export DEPLOY_STACKROX_VIA_OPERATOR="true" - # shellcheck disable=SC2030,SC2031 - export SENSOR_SCANNER_SUPPORT=true _begin "deploy-stackrox" @@ -826,11 +811,9 @@ EOT _begin "verify" - verify_scannerV2_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_scannerV4_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_CENTRAL_NAMESPACE}" "central" - verify_scannerV2_deployed "${CUSTOM_SENSOR_NAMESPACE}" verify_scannerV4_indexer_deployed "${CUSTOM_SENSOR_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_SENSOR_NAMESPACE}" "sensor" @@ -850,8 +833,6 @@ EOT # shellcheck disable=SC2030,SC2031 export DEPLOY_STACKROX_VIA_OPERATOR="true" # shellcheck disable=SC2030,SC2031 - export SENSOR_SCANNER_SUPPORT=true - # shellcheck disable=SC2030,SC2031 export ROX_SCANNER_V4="" # Scanner V4 enabled by default. _begin "deploy-stackrox" @@ -863,10 +844,9 @@ EOT _begin "verify" - verify_scannerV2_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_scannerV4_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_CENTRAL_NAMESPACE}" "central" - verify_scannerV2_deployed "${CUSTOM_SENSOR_NAMESPACE}" + # Scanner V2 in sensor is disabled via operator deployment path. verify_scannerV4_indexer_deployed "${CUSTOM_SENSOR_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_SENSOR_NAMESPACE}" "sensor" @@ -885,10 +865,10 @@ EOT _begin "verify" - verify_scannerV2_deployed "${CUSTOM_CENTRAL_NAMESPACE}" + verify_no_scannerV2_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_scannerV4_deployed "${CUSTOM_CENTRAL_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_CENTRAL_NAMESPACE}" "central" - verify_scannerV2_deployed "${CUSTOM_SENSOR_NAMESPACE}" + verify_no_scannerV2_deployed "${CUSTOM_SENSOR_NAMESPACE}" verify_scannerV4_indexer_deployed "${CUSTOM_SENSOR_NAMESPACE}" verify_deployment_scannerV4_env_var_set "${CUSTOM_SENSOR_NAMESPACE}" "sensor" @@ -942,7 +922,6 @@ EOT _begin "verify" - verify_scannerV2_deployed "stackrox" verify_scannerV4_deployed "stackrox" verify_deployment_scannerV4_env_var_set "stackrox" "central" @@ -965,7 +944,15 @@ EOT _begin "verify" - verify_scannerV2_deployed + # Note: The above deployment machinery is using the deployment scripts from this branch under the hood for working with + # manifest bundles generated by the previous roxctl version. + # The support in these scripts for deploying scanner V2 has been removed. Hence we cannot check here for installability + # of scanner V2. + # + # Also, we wouldn't be able to confirm any behaviour such as "existing scanner V2 is removed on upgrade", because + # manifest bundles don't provide this functionality. + # + # Bottom line is, this test case is now purely a V4 test case. verify_no_scannerV4_deployed run ! verify_deployment_scannerV4_env_var_set "stackrox" "central" run ! verify_deployment_scannerV4_env_var_set "stackrox" "sensor" @@ -977,7 +964,6 @@ EOT _begin "verify" - verify_scannerV2_deployed verify_scannerV4_deployed verify_deployment_scannerV4_env_var_set "stackrox" "central" run ! verify_deployment_scannerV4_env_var_set "stackrox" "sensor" # no Scanner V4 support in Sensor with roxctl @@ -1077,16 +1063,6 @@ verify_no_scannerV4_matcher_deployed() { refute_output --regexp "scanner-v4-matcher" } -# TODO: For now, Scanner v2 is expected to run in parallel. -# This must be removed when Scanner v2 will be phased out. -verify_scannerV2_deployed() { - local namespace=${1:-stackrox} - info "Waiting for Scanner V2 deployment to appear in namespace ${namespace}..." - wait_for_object_to_appear "$namespace" deploy/scanner-db 600 - wait_for_object_to_appear "$namespace" deploy/scanner 300 - info "** Scanner V2 is deployed in namespace ${namespace}" -} - verify_no_scannerV2_deployed() { local namespace=${1:-stackrox} echo "Verifying that scanner V2 is not deployed" diff --git a/tests/roxctl/bats-tests/cluster/scanner-generate.bats b/tests/roxctl/bats-tests/cluster/scanner-generate.bats index 278cffff40682..048856fe37ee4 100755 --- a/tests/roxctl/bats-tests/cluster/scanner-generate.bats +++ b/tests/roxctl/bats-tests/cluster/scanner-generate.bats @@ -37,7 +37,7 @@ run_scanner_generate_and_check() { } assert_number_of_k8s_resources() { - local -r k8s_resources_count=$(cat "${output_dir}/scanner/"*.yaml | grep -c "^apiVersion") || true + local -r k8s_resources_count=$(cat "${output_dir}/scanner-v4/"*.yaml | grep -c "^apiVersion") || true [[ "${k8s_resources_count}" = "${1}" ]] || fail "Unexpected number of k8s resources: expected ${1}, got ${k8s_resources_count}" } @@ -45,31 +45,25 @@ assert_number_of_k8s_resources() { @test "[openshift4] roxctl scanner generate" { run_scanner_generate_and_check openshift4 - assert_file_exist "${output_dir}/scanner/02-scanner-06-deployment.yaml" + assert_file_exist "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" - # The only difference between OpenShift and OpenShift 4 configurations is that OpenShift 4 configuration has - # additional mounted volume. It's called "trusted-ca-volume" and we use it to identify OpenShift 4 configuration. - run -0 grep -q 'trusted-ca-volume' "${output_dir}/scanner/02-scanner-06-deployment.yaml" - run -0 grep -q 'ROX_OPENSHIFT_API' "${output_dir}/scanner/02-scanner-06-deployment.yaml" - assert_number_of_k8s_resources 14 + run -0 grep -q 'ROX_OPENSHIFT_API' "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" + assert_number_of_k8s_resources 24 } @test "[k8s] roxctl scanner generate" { run_scanner_generate_and_check k8s - assert_file_exist "${output_dir}/scanner/scripts/setup.sh" - run -0 grep -q 'KUBE_COMMAND:-kubectl' "${output_dir}/scanner/scripts/setup.sh" + assert_file_exist "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" + run -1 grep -q 'ROX_OPENSHIFT_API' "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" - assert_file_exist "${output_dir}/scanner/02-scanner-06-deployment.yaml" - run -1 grep -q 'ROX_OPENSHIFT_API' "${output_dir}/scanner/02-scanner-06-deployment.yaml" - - assert_number_of_k8s_resources 12 + assert_number_of_k8s_resources 20 } -@test "[k8s scanner-image] roxctl scanner generate" { - run_scanner_generate_and_check k8s --scanner-image bats-tests +@test "[k8s scanner-v4-image] roxctl scanner generate" { + run_scanner_generate_and_check k8s --scanner-v4-image bats-tests - assert_file_exist "${output_dir}/scanner/02-scanner-06-deployment.yaml" - run -0 grep -q "bats-tests" "${output_dir}/scanner/02-scanner-06-deployment.yaml" - assert_number_of_k8s_resources 12 + assert_file_exist "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" + run -0 grep -q "bats-tests" "${output_dir}/scanner-v4/02-scanner-v4-07-indexer-deployment.yaml" + assert_number_of_k8s_resources 20 } diff --git a/tests/roxctl/bats-tests/helpers.bash b/tests/roxctl/bats-tests/helpers.bash index 19416b9678967..09b5c884b3cea 100644 --- a/tests/roxctl/bats-tests/helpers.bash +++ b/tests/roxctl/bats-tests/helpers.bash @@ -93,7 +93,7 @@ helm_template_central() { --output-dir="$out_dir/rendered" assert_success assert_output --partial "wrote $out_dir/rendered/stackrox-central-services/templates/01-central-13-deployment.yaml" - assert_output --partial "wrote $out_dir/rendered/stackrox-central-services/templates/02-scanner-06-deployment.yaml" + assert_output --partial "wrote $out_dir/rendered/stackrox-central-services/templates/02-scanner-v4-07-indexer-deployment.yaml" } # helm_template shall be used instead of `helm template --output-dir` to avoid potential overwriting of resulting manifests @@ -193,6 +193,9 @@ component_image() { admission-controller|sensor) echo "main" ;; + scanner-v4-indexer) + echo "scanner-v4" + ;; *) echo "$1" ;; @@ -219,11 +222,11 @@ assert_components_registry() { central-db) assert_registry_version_file "${dir}/01-central-12-central-db.yaml" 0 "central-db" "$regex" ;; - scanner) - assert_registry_version_file "${dir}/02-scanner-06-deployment.yaml" 0 "scanner" "$regex" + scanner-v4-indexer) + assert_registry_version_file "${dir}/02-scanner-v4-07-indexer-deployment.yaml" 0 "indexer" "$regex" ;; - scanner-db) - assert_registry_version_file "${dir}/02-scanner-06-deployment.yaml" 1 "db" "$regex" + scanner-v4-db) + assert_registry_version_file "${dir}/02-scanner-v4-07-db-deployment.yaml" 0 "db" "$regex" ;; collector) # only when generated by helm output assert_registry_version_file "${dir}/stackrox-secured-cluster-services/templates/collector.yaml" 0 "collector" "$regex" @@ -290,7 +293,7 @@ image_reference_regex() { # $1 - path to roxctl binary # $2 - orchestrator (k8s, openshift) # $3 - registry-slug for expected main registry (see 'image_reference_regex()' for the list of currently supported registry-slugs) -# $4 - registry-slug for expected scanner and scanner-db registries (see 'image_reference_regex()' for the list of currently supported registry-slugs) +# $4 - registry-slug for expected scanner-v4 registries (see 'image_reference_regex()' for the list of currently supported registry-slugs) # $@ - open-ended list of other parameters that should be passed into 'roxctl central generate' run_image_defaults_registry_test() { local roxctl_bin="$1"; shift; @@ -304,7 +307,7 @@ run_image_defaults_registry_test() { run "$roxctl_bin" central generate "$orch" "${extra_params[@]}" pvc --output-dir "$out_dir" assert_success assert_components_registry "$out_dir/central" "$expected_main_registry" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "$expected_scanner_registry" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "$expected_scanner_registry" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' assert_components_registry "$out_dir/central" "$expected_main_registry" "$any_version" 'central-db' } diff --git a/tests/roxctl/bats-tests/local/central-generate-development.bats b/tests/roxctl/bats-tests/local/central-generate-development.bats index 876b057b31ec4..ae4204c7fc345 100755 --- a/tests/roxctl/bats-tests/local/central-generate-development.bats +++ b/tests/roxctl/bats-tests/local/central-generate-development.bats @@ -33,8 +33,8 @@ teardown() { 'example.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example.com/scanner:1.2.3' \ - '--scanner-db-image' 'example.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example.com/scanner-v4-db:1.2.3' } @test "roxctl-development central generate k8s should work when main and scanner are from different registries" { @@ -43,8 +43,8 @@ teardown() { 'example2.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example2.com/scanner:1.2.3' \ - '--scanner-db-image' 'example2.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example2.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example2.com/scanner-v4-db:1.2.3' } @test "roxctl-development central generate k8s should work when main is from custom registry and --image-defaults are used" { @@ -84,8 +84,8 @@ teardown() { 'example.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example.com/scanner:1.2.3' \ - '--scanner-db-image' 'example.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example.com/scanner-v4-db:1.2.3' } @test "roxctl-development central generate openshift should work when main and scanner are from different registries" { @@ -94,8 +94,8 @@ teardown() { 'example2.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example2.com/scanner:1.2.3' \ - '--scanner-db-image' 'example2.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example2.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example2.com/scanner-v4-db:1.2.3' } @test "roxctl-development central generate openshift should work when main is from custom registry and --image-defaults are used" { diff --git a/tests/roxctl/bats-tests/local/central-generate-interactive-flavors.bats b/tests/roxctl/bats-tests/local/central-generate-interactive-flavors.bats index fb6706fbd7b3a..a67d0e7ceea0e 100755 --- a/tests/roxctl/bats-tests/local/central-generate-interactive-flavors.bats +++ b/tests/roxctl/bats-tests/local/central-generate-interactive-flavors.bats @@ -34,10 +34,10 @@ bitfield_to_failure() { msg="$msg main" fi if ((status & 2)); then - msg="$msg scanner-db" + msg="$msg scanner-v4-db" fi if ((status & 1)); then - msg="$msg scanner" + msg="$msg scanner-v4" fi fail "$msg - status $status" } @@ -52,22 +52,22 @@ assert_flavor_prompt_release() { assert_prompts_development() { # partial line matching allows to avoid problems with leading an trailing whitespaces - # main/scanner/scanner-db are constants from code + # main/scanner-v4/scanner-v4-db are constants from code assert_line --regexp '^The main .* "quay.io/rhacs-eng/main:' - assert_line --regexp '^The scanner-db .* "quay.io/rhacs-eng/scanner-db:' - assert_line --regexp '^The scanner .* "quay.io/rhacs-eng/scanner:' + assert_line --regexp '^The scanner-v4-db .* "quay.io/rhacs-eng/scanner-v4-db:' + assert_line --regexp '^The scanner-v4 .* "quay.io/rhacs-eng/scanner-v4:' } assert_prompts_opensource() { assert_line --regexp '^The main .* "quay.io/stackrox-io/main:' - assert_line --regexp '^The scanner-db .* "quay.io/stackrox-io/scanner-db:' - assert_line --regexp '^The scanner .* "quay.io/stackrox-io/scanner:' + assert_line --regexp '^The scanner-v4-db .* "quay.io/stackrox-io/scanner-v4-db:' + assert_line --regexp '^The scanner-v4 .* "quay.io/stackrox-io/scanner-v4:' } assert_prompts_rhacs() { assert_line --regexp '^The main .* "registry.redhat.io/advanced-cluster-security/rhacs-main-rhel9:' - assert_line --regexp '^The scanner-db .* "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9:' - assert_line --regexp '^The scanner .* "registry.redhat.io/advanced-cluster-security/rhacs-scanner-rhel9:' + assert_line --regexp '^The scanner-v4-db .* "registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-db-rhel9:' + assert_line --regexp '^The scanner-v4 .* "registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-rhel9:' } @test "roxctl-development central generate interactive flavor=dummy should ask for valid value" { @@ -85,7 +85,7 @@ assert_prompts_rhacs() { assert_flavor_prompt_development sleep 2 # due to frequent flakes of missing yaml files assert_components_registry "$out_dir/central" "quay.io/rhacs-eng" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "quay.io/rhacs-eng" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "quay.io/rhacs-eng" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development central generate interactive flavor=opensource" { @@ -97,7 +97,7 @@ assert_prompts_rhacs() { assert_flavor_prompt_development sleep 2 # due to frequent flakes of missing yaml files assert_components_registry "$out_dir/central" "quay.io/stackrox-io" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "quay.io/stackrox-io" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "quay.io/stackrox-io" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development central generate interactive flavor=rhacs" { @@ -109,7 +109,7 @@ assert_prompts_rhacs() { assert_flavor_prompt_development sleep 2 # due to frequent flakes of missing yaml files assert_components_registry "$out_dir/central" "registry.redhat.io" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "registry.redhat.io" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "registry.redhat.io" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' } # RELEASE @@ -129,7 +129,7 @@ assert_prompts_rhacs() { assert_flavor_prompt_release sleep 2 # due to frequent flakes of missing yaml files assert_components_registry "$out_dir/central" "quay.io/stackrox-io" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "quay.io/stackrox-io" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "quay.io/stackrox-io" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release central generate interactive flavor=rhacs" { @@ -141,5 +141,5 @@ assert_prompts_rhacs() { assert_flavor_prompt_release sleep 2 # due to frequent flakes of missing yaml files assert_components_registry "$out_dir/central" "registry.redhat.io" "$any_version" 'main' - assert_components_registry "$out_dir/scanner" "registry.redhat.io" "$any_version" 'scanner' 'scanner-db' + assert_components_registry "$out_dir/scanner-v4" "registry.redhat.io" "$any_version" 'scanner-v4-indexer' 'scanner-v4-db' } diff --git a/tests/roxctl/bats-tests/local/central-generate-release.bats b/tests/roxctl/bats-tests/local/central-generate-release.bats index fe103a6a6139a..d4d46f9097103 100755 --- a/tests/roxctl/bats-tests/local/central-generate-release.bats +++ b/tests/roxctl/bats-tests/local/central-generate-release.bats @@ -32,8 +32,8 @@ teardown() { 'example.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example.com/scanner:1.2.3' \ - '--scanner-db-image' 'example.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example.com/scanner-v4-db:1.2.3' } @test "roxctl-release central generate k8s should work when main and scanner are from different registries" { @@ -42,8 +42,8 @@ teardown() { 'example2.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example2.com/scanner:1.2.3' \ - '--scanner-db-image' 'example2.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example2.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example2.com/scanner-v4-db:1.2.3' } @test "roxctl-release central generate k8s should work when main is from custom registry and --image-defaults are used" { @@ -83,8 +83,8 @@ teardown() { 'example.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example.com/scanner:1.2.3' \ - '--scanner-db-image' 'example.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example.com/scanner-v4-db:1.2.3' } @test "roxctl-release central generate openshift should work when main and scanner are from different registries" { @@ -93,8 +93,8 @@ teardown() { 'example2.com' \ '--main-image' 'example.com/main:1.2.3' \ '--central-db-image' 'example.com/central-db:1.2.3' \ - '--scanner-image' 'example2.com/scanner:1.2.3' \ - '--scanner-db-image' 'example2.com/scanner-db:1.2.3' + '--scanner-v4-image' 'example2.com/scanner-v4:1.2.3' \ + '--scanner-v4-db-image' 'example2.com/scanner-v4-db:1.2.3' } @test "roxctl-release central generate openshift should work when main is from custom registry and --image-defaults are used" { diff --git a/tests/roxctl/bats-tests/local/expect/flavor-interactive.expect.tcl b/tests/roxctl/bats-tests/local/expect/flavor-interactive.expect.tcl index c3bf7c76f62e0..e734d7449388e 100755 --- a/tests/roxctl/bats-tests/local/expect/flavor-interactive.expect.tcl +++ b/tests/roxctl/bats-tests/local/expect/flavor-interactive.expect.tcl @@ -21,8 +21,8 @@ set registry [lindex $argv 3] # exitWith is an integer interpreted as binary field, where each bit denotes a different error # 8 - a question has not been displayed # 4 - prompt for entering main-image is missing default value hint -# 2 - prompt for entering scanner-db-image is missing default value hint -# 1 - prompt for entering scanner-image is missing default value hint +# 2 - prompt for entering scanner-v4-db-image is missing default value hint +# 1 - prompt for entering scanner-v4-image is missing default value hint # 0 - no error set exitWith 0 @@ -100,55 +100,7 @@ expect "List of config maps to add as declarative configuration*:" { send "\n" } expect "The deployment tool to use (kubectl, helm, helm-values)*:" { send "\n" } expect "Istio version when deploying into an Istio-enabled cluster*:" { send "\n" } -# The scanner-db image to use (default: "docker.io/stackrox/scanner-db:2.21.0-15-g448f2dc8fa"): -# The scanner-db image to use (default: "quay.io/stackrox-io/scanner-db:3.67.x-296-g56df6a892d"): -# The scanner-db image to use (default: "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9:3.68.x-30-g516b4e7a6c-dirty"): -expect { - default { - send_user "\nFATAL: No question about scanner-db image\n" - exit 8 - } - "The scanner-db * (if unset, the default will be used):" { - send_user "WARNING: roxctl does not suggest any registry for scanner-db" - send "\n" - set exitWith [expr {$exitWith + 2}] - } - "The scanner-db * (default: \"$registry/scanner-db:*\"):" { - send_user "roxctl suggests correct registry for scanner-db" - send "\n" - } - # Special case for RHACS to avoid writing a regexp in TCL - "The scanner-db * (default: \"$registry/rhacs-scanner-db-rhel9:*\"):" { - send_user "roxctl suggests correct registry for scanner-db" - send "\n" - } -} -# The scanner image to use (default: "docker.io/stackrox/scanner:2.21.0-15-g448f2dc8fa"): -# The scanner image to use (default: "quay.io/stackrox-io/scanner:3.67.x-296-g56df6a892d"): -expect { - default { - send_user "\nFATAL: No question about scanner image\n" - exit 8 - } - "The scanner * (if unset, the default will be used):" { - send_user "exitWith before $exitWith" - send_user "WARNING: roxctl does not suggest any registry for scanner" - send "\n" - set exitWith [expr {$exitWith + 1}] - send_user "exitWith is now $exitWith" - } - "The scanner * (default: \"$registry/scanner:*\"):" { - send_user "roxctl suggests correct registry for scanner" - send "\n" - } - # Special case for RHACS to avoid writing a regexp in TCL - "The scanner * (default: \"$registry/rhacs-scanner-rhel9:*\"):" { - send_user "roxctl suggests correct registry for scanner" - send "\n" - } -} - -# The scanner-v4-db image to use (if unset, a default will be used according to --image-defaults) (default: "quay.io/rhacs-eng/scanner-v4:4.3.x-1304-g0b0cc2d4f7"): +# The scanner-v4-db image to use (if unset, a default will be used according to --image-defaults) (default: "quay.io/rhacs-eng/scanner-v4-db:4.3.x-1304-g0b0cc2d4f7"): expect { default { send_user "\nFATAL: No question about scanner-v4-db image\n" diff --git a/tests/roxctl/bats-tests/local/roxctl-helm-output-central-development.bats b/tests/roxctl/bats-tests/local/roxctl-helm-output-central-development.bats index 2a38b8e73229b..3b44a7b116139 100755 --- a/tests/roxctl/bats-tests/local/roxctl-helm-output-central-development.bats +++ b/tests/roxctl/bats-tests/local/roxctl-helm-output-central-development.bats @@ -42,7 +42,7 @@ teardown() { run roxctl-development helm output central-services --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development helm output central-services --rhacs should use redhat.io registry and display deprecation warning" { @@ -50,7 +50,7 @@ teardown() { assert_success assert_output --partial "Written Helm chart central-services to directory" has_deprecation_warning - assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development helm output central-services --image-defaults=dummy should fail" { @@ -63,21 +63,21 @@ teardown() { run roxctl-development helm output central-services --image-defaults=rhacs --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development helm output central-services --image-defaults=development_build should use quay.io/rhacs-eng registry" { run roxctl-development helm output central-services --image-defaults=development_build --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development helm output central-services --image-defaults=opensource should use quay.io/stackrox-io registry" { run roxctl-development helm output central-services --image-defaults=opensource --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'quay.io/stackrox-io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'quay.io/stackrox-io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-development helm output central-services --rhacs --image-defaults=development_build should return error about --rhacs colliding with --image-defaults" { diff --git a/tests/roxctl/bats-tests/local/roxctl-helm-output-central-release.bats b/tests/roxctl/bats-tests/local/roxctl-helm-output-central-release.bats index cd0b995fa5804..b0dcb17d8001e 100755 --- a/tests/roxctl/bats-tests/local/roxctl-helm-output-central-release.bats +++ b/tests/roxctl/bats-tests/local/roxctl-helm-output-central-release.bats @@ -30,7 +30,7 @@ teardown() { run roxctl-release helm output central-services --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release helm output central-services --rhacs should use registry.redhat.io registry and display deprecation warning" { @@ -38,28 +38,28 @@ teardown() { assert_success assert_output --partial "Written Helm chart central-services to directory" has_deprecation_warning - assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release helm output central-services --image-defaults=rhacs should use registry.redhat.io registry" { run roxctl-release helm output central-services --image-defaults=rhacs --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'registry.redhat.io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release helm output central-services --image-defaults=development_build should use quay.io/rhacs-eng registry" { run roxctl-release helm output central-services --image-defaults=development_build --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'quay.io/rhacs-eng' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release helm output central-services --image-defaults=opensource should use quay.io/stackrox-io registry" { run roxctl-release helm output central-services --image-defaults=opensource --output-dir "$out_dir" assert_success assert_output --partial "Written Helm chart central-services to directory" - assert_helm_template_central_registry "$out_dir" 'quay.io/stackrox-io' "$any_version" 'main' 'scanner' 'scanner-db' + assert_helm_template_central_registry "$out_dir" 'quay.io/stackrox-io' "$any_version" 'main' 'scanner-v4-indexer' 'scanner-v4-db' } @test "roxctl-release helm output central-services --image-defaults='' should fail with unexpected value of --image-defaults" { diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index a7a07dd6089aa..901b33e277b25 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -5,19 +5,28 @@ set -euo pipefail # Test utility functions for upgrades +central_deployment_count() { + curl -sSk --config <(curl_cfg user "admin:$ROX_ADMIN_PASSWORD") -X POST \ + -d '{"operationName":"summary_counts","variables":{},"query":"query summary_counts { clusterCount nodeCount violationCount deploymentCount imageCount secretCount }"}' \ + "https://$API_ENDPOINT/api/graphql" | jq '.data.deploymentCount' -r +} + wait_for_central_reconciliation() { info "Waiting for central reconciliation" - # Reconciliation is rather slow in this case, since the central has a DB with a bunch of deployments, - # none of which exist. So when sensor connects, the reconciliation deletion takes a while to flush. - # This causes flakiness with the smoke tests. - # To mitigate this, wait for the deployments to get deleted before running the tests. + # Sensor reconnect flushes deployments that no longer exist on the + # cluster. Smoke flakes if that leftover count is still high. local success=0 + local i for i in $(seq 1 90); do local numDeployments - numDeployments="$(curl -sSk --config <(curl_cfg user "admin:$ROX_ADMIN_PASSWORD") -X POST -d "{\"operationName\":\"summary_counts\",\"variables\":{},\"query\":\"query summary_counts {\n clusterCount\n nodeCount\n violationCount\n deploymentCount\n imageCount\n secretCount\n}\"}" "https://$API_ENDPOINT/api/graphql" | jq '.data.deploymentCount' -r)" + numDeployments="$(central_deployment_count)" echo "Try number ${i}. Number of deployments in Central: $numDeployments" - [[ -n "$numDeployments" ]] + # GraphQL returns null while Central is still coming up; retry. + if [[ -z "$numDeployments" || "$numDeployments" == "null" ]]; then + sleep 10 + continue + fi if [[ "$numDeployments" -lt 100 ]]; then success=1 break @@ -27,29 +36,6 @@ wait_for_central_reconciliation() { [[ "$success" == 1 ]] } -wait_for_scanner_to_be_ready() { - echo "Waiting for scanner to be ready" - start_time="$(date '+%s')" - while true; do - scanner_json="$(kubectl -n stackrox get deploy/scanner -o json)" - replicas="$(jq '.status.replicas' <<<"${scanner_json}")" - readyReplicas="$(jq '.status.readyReplicas' <<<"${scanner_json}")" - echo "scanner replicas: $replicas" - echo "scanner readyReplicas: $readyReplicas" - if [[ "$replicas" == "$readyReplicas" ]]; then - break - fi - if (( $(date '+%s') - start_time > 1200 )); then - kubectl -n stackrox get pod -o wide - kubectl -n stackrox get deploy -o wide - echo >&2 "Timed out after 20m" - exit 1 - fi - sleep 5 - done - echo "Scanner is ready" -} - validate_upgrade() { if [[ "$#" -ne 3 ]]; then die "missing args. usage: validate_upgrade " @@ -156,6 +142,116 @@ deploy_earlier_postgres_central() { ci_export "ROX_ADMIN_PASSWORD" "$ROX_ADMIN_PASSWORD" } +# upgrade_central_helm_to_head applies the HEAD chart to the existing +# stackrox-central-services release so Scanner V4 is installed. kubectl set +# image does not create V4, and HEAD Central does not use leftover Scanner V2. +upgrade_central_helm_to_head() { + local namespace="${1:-stackrox}" + local image_tag="${2:-$CURRENT_TAG}" + local registry="${3:-$REGISTRY}" + + info "Upgrading central Helm release to HEAD chart with Scanner V4" + + if ! helm -n "$namespace" status stackrox-central-services >/dev/null 2>&1; then + die "Helm release stackrox-central-services not found in namespace ${namespace}" + fi + + local chart_dir + chart_dir="$(mktemp -d)" + "$TEST_ROOT/bin/$TEST_HOST_PLATFORM/roxctl" helm output central-services \ + --image-defaults opensource \ + --output-dir "${chart_dir}" --remove + + # Retrieve the generated-values secret from installation time. + # The helm chart needs the CA stored there to sign the V4 TLS certs. + local helm_generated_values_file + helm_generated_values_file="$(mktemp)" + local generated_secrets_json + generated_secrets_json="$(kubectl -n "$namespace" get secrets -o json | jq '[.items[] + | select(.metadata.name | startswith("stackrox-generated-")) + | select(.data["generated-values.yaml"] != null)]')" + local generated_secret_count + generated_secret_count="$(jq 'length' <<<"$generated_secrets_json")" + if [[ "$generated_secret_count" -lt 1 ]]; then + die "Expected a Helm generated-values secret in ${namespace}, found 0" + fi + # Helm pre-upgrade hooks use resource-policy=keep, so extra generated-values + # secrets can exist. V4 certs must use the install-time CA in the oldest. + local helm_generated_secret_name + helm_generated_secret_name="$(jq -r 'sort_by(.metadata.creationTimestamp)[0].metadata.name' <<<"$generated_secrets_json")" + if [[ "$generated_secret_count" -gt 1 ]]; then + info "Found ${generated_secret_count} generated-values secrets; using install-time ${helm_generated_secret_name}" + fi + jq -r --arg name "$helm_generated_secret_name" \ + '.[] | select(.metadata.name == $name) | .data["generated-values.yaml"] | @base64d' \ + <<<"$generated_secrets_json" > "$helm_generated_values_file" + if [[ ! -s "$helm_generated_values_file" ]]; then + die "Helm generated-values secret ${helm_generated_secret_name} was empty" + fi + + # 4.6 installs this CRD outside Helm ownership; HEAD templates it. Helm + # refuses the upgrade until the existing CRD is adopted. + if kubectl get crd securitypolicies.config.stackrox.io >/dev/null 2>&1; then + kubectl annotate crd/securitypolicies.config.stackrox.io \ + meta.helm.sh/release-name=stackrox-central-services \ + meta.helm.sh/release-namespace="${namespace}" \ + --overwrite + kubectl label crd/securitypolicies.config.stackrox.io \ + app.kubernetes.io/managed-by=Helm \ + --overwrite + fi + + local helm_extra_args=() + if [[ -n "${SCANNER_V4_DB_STORAGE_CLASS:-}" ]]; then + if [[ "${SCANNER_V4_DB_STORAGE_CLASS}" == "faster" ]]; then + kubectl apply -f "${TEST_ROOT}/deploy/common/ssd-storageclass.yaml" + fi + helm_extra_args+=(--set "scannerV4.db.persistence.persistentVolumeClaim.storageClass=${SCANNER_V4_DB_STORAGE_CLASS}") + fi + # The image walk uses kubectl patch/set, so Helm 4 SSA will not overwrite + # those fields (central-config, CPU/memory) without --force-conflicts. + if helm upgrade --help 2>&1 | grep -q -- '--force-conflicts'; then + helm_extra_args+=(--force-conflicts) + fi + + # The chart emits the Scanner V4 DB PVC only when createClaim is true; + # this is an upgrade so .Release.IsInstall is false. + # SCANNER_V4_MATCHER_READINESS=vulnerability keeps matcher unready until + # the vuln DB is loaded, which wait_for_scanner_V4 then waits on. + helm -n "$namespace" upgrade --reuse-values \ + -f "$helm_generated_values_file" \ + -f - \ + "${helm_extra_args[@]}" \ + stackrox-central-services "${chart_dir}" < { cy.get('input[aria-label="Select value 1 of 1 for the namespace name"]').type('stackrox'); // Test that Stackrox deployments are matched - assertDeploymentsAreMatched(['central', 'central-db', 'collector', 'scanner', 'sensor']); + assertDeploymentsAreMatched([ + 'central', + 'central-db', + 'collector', + 'scanner-v4-indexer', + 'scanner-v4-matcher', + 'scanner-v4-db', + 'sensor', + ]); // Restrict collection to two specific deployments cy.get('button:contains("No deployments specified")').click(); diff --git a/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.helpers.js b/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.helpers.js index 1d8f11767f48d..e3bc6efbb1d59 100644 --- a/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.helpers.js +++ b/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.helpers.js @@ -53,12 +53,12 @@ export function visitSystemConfigurationWithCentralCredentialExpiryBanner( ); } -export function visitSystemConfigurationWithScannerCredentialExpiryBanner( +export function visitSystemConfigurationWithScannerV4CredentialExpiryBanner( expiry, staticResponseForPermissions ) { visitSystemConfigurationWithCredentialExpiryBanner( - 'SCANNER', + 'SCANNER_V4', expiry, staticResponseForPermissions ); @@ -83,6 +83,6 @@ export function interactAndWaitForCentralCertificateDownload(interactionCallback interactAndWaitForCertificateDownload('central', interactionCallback); } -export function interactAndWaitForScannerCertificateDownload(interactionCallback) { - interactAndWaitForCertificateDownload('scanner', interactionCallback); +export function interactAndWaitForScannerV4CertificateDownload(interactionCallback) { + interactAndWaitForCertificateDownload('scanner?v=4', interactionCallback); } diff --git a/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.test.js b/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.test.js index 2b63b528077bc..8f227ea4f423f 100644 --- a/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.test.js +++ b/ui/apps/platform/cypress/integration/credentialExpiry/credentialExpiry.test.js @@ -4,13 +4,13 @@ import withAuth from '../../helpers/basicAuth'; import { interactAndWaitForCentralCertificateDownload, - interactAndWaitForScannerCertificateDownload, + interactAndWaitForScannerV4CertificateDownload, visitSystemConfigurationWithCentralCredentialExpiryBanner, - visitSystemConfigurationWithScannerCredentialExpiryBanner, + visitSystemConfigurationWithScannerV4CredentialExpiryBanner, } from './credentialExpiry.helpers'; const centralCredentialExpiryBanner = '.pf-v6-c-banner:contains("Central certificate")'; -const scannerCredentialExpiryBanner = '.pf-v6-c-banner:contains("Scanner certificate")'; +const scannerV4CredentialExpiryBanner = '.pf-v6-c-banner:contains("Scanner V4 certificate")'; describe('Credential expiry', () => { withAuth(); @@ -76,16 +76,16 @@ describe('Credential expiry', () => { }); }); - describe('for scanner', () => { - it('should not display banner if scanner cert is expiring more than 14 days later', () => { + describe('for scanner v4', () => { + it('should not display banner if scanner v4 cert is expiring more than 14 days later', () => { const expiry = dateFns.addHours(dateFns.addDays(new Date(), 15), 1); - visitSystemConfigurationWithScannerCredentialExpiryBanner(expiry); + visitSystemConfigurationWithScannerV4CredentialExpiryBanner(expiry); - cy.get(centralCredentialExpiryBanner).should('not.exist'); + cy.get(scannerV4CredentialExpiryBanner).should('not.exist'); }); - it("should display banner without download button if user doesn't have the required permission", () => { + it('should display banner without download button if user does not have the required permission', () => { const expiry = dateFns.addMinutes(dateFns.addHours(new Date(), 23), 30); cy.fixture('auth/mypermissionsMinimalAccess.json').then(({ resourceToAccess }) => { @@ -95,44 +95,44 @@ describe('Credential expiry', () => { }, }; - visitSystemConfigurationWithScannerCredentialExpiryBanner( + visitSystemConfigurationWithScannerV4CredentialExpiryBanner( expiry, staticResponseForPermissions ); - cy.get(scannerCredentialExpiryBanner) + cy.get(scannerV4CredentialExpiryBanner) .invoke('text') .then((text) => { - expect(text).to.include('Scanner certificate expires in 23 hours'); + expect(text).to.include('Scanner V4 certificate expires in 23 hours'); expect(text).to.include('Contact your administrator'); }); - cy.get(scannerCredentialExpiryBanner).find('button').should('not.exist'); + cy.get(scannerV4CredentialExpiryBanner).find('button').should('not.exist'); }); }); it('should show a warning banner if the expiry date is within 4-14 days', () => { const expiry = dateFns.addDays(new Date(), 10); - visitSystemConfigurationWithScannerCredentialExpiryBanner(expiry); + visitSystemConfigurationWithScannerV4CredentialExpiryBanner(expiry); - cy.get(scannerCredentialExpiryBanner).should('have.class', 'pf-m-yellow'); + cy.get(scannerV4CredentialExpiryBanner).should('have.class', 'pf-m-yellow'); }); - it('should show a danger banner if the expiry date is greater than 14 days', () => { + it('should show a danger banner if the expiry date is less than or equal to 3 days', () => { const expiry = dateFns.addDays(new Date(), 2); - visitSystemConfigurationWithScannerCredentialExpiryBanner(expiry); + visitSystemConfigurationWithScannerV4CredentialExpiryBanner(expiry); - cy.get(scannerCredentialExpiryBanner).should('have.class', 'pf-m-red'); + cy.get(scannerV4CredentialExpiryBanner).should('have.class', 'pf-m-red'); }); it('should download the YAML', () => { const expiry = dateFns.addDays(new Date(), 1); - visitSystemConfigurationWithScannerCredentialExpiryBanner(expiry); + visitSystemConfigurationWithScannerV4CredentialExpiryBanner(expiry); - interactAndWaitForScannerCertificateDownload(() => { - cy.get(scannerCredentialExpiryBanner).find('button').click(); + interactAndWaitForScannerV4CertificateDownload(() => { + cy.get(scannerV4CredentialExpiryBanner).find('button').click(); }); }); }); diff --git a/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts b/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts deleted file mode 100644 index dab6f1c1e4ad1..0000000000000 --- a/ui/apps/platform/cypress/integration/legacyScannerFeatureFlag.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import dateFns from 'date-fns'; - -import withAuth from '../helpers/basicAuth'; -import { interceptAndOverrideFeatureFlags } from '../helpers/request'; -import { assertCannotFindThePage, visit } from '../helpers/visit'; -import { - credentialForScannerExpiryAlias, - integrationHealthVulnDefinitionsAlias, - setClock, - visitSystemHealth, - visitSystemHealthWithKeysRemoved, -} from '../helpers/systemHealth'; -import { visitIntegrationsDashboard } from './integrations/integrations.helpers'; -import { visitSystemConfigurationWithScannerCredentialExpiryBanner } from './credentialExpiry/credentialExpiry.helpers'; -import { visitSystemConfiguration } from './systemConfig/systemConfig.helpers'; -import { visitNodeCveOverviewPage } from './vulnerabilities/nodeCves/NodeCve.helpers'; - -const vulnDefinitionsCardSelector = - '.pf-v6-c-card:contains("StackRox Scanner Vulnerability Definitions")'; -const scannerCertificateCardSelector = '.pf-v6-c-card:contains("Scanner certificate")'; -const scannerCredentialExpiryBanner = '.pf-v6-c-banner:contains("Scanner certificate")'; -const stackRoxScannerTileSelector = '[data-testid="integration-tile"]:contains("StackRox Scanner")'; -const nodeCveScannerInfoBoxSelector = '.pf-v6-c-alert:contains("StackRox Scanner")'; - -describe('Legacy Scanner feature flag (ROX_LEGACY_SCANNER)', () => { - withAuth(); - - describe('when disabled', () => { - beforeEach(() => { - interceptAndOverrideFeatureFlags({ ROX_LEGACY_SCANNER: false }); - }); - - it('should hide StackRox Scanner tile on Integrations page', () => { - visitIntegrationsDashboard(); - - cy.get(stackRoxScannerTileSelector).should('not.exist'); - }); - - it('should hide scanner comparison info box on Node CVEs page', () => { - interceptAndOverrideFeatureFlags({ - ROX_LEGACY_SCANNER: false, - ROX_SCANNER_V4: true, - ROX_NODE_INDEX_ENABLED: true, - }); - - visitNodeCveOverviewPage(); - - cy.get(nodeCveScannerInfoBoxSelector).should('not.exist'); - }); - - it('should hide scanner credential expiry banner', () => { - visitSystemConfiguration(); - - cy.get(scannerCredentialExpiryBanner).should('not.exist'); - }); - - it('should not show vuln definitions card on System Health page', () => { - visitSystemHealthWithKeysRemoved([ - credentialForScannerExpiryAlias, - integrationHealthVulnDefinitionsAlias, - ]); - - cy.get(vulnDefinitionsCardSelector).should('not.exist'); - }); - - it('should not show scanner certificate card on System Health page', () => { - visitSystemHealthWithKeysRemoved([ - credentialForScannerExpiryAlias, - integrationHealthVulnDefinitionsAlias, - ]); - - cy.get(scannerCertificateCardSelector).should('not.exist'); - }); - - it('should show generic 404 page for Platform CVEs', () => { - visit('/main/vulnerabilities/platform-cves'); - - assertCannotFindThePage(); - cy.get('a:contains("Go to Vulnerability Management")').should('not.exist'); - }); - }); - - describe('when enabled', () => { - beforeEach(() => { - interceptAndOverrideFeatureFlags({ ROX_LEGACY_SCANNER: true }); - }); - - it('should show active vuln definitions card on System Health page', () => { - const currentDatetime = new Date('2020-12-10T02:04:59.377369440Z'); - const lastUpdatedTimestamp = '2020-12-09T03:04:59.377369440Z'; - - const staticResponseMap = { - [integrationHealthVulnDefinitionsAlias]: { - body: { lastUpdatedTimestamp }, - }, - }; - - setClock(currentDatetime); - visitSystemHealth(staticResponseMap); - - cy.get(vulnDefinitionsCardSelector).should('exist'); - cy.get(vulnDefinitionsCardSelector).should('not.have.css', 'opacity', '0.5'); - cy.get(vulnDefinitionsCardSelector).should('contain', 'up to date'); - }); - - it('should show active scanner certificate card on System Health page', () => { - visitSystemHealth(); - - cy.get(scannerCertificateCardSelector).should('exist'); - cy.get(scannerCertificateCardSelector).should('not.have.css', 'opacity', '0.5'); - }); - - it('should show StackRox Scanner tile on Integrations page', () => { - visitIntegrationsDashboard(); - - cy.get(stackRoxScannerTileSelector).should('exist'); - }); - - it('should show scanner credential expiry banner', () => { - const expiry = dateFns.addDays(new Date(), 2); - - visitSystemConfigurationWithScannerCredentialExpiryBanner(expiry); - - cy.get(scannerCredentialExpiryBanner).should('exist'); - }); - }); -}); diff --git a/ui/apps/platform/cypress/integration/networkGraph/networkDeploymentSidebar.test.js b/ui/apps/platform/cypress/integration/networkGraph/networkDeploymentSidebar.test.js index c33dbf2c1082e..55d2a9d346116 100644 --- a/ui/apps/platform/cypress/integration/networkGraph/networkDeploymentSidebar.test.js +++ b/ui/apps/platform/cypress/integration/networkGraph/networkDeploymentSidebar.test.js @@ -33,7 +33,7 @@ describe('Network Graph deployment sidebar', () => { // `${networkGraphSelectors.nodes} > [data-type="node"] .pf-topology__node__label:contains("central")` // ).should('not.exist'); cy.get( - `${networkGraphSelectors.nodes} > [data-type="node"] .pf-topology__node__label:contains("scanner")` + `${networkGraphSelectors.nodes} > [data-type="node"] .pf-topology__node__label:contains("scanner-v4-indexer")` ).should('not.exist'); cy.get( `${networkGraphSelectors.nodes} > [data-type="node"] .pf-topology__node__label:contains("admission-controller")` diff --git a/ui/apps/platform/cypress/integration/networkGraph/networkGraph.test.js b/ui/apps/platform/cypress/integration/networkGraph/networkGraph.test.js index 58cec4d0ac364..1796e9c8218ec 100644 --- a/ui/apps/platform/cypress/integration/networkGraph/networkGraph.test.js +++ b/ui/apps/platform/cypress/integration/networkGraph/networkGraph.test.js @@ -105,28 +105,37 @@ describe('Network Graph smoke tests', () => { // Verify that 'stackrox' namespace is present cy.get(networkGraphSelectors.filteredNamespaceGroupNode('stackrox')); - // Verify that central, central-db, scanner, scanner-db, sensor are present - ['central', 'central-db', 'scanner', 'scanner-db', 'sensor'].forEach((deployment) => { + // Verify that central, central-db, scanner-v4-indexer, scanner-v4-matcher, scanner-v4-db, sensor are present + [ + 'central', + 'central-db', + 'scanner-v4-indexer', + 'scanner-v4-matcher', + 'scanner-v4-db', + 'sensor', + ].forEach((deployment) => { cy.get(networkGraphSelectors.deploymentNode(deployment)); }); // Apply a deployment filter for 'central-db' selectDeployment('central-db'); - // Verify that central, central-db are present and that scanner, scanner-db, sensor are not present + // Verify that central, central-db are present and that scanner-v4-indexer, scanner-v4-matcher, scanner-v4-db, sensor are not present ['central', 'central-db'].forEach((deployment) => { cy.get(networkGraphSelectors.deploymentNode(deployment)); }); - ['scanner', 'scanner-db', 'sensor'].forEach((deployment) => { - cy.get(networkGraphSelectors.deploymentNode(deployment)).should('not.exist'); - }); + ['scanner-v4-indexer', 'scanner-v4-matcher', 'scanner-v4-db', 'sensor'].forEach( + (deployment) => { + cy.get(networkGraphSelectors.deploymentNode(deployment)).should('not.exist'); + } + ); // Remove the central-db selection from the scope filter selectDeployment('central-db'); - // Apply a general filter of "Deployment Label" for 'app=scanner-db' - selectFilter('Deployment Label', 'app=scanner-db'); + // Apply a general filter of "Deployment Label" for 'app=scanner-v4-db' + selectFilter('Deployment Label', 'app=scanner-v4-db'); - ['scanner', 'scanner-db'].forEach((deployment) => { + ['scanner-v4-indexer', 'scanner-v4-matcher', 'scanner-v4-db'].forEach((deployment) => { cy.get(networkGraphSelectors.deploymentNode(deployment)); }); ['central', 'central-db', 'sensor'].forEach((deployment) => { diff --git a/ui/apps/platform/cypress/integration/risk/risk.test.js b/ui/apps/platform/cypress/integration/risk/risk.test.js index cd2b730c7a13d..a39e345fe897f 100644 --- a/ui/apps/platform/cypress/integration/risk/risk.test.js +++ b/ui/apps/platform/cypress/integration/risk/risk.test.js @@ -126,7 +126,7 @@ describe('Risk', () => { const nsOption = 'Namespace'; const nsValue = 'stackrox'; const deployOption = 'Deployment'; - const deployValue = 'scanner'; + const deployValue = 'scanner-v4-indexer'; visitRiskDeploymentsWithSearchQuery( 'Platform view', diff --git a/ui/apps/platform/cypress/integration/systemHealth/certificateHealthCards.test.js b/ui/apps/platform/cypress/integration/systemHealth/certificateHealthCards.test.js index e43c7dfb2a702..08754d9e20687 100644 --- a/ui/apps/platform/cypress/integration/systemHealth/certificateHealthCards.test.js +++ b/ui/apps/platform/cypress/integration/systemHealth/certificateHealthCards.test.js @@ -2,7 +2,7 @@ import withAuth from '../../helpers/basicAuth'; import { credentialForCentralDbExpiryAlias, credentialForCentralExpiryAlias, - credentialForScannerExpiryAlias, + credentialForScannerV4ExpiryAlias, setClock, visitSystemHealth, } from '../../helpers/systemHealth'; @@ -10,8 +10,7 @@ import { const statusSelectorCentral = 'div:has(.pf-v6-c-card__header:contains("Central certificate"))'; const statusSelectorCentralDb = 'div:has(.pf-v6-c-card__header:contains("Central Database certificate"))'; -const statusSelectorScanner = - 'div:has(.pf-v6-c-card__header:contains("StackRox Scanner certificate"))'; +const statusSelectorScannerV4 = 'div:has(.pf-v6-c-card__header:contains("Scanner V4 certificate"))'; describe('System Health Certificate Health Cards', () => { withAuth(); @@ -104,19 +103,19 @@ describe('System Health Certificate Health Cards', () => { }); }); - describe('StackRox Scanner certificate', () => { + describe('Scanner V4 certificate', () => { it('should have widget and up to date text', () => { const currentDatetime = new Date('2025-05-21T02:04:59.377369440Z'); // about 355 days until expiry const expiry = '2026-05-20T03:04:59.377369440Z'; const staticResponseMap = { - [credentialForScannerExpiryAlias]: { body: { expiry } }, + [credentialForScannerV4ExpiryAlias]: { body: { expiry } }, }; setClock(currentDatetime); // call before visit visitSystemHealth(staticResponseMap); - cy.get(`${statusSelectorScanner}:contains("expires in 12 months")`); + cy.get(`${statusSelectorScannerV4}:contains("expires in 12 months")`); }); it('should have widget with warning text for less than 2 weeks', () => { @@ -124,13 +123,13 @@ describe('System Health Certificate Health Cards', () => { const expiry = '2026-05-20T03:04:59.377369440Z'; const staticResponseMap = { - [credentialForScannerExpiryAlias]: { body: { expiry } }, + [credentialForScannerV4ExpiryAlias]: { body: { expiry } }, }; setClock(currentDatetime); // call before visit visitSystemHealth(staticResponseMap); - cy.get(`${statusSelectorScanner}:contains("expires in 13 days")`); + cy.get(`${statusSelectorScannerV4}:contains("expires in 13 days")`); }); it('should have widget with error text for less than 24 hours', () => { @@ -138,13 +137,13 @@ describe('System Health Certificate Health Cards', () => { const expiry = '2026-05-20T03:04:59.377369440Z'; const staticResponseMap = { - [credentialForScannerExpiryAlias]: { body: { expiry } }, + [credentialForScannerV4ExpiryAlias]: { body: { expiry } }, }; setClock(currentDatetime); // call before visit visitSystemHealth(staticResponseMap); - cy.get(`${statusSelectorScanner}:contains("expires in 1 day")`); + cy.get(`${statusSelectorScannerV4}:contains("expires in 1 day")`); }); }); }); diff --git a/ui/apps/platform/cypress/integration/systemHealth/vulnDefinitions.test.js b/ui/apps/platform/cypress/integration/systemHealth/vulnDefinitions.test.js index a885f8f521dc5..13e9556fa46cf 100644 --- a/ui/apps/platform/cypress/integration/systemHealth/vulnDefinitions.test.js +++ b/ui/apps/platform/cypress/integration/systemHealth/vulnDefinitions.test.js @@ -5,8 +5,7 @@ import { visitSystemHealth, } from '../../helpers/systemHealth'; -const statusSelector = - 'div:has(.pf-v6-c-card__header:contains("StackRox Scanner Vulnerability Definitions"))'; +const statusSelector = 'div:has(.pf-v6-c-card__header:contains("Scanner V4 Vulnerabilities"))'; describe('System Health Vulnerability Definitions', () => { withAuth(); diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/VulnerabilityManagement.helpers.js b/ui/apps/platform/cypress/integration/vulnmanagement/VulnerabilityManagement.helpers.js index 4ee5ab5f4fc3a..a9a14f7428e40 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/VulnerabilityManagement.helpers.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/VulnerabilityManagement.helpers.js @@ -10,7 +10,6 @@ const opnamesForDashboard = [ 'topRiskiestImageVulns', 'recentlyDetectedImageVulnerabilities', 'mostCommonImageVulnerabilities', - 'clustersWithMostClusterVulnerabilities', ]; const routeMatcherMapForVulnerabilityManagementDashboard = diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js index a5759effc87ed..940951578ef06 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/clusters.test.js @@ -1,4 +1,5 @@ import withAuth from '../../helpers/basicAuth'; +import { hasFeatureFlag } from '../../helpers/features'; import { assertSortedItems, callbackForPairOfAscendingNumberValuesFromElements, @@ -25,7 +26,6 @@ describe('Vulnerability Management Clusters', () => { 'Cluster', 'Image CVEs', 'Node CVEs', - 'Platform CVEs', 'K8S Version', 'Entities', 'Latest Violation', @@ -37,7 +37,8 @@ describe('Vulnerability Management Clusters', () => { visitVulnerabilityManagementEntities(entitiesKey); const thSelector = '.rt-th:contains("Risk Priority")'; - const tdSelector = '.rt-td:nth-child(9)'; + const columnIndex = hasFeatureFlag('ROX_LEGACY_SCANNER') ? 9 : 8; + const tdSelector = `.rt-td:nth-child(${columnIndex})`; // 0. Initial table state indicates that the column is sorted ascending. cy.get(thSelector).should('have.class', '-sort-asc'); @@ -82,14 +83,17 @@ describe('Vulnerability Management Clusters', () => { }); it('should display links for namespaces', () => { - verifySecondaryEntities(entitiesKey, 'namespaces', 7); + const columnIndex = hasFeatureFlag('ROX_LEGACY_SCANNER') ? 7 : 6; + verifySecondaryEntities(entitiesKey, 'namespaces', columnIndex); }); it('should display links for deployments', () => { - verifySecondaryEntities(entitiesKey, 'deployments', 7); + const columnIndex = hasFeatureFlag('ROX_LEGACY_SCANNER') ? 7 : 6; + verifySecondaryEntities(entitiesKey, 'deployments', columnIndex); }); it('should display links for nodes', () => { - verifySecondaryEntities(entitiesKey, 'nodes', 7); + const columnIndex = hasFeatureFlag('ROX_LEGACY_SCANNER') ? 7 : 6; + verifySecondaryEntities(entitiesKey, 'nodes', columnIndex); }); }); diff --git a/ui/apps/platform/cypress/integration/vulnmanagement/nodeCves.test.js b/ui/apps/platform/cypress/integration/vulnmanagement/nodeCves.test.js index fe373a67fcfc9..3ceba3c6c9890 100644 --- a/ui/apps/platform/cypress/integration/vulnmanagement/nodeCves.test.js +++ b/ui/apps/platform/cypress/integration/vulnmanagement/nodeCves.test.js @@ -1,27 +1,29 @@ import { selectors } from './VulnerabilityManagement.selectors'; import withAuth from '../../helpers/basicAuth'; -import { hasOrchestratorFlavor } from '../../helpers/features'; import { - assertSortedItems, - callbackForPairOfAscendingNumberValuesFromElements, - callbackForPairOfAscendingVulnerabilitySeverityValuesFromElements, - callbackForPairOfDescendingNumberValuesFromElements, - callbackForPairOfDescendingVulnerabilitySeverityValuesFromElements, -} from '../../helpers/sort'; -import { - hasTableColumnHeadings, - interactAndWaitForVulnerabilityManagementEntities, - verifySecondaryEntities, - visitVulnerabilityManagementEntities, -} from './VulnerabilityManagement.helpers'; - -const entitiesKey = 'node-cves'; + expectRequestedSort, + getRouteMatcherMapForGraphQL, + interceptAndWatchRequests, +} from '../../helpers/request'; +import { visit } from '../../helpers/visit'; +import { hasTableColumnHeadings } from './VulnerabilityManagement.helpers'; + +const nodeCvesFixturePath = 'vulnmanagement/nodeCves.json'; + +function visitNodeCvesWithMockedData() { + const routeMatcherMap = getRouteMatcherMapForGraphQL(['searchOptions', 'getNodeCves']); + const staticResponseMap = { + getNodeCves: { fixture: nodeCvesFixturePath }, + }; + visit('/main/vulnerability-management/node-cves', routeMatcherMap, staticResponseMap); + cy.get('h1:contains("Node CVEs")'); +} describe('Vulnerability Management Node CVEs', () => { withAuth(); it('should display table columns', () => { - visitVulnerabilityManagementEntities(entitiesKey); + visitNodeCvesWithMockedData(); hasTableColumnHeadings([ '', // checkbox @@ -40,118 +42,56 @@ describe('Vulnerability Management Node CVEs', () => { ]); }); - it('should sort the CVSS Score column', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - visitVulnerabilityManagementEntities(entitiesKey); + it('should sort the CVSS Score column', () => { + visitNodeCvesWithMockedData(); const thSelector = '.rt-th:contains("CVSS Score")'; - const tdSelector = '.rt-td:nth-child(7) [data-testid="label-chip"]'; - - // 0. Initial table state indicates that the column is sorted descending. - cy.get(thSelector).should('have.class', '-sort-desc'); - cy.get(tdSelector).then((items) => { - assertSortedItems(items, callbackForPairOfDescendingNumberValuesFromElements); - }); - - // 1. Sort ascending by the column. - interactAndWaitForVulnerabilityManagementEntities(() => { - cy.get(thSelector).click(); - }, entitiesKey); - cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=false'); - - cy.get(thSelector).should('have.class', '-sort-asc'); - cy.get(tdSelector).then((items) => { - assertSortedItems(items, callbackForPairOfAscendingNumberValuesFromElements); - }); - - // 2. Sort descending by the column. - cy.get(thSelector).click(); // no request because initial response has been cached - cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=true'); cy.get(thSelector).should('have.class', '-sort-desc'); - // Do not assert because of potential timing problem: get td elements before table re-renders. - }); - // TODO Investigate whether not yet supported or incorrect field in payload. - it.skip('should sort the Severity column', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - visitVulnerabilityManagementEntities(entitiesKey); - - const thSelector = '.rt-th:contains("Severity")'; - const tdSelector = '.rt-td:nth-child(6)'; - - // 0. Initial table state indicates that the column is not sorted. - cy.get(thSelector) - .should('not.have.class', '-sort-asc') - .should('not.have.class', '-sort-desc'); - - // 1. Sort ascending by the column. - interactAndWaitForVulnerabilityManagementEntities(() => { - cy.get(thSelector).click(); - }, entitiesKey); - cy.location('search').should('eq', '?sort[0][id]=Severity&sort[0][desc]=false'); - - cy.get(thSelector).should('have.class', '-sort-asc'); - cy.get(tdSelector).then((items) => { - assertSortedItems( - items, - callbackForPairOfAscendingVulnerabilitySeverityValuesFromElements - ); - }); - - // 2. Sort descending by the column. - interactAndWaitForVulnerabilityManagementEntities(() => { - cy.get(thSelector).click(); - }, entitiesKey); - cy.location('search').should('eq', '?sort[0][id]=Severity&sort[0][desc]=true'); + const routeMatcherMap = getRouteMatcherMapForGraphQL(['getNodeCves']); + const staticResponseMap = { + getNodeCves: { fixture: nodeCvesFixturePath }, + }; + interceptAndWatchRequests(routeMatcherMap, staticResponseMap).then( + ({ waitAndYieldRequestBodyVariables }) => { + cy.get(thSelector).click(); + cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=false'); + cy.get(thSelector).should('have.class', '-sort-asc'); + + waitAndYieldRequestBodyVariables().then( + expectRequestedSort({ field: 'CVSS', reversed: false }) + ); + } + ); + cy.get(thSelector).click(); + cy.location('search').should('eq', '?sort[0][id]=CVSS&sort[0][desc]=true'); cy.get(thSelector).should('have.class', '-sort-desc'); - cy.get(tdSelector).then((items) => { - assertSortedItems( - items, - callbackForPairOfDescendingVulnerabilitySeverityValuesFromElements - ); - }); }); - it('should display vulnerability descriptions', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } + it('should display vulnerability descriptions', () => { + visitNodeCvesWithMockedData(); - visitVulnerabilityManagementEntities(entitiesKey); - - // Balance positive and negative assertions. cy.get(selectors.cveDescription).should('exist'); cy.get(`${selectors.cveDescription}:contains("No description available")`).should( 'not.exist' ); }); - // Argument 3 in verify functions is index of column which has the links. - // The one-based index includes checkbox, hidden, invisible. - - // Some tests might fail in local deployment. - - it('should display links for nodes', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } + it('should display links for nodes', () => { + visitNodeCvesWithMockedData(); - verifySecondaryEntities(entitiesKey, 'nodes', 10); + cy.get('.rt-tbody .rt-td') + .contains('a', /^\d+ nodes?$/) + .should('exist'); }); - it('should display links for node-components', function () { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } + it('should display links for node-components', () => { + visitNodeCvesWithMockedData(); - verifySecondaryEntities(entitiesKey, 'node-components', 10); + cy.get('.rt-tbody .rt-td') + .contains('a', /^\d+ node components?$/) + .should('exist'); }); }); From e131c7b7635bd0436a8c5ea4ba9f36504581f5e2 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 03:58:50 +0000 Subject: [PATCH 294/638] Refresh RPM lockfiles (#22727) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 200 ++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index fb563b41e14b1..c282d700c4b1c 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.46.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2879929 - checksum: sha256:62e7fb8318b2c8d630c67139f09325dbc2bed069ff0b80c0b279396aa85ed1f1 + size: 2882381 + checksum: sha256:c99d6e379176e2617942bbb1b429f58377a74930af88ed24e9d85f3d53d75fb0 name: kernel-headers - evr: 5.14.0-687.45.1.el9_8 - sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm + evr: 5.14.0-687.46.1.el9_8 + sourcerpm: kernel-5.14.0-687.46.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -242,20 +242,20 @@ arches: name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1177900 - checksum: sha256:ee2de9f5fbc5e83bad920a353c45d3446997aa0e4023436528b2d6f817bd6f50 + size: 1175876 + checksum: sha256:22827aedd764c1ab706086965859e7f8fef0d719ac0b3d8735f6b8e53b0a13e9 name: coreutils - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.aarch64.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 2114764 - checksum: sha256:cd79ca65bba152b9ccadcb9780ec78e3d06ce538b693078fd14459d038ab5c0c + size: 2115646 + checksum: sha256:b449955249a6d2da7369522a5ac2759919d36633e129ba88924057814906d5f5 name: coreutils-common - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 102026 @@ -1154,12 +1154,12 @@ arches: checksum: sha256:b2618b278f5c8d6dacfae790c8c73b1fc1578b8f64011f325ced5a4a2e3b58bc name: chkconfig evr: 1.24-2.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.1.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 5696745 - checksum: sha256:9129bd65888eb0b6d802d8fe912609726af5d1f6347df8a18b243c426dcbb4db + size: 5697510 + checksum: sha256:5852781d2fbb2c447c4425960f4fe30310870a0e4073363d2254047b785b5a9e name: coreutils - evr: 8.32-41.el9_8 + evr: 8.32-41.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/cracklib-2.9.6-28.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 6416053 @@ -1286,12 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.46.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152427730 - checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + size: 152431117 + checksum: sha256:378c1016ec891505f10477bf4f32f695033e3d4a94e7366d457bc0b076c826fd name: kernel - evr: 5.14.0-687.45.1.el9_8 + evr: 5.14.0-687.46.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0e9a5114665a991528e7b5b7fc6df6f4e56c465bc1f69fa94d84dbbbb13a05fb-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/4bc3c61db4611b362edc13ee5c445b4ca84c2bfcef77e623494428ab15132ae1-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms size: 65894 - checksum: sha256:0e9a5114665a991528e7b5b7fc6df6f4e56c465bc1f69fa94d84dbbbb13a05fb + checksum: sha256:4bc3c61db4611b362edc13ee5c445b4ca84c2bfcef77e623494428ab15132ae1 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1711,13 +1711,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.46.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2903733 - checksum: sha256:aabe7d848f91d233740825606a5773e8b5c85fb97a5caae0ac53434afc161788 + size: 2906129 + checksum: sha256:dd1c1221252a44f184e44ab4a4bbe82743e4970169cfc3405f44d4a7d0f54232 name: kernel-headers - evr: 5.14.0-687.45.1.el9_8 - sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm + evr: 5.14.0-687.46.1.el9_8 + sourcerpm: kernel-5.14.0-687.46.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -1879,20 +1879,20 @@ arches: name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/c/coreutils-8.32-41.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 1259870 - checksum: sha256:d9fc3be701da7abe502bc5f9d07f93e13088de2e78cf0de8bcf5fb8c0fc7e737 + size: 1262204 + checksum: sha256:195fa8b9b1eee88c69323de32c1fadc6dd9af2dfe7df10a2a04ee796b5248767 name: coreutils - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.ppc64le.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 2115089 - checksum: sha256:a242aae86238374731d5d52f23962a6af31b5ed51ce5cffaeee62e05046ed2ce + size: 2113363 + checksum: sha256:a9006eb589f6f6dfd468ae1f2c8d3ea9874b86238f462b21f75a12a5c298f7b1 name: coreutils-common - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/c/cracklib-2.9.6-28.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 104055 @@ -2798,12 +2798,12 @@ arches: checksum: sha256:b2618b278f5c8d6dacfae790c8c73b1fc1578b8f64011f325ced5a4a2e3b58bc name: chkconfig evr: 1.24-2.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.1.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 5696745 - checksum: sha256:9129bd65888eb0b6d802d8fe912609726af5d1f6347df8a18b243c426dcbb4db + size: 5697510 + checksum: sha256:5852781d2fbb2c447c4425960f4fe30310870a0e4073363d2254047b785b5a9e name: coreutils - evr: 8.32-41.el9_8 + evr: 8.32-41.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/cracklib-2.9.6-28.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 6416053 @@ -2930,12 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.46.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152427730 - checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + size: 152431117 + checksum: sha256:378c1016ec891505f10477bf4f32f695033e3d4a94e7366d457bc0b076c826fd name: kernel - evr: 5.14.0-687.45.1.el9_8 + evr: 5.14.0-687.46.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/4ba83a04fb5c3aeaa965553dca22ff7e512ec7e7b7fe88b3785b66fd19025b1a-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/7be89893f89f1eeab3a3acde3a734175464076f4b4567c5b0f4747b88ed95c70-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms size: 65561 - checksum: sha256:4ba83a04fb5c3aeaa965553dca22ff7e512ec7e7b7fe88b3785b66fd19025b1a + checksum: sha256:7be89893f89f1eeab3a3acde3a734175464076f4b4567c5b0f4747b88ed95c70 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3361,13 +3361,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.46.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2909877 - checksum: sha256:e0f15f18c9c6e2c8451b4ef0b722d9a979a4bf5dc419c75273da560e4da8c89f + size: 2912377 + checksum: sha256:0b7f6e5b57c4a4a459fd36b9e930959958bb5a18b2bdac21f46b53b5c2950b47 name: kernel-headers - evr: 5.14.0-687.45.1.el9_8 - sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm + evr: 5.14.0-687.46.1.el9_8 + sourcerpm: kernel-5.14.0-687.46.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -3529,20 +3529,20 @@ arches: name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/c/coreutils-8.32-41.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1206429 - checksum: sha256:ca42d8e48fa5056805089051e473d693c1eeb2b05b0f47ecc00f6a3a4c10496f + size: 1206694 + checksum: sha256:a86313c10c9310922710ae321145017bc1046a600ad35c528691164fcaba512b name: coreutils - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.s390x.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 2112471 - checksum: sha256:3eb7d326a40de65cee2458c471b6b8f8a16218452a6d2194c6b6f2eb1c344bb9 + size: 2117124 + checksum: sha256:b0f8b8a7b42cb74b9397afb79c98f6614bd2da8331e51081a3d9b7d60623b5d5 name: coreutils-common - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/c/cracklib-2.9.6-28.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 101566 @@ -4435,12 +4435,12 @@ arches: checksum: sha256:b2618b278f5c8d6dacfae790c8c73b1fc1578b8f64011f325ced5a4a2e3b58bc name: chkconfig evr: 1.24-2.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.1.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 5696745 - checksum: sha256:9129bd65888eb0b6d802d8fe912609726af5d1f6347df8a18b243c426dcbb4db + size: 5697510 + checksum: sha256:5852781d2fbb2c447c4425960f4fe30310870a0e4073363d2254047b785b5a9e name: coreutils - evr: 8.32-41.el9_8 + evr: 8.32-41.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/cracklib-2.9.6-28.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 6416053 @@ -4567,12 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.46.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152427730 - checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + size: 152431117 + checksum: sha256:378c1016ec891505f10477bf4f32f695033e3d4a94e7366d457bc0b076c826fd name: kernel - evr: 5.14.0-687.45.1.el9_8 + evr: 5.14.0-687.46.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/668e057d4dcd8b7a8a8099b1bd2994fbd30b9a45965337108a656168fd5d4b7a-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/d7098b11cf478a131c610b3ffbb18970f2ee9f73e3fc55449a8f8ffb790ce054-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms size: 63885 - checksum: sha256:668e057d4dcd8b7a8a8099b1bd2994fbd30b9a45965337108a656168fd5d4b7a + checksum: sha256:d7098b11cf478a131c610b3ffbb18970f2ee9f73e3fc55449a8f8ffb790ce054 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4999,13 +4999,13 @@ arches: name: golang-src evr: 1.26.7-1.el9_8 sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.45.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.46.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2919193 - checksum: sha256:73240f2b54765a2e2cd551a836d927acc78fa4f882a7d3a924e65c8177e7e97a + size: 2921665 + checksum: sha256:a33898add57c43e68cc37c0cfb99d8b96cbc0be46b47e58d69b2e9738b9ad998 name: kernel-headers - evr: 5.14.0-687.45.1.el9_8 - sourcerpm: kernel-5.14.0-687.45.1.el9_8.src.rpm + evr: 5.14.0-687.46.1.el9_8 + sourcerpm: kernel-5.14.0-687.46.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5153,20 +5153,20 @@ arches: name: ca-certificates evr: 2025.2.80_v9.0.305-91.el9 sourcerpm: ca-certificates-2025.2.80_v9.0.305-91.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/c/coreutils-8.32-41.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 1220715 - checksum: sha256:e53f0f831246c73ab03ae395054d261ae392faa1229e6c691c08d2321a824b75 + size: 1222083 + checksum: sha256:374257c4cd69107333a7f524dd99579f3ea3ab842b66168eade0a3475fb6eea1 name: coreutils - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.x86_64.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/c/coreutils-common-8.32-41.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 2116611 - checksum: sha256:1d4010cdc4f0fb7a4be68bf1bf0f0cda84a11d7337bd15d8368ab0ff90ca0db4 + size: 2113503 + checksum: sha256:41f69eb8b2087feaa98d0228fb933b6fe8af20a4bf371cfef51e11cbd1f84b4e name: coreutils-common - evr: 8.32-41.el9_8 - sourcerpm: coreutils-8.32-41.el9_8.src.rpm + evr: 8.32-41.el9_8.1 + sourcerpm: coreutils-8.32-41.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/c/cracklib-2.9.6-28.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 102444 @@ -6058,12 +6058,12 @@ arches: checksum: sha256:b2618b278f5c8d6dacfae790c8c73b1fc1578b8f64011f325ced5a4a2e3b58bc name: chkconfig evr: 1.24-2.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/coreutils-8.32-41.el9_8.1.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 5696745 - checksum: sha256:9129bd65888eb0b6d802d8fe912609726af5d1f6347df8a18b243c426dcbb4db + size: 5697510 + checksum: sha256:5852781d2fbb2c447c4425960f4fe30310870a0e4073363d2254047b785b5a9e name: coreutils - evr: 8.32-41.el9_8 + evr: 8.32-41.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/cracklib-2.9.6-28.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 6416053 @@ -6190,12 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.45.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.46.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152427730 - checksum: sha256:41a4374839cc62cba2c086248786fc8d0c6e6de797987600598aa9986dafbacb + size: 152431117 + checksum: sha256:378c1016ec891505f10477bf4f32f695033e3d4a94e7366d457bc0b076c826fd name: kernel - evr: 5.14.0-687.45.1.el9_8 + evr: 5.14.0-687.46.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/f8b0da92fb3d890c8c68ac2b64b87aba3ff7b52a22debd0262f3d6e49ebea660-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/e95e500c807e82466f58d9c5c003da512340e61b83c81070a541c9e041768cf4-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms size: 68115 - checksum: sha256:f8b0da92fb3d890c8c68ac2b64b87aba3ff7b52a22debd0262f3d6e49ebea660 + checksum: sha256:e95e500c807e82466f58d9c5c003da512340e61b83c81070a541c9e041768cf4 From 90f60706b1fb9e433debd5dfbe4f4c9675174134 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 03:33:47 +0000 Subject: [PATCH 295/638] Update quay.io/rhacs-eng/konflux-tasks:latest Docker digest to 8fd043b (#22774) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index a4b5f9f050ee4..70370e9453d80 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 9c735261a3103..73b1685975265 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 87a2b58c25709..330d4dcf5ec03 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -335,7 +335,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index cd6171a64e826..011c2c315a3d8 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index fd81b86744b60..7c65dae7c3061 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -308,7 +308,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -324,7 +324,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -365,7 +365,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 82242850be435..9bffaa9136075 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 57dd59ba54659..f58c8a7e7df52 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 9060e25ebfaaf..2d9d8c361897b 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:4060c43f1ce0d2bfac74bdafeb2343247f2d7512091ea8b68dba6f9567c80e36 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:8fd043b9dd843d627e82237ddafe544a31c05ba4bd9278a7b96f6f2e07e8635a - name: kind value: task resolver: bundles From 711b10f28b4b147dc509f9d9aa67a10e7264cb7f Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 07:41:05 +0000 Subject: [PATCH 296/638] Update Konflux references (#22797) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/main-pipeline.yaml | 6 +++--- .tekton/operator-bundle-pipeline.yaml | 6 +++--- .tekton/scanner-v4-pipeline.yaml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 70370e9453d80..a53013f1bd5b9 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -251,7 +251,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:9fd7d251f92250d89a5465756e278e053af28c6356e5ccdedc4be87bbb13c190 - name: kind value: task resolver: bundles @@ -511,7 +511,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:a973b32f0e11958aba08a635981e4d4e7eb200c417492371a1e692ff589b4e8f - name: kind value: task resolver: bundles @@ -556,7 +556,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:64bb1cf7d875157e68043a411f00e7852afffc68f50881fb041ba994109f4763 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 011c2c315a3d8..f9f15e8fd095e 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -272,7 +272,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:9fd7d251f92250d89a5465756e278e053af28c6356e5ccdedc4be87bbb13c190 - name: kind value: task resolver: bundles @@ -533,7 +533,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:a973b32f0e11958aba08a635981e4d4e7eb200c417492371a1e692ff589b4e8f - name: kind value: task resolver: bundles @@ -578,7 +578,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:64bb1cf7d875157e68043a411f00e7852afffc68f50881fb041ba994109f4763 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 7c65dae7c3061..aa6ddc7c9985c 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -348,7 +348,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:9fd7d251f92250d89a5465756e278e053af28c6356e5ccdedc4be87bbb13c190 - name: kind value: task resolver: bundles @@ -716,7 +716,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:a973b32f0e11958aba08a635981e4d4e7eb200c417492371a1e692ff589b4e8f - name: kind value: task resolver: bundles @@ -756,7 +756,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:64bb1cf7d875157e68043a411f00e7852afffc68f50881fb041ba994109f4763 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 2d9d8c361897b..274a27b751c08 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -271,7 +271,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.2@sha256:9b85915e2dae0351ed8eb9ad113bb11f0a9ef3386ddef6e7d7b6f7a51d5c0ffe + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:9fd7d251f92250d89a5465756e278e053af28c6356e5ccdedc4be87bbb13c190 - name: kind value: task resolver: bundles @@ -531,7 +531,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:99e2263ad98c00b1b44012a325bf0b114684c9f6152fe259ada44e2561a8479e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:a973b32f0e11958aba08a635981e4d4e7eb200c417492371a1e692ff589b4e8f - name: kind value: task resolver: bundles @@ -576,7 +576,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:9ef4dabd53e823e3139b99c8de708be4ee759d63b32982847929df19ab75b2f8 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:64bb1cf7d875157e68043a411f00e7852afffc68f50881fb041ba994109f4763 - name: kind value: task resolver: bundles From b625dd440ae884dff94bc4ad35d434dc4b11214e Mon Sep 17 00:00:00 2001 From: Charmik Sheth <101146970+charmik-redhat@users.noreply.github.com> Date: Sun, 13 Sep 2026 19:18:18 -0700 Subject: [PATCH 297/638] fix(be): Add operating system column to Node CVE reports (#22761) Co-authored-by: Claude Opus 5 --- .../v2/reportgenerator/node/csv_gen.go | 2 + .../v2/reportgenerator/node/csv_gen_test.go | 68 +++++++++++++------ .../reportgenerator/node/report_gen_impl.go | 1 + .../v2/reportgenerator/node/types.go | 10 +++ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/central/reports/scheduler/v2/reportgenerator/node/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/node/csv_gen.go index 0c5cdeb11bf39..7117c8b747712 100644 --- a/central/reports/scheduler/v2/reportgenerator/node/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/node/csv_gen.go @@ -16,6 +16,7 @@ import ( var csvHeader = []string{ "Cluster", "Node", + "Operating System", "Component", "Component Version", "CVE", @@ -34,6 +35,7 @@ func generateCSV(cveResponses []*NodeCVEQueryResponse, configName string) (*byte row := csv.Value{ r.GetCluster(), r.GetNode(), + r.GetOperatingSystem(), r.GetComponent(), r.GetComponentVersion(), r.GetCVE(), diff --git a/central/reports/scheduler/v2/reportgenerator/node/csv_gen_test.go b/central/reports/scheduler/v2/reportgenerator/node/csv_gen_test.go index 3da8970615ec2..4943131f8ea0a 100644 --- a/central/reports/scheduler/v2/reportgenerator/node/csv_gen_test.go +++ b/central/reports/scheduler/v2/reportgenerator/node/csv_gen_test.go @@ -27,6 +27,7 @@ func TestGenerateCSV_EmptyResponses(t *testing.T) { func TestGenerateCSV_WithResponses(t *testing.T) { cluster := "prod-us" node := "node-1" + operatingSystem := "rhcos:4.14" component := "openssl" componentVersion := "1.1.1" cve := "CVE-2021-1234" @@ -41,6 +42,7 @@ func TestGenerateCSV_WithResponses(t *testing.T) { { Cluster: &cluster, Node: &node, + OperatingSystem: &operatingSystem, Component: &component, ComponentVersion: &componentVersion, CVE: &cve, @@ -73,15 +75,16 @@ func TestGenerateCSV_WithResponses(t *testing.T) { assert.Equal(t, csvHeader, records[0]) assert.Equal(t, "prod-us", records[1][0]) assert.Equal(t, "node-1", records[1][1]) - assert.Equal(t, "openssl", records[1][2]) - assert.Equal(t, "1.1.1", records[1][3]) - assert.Equal(t, "CVE-2021-1234", records[1][4]) - assert.Equal(t, "true", records[1][5]) - assert.Equal(t, "1.1.2", records[1][6]) - assert.Equal(t, "CRITICAL", records[1][7]) - assert.Equal(t, "9.80", records[1][8]) - assert.Equal(t, "June 15, 2021", records[1][9]) - assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2021-1234", records[1][10]) + assert.Equal(t, "rhcos:4.14", records[1][2]) + assert.Equal(t, "openssl", records[1][3]) + assert.Equal(t, "1.1.1", records[1][4]) + assert.Equal(t, "CVE-2021-1234", records[1][5]) + assert.Equal(t, "true", records[1][6]) + assert.Equal(t, "1.1.2", records[1][7]) + assert.Equal(t, "CRITICAL", records[1][8]) + assert.Equal(t, "9.80", records[1][9]) + assert.Equal(t, "June 15, 2021", records[1][10]) + assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2021-1234", records[1][11]) } func TestGenerateCSV_LongConfigNameIsTruncated(t *testing.T) { @@ -132,15 +135,40 @@ func TestGenerateCSV_NilFieldsProduceDefaults(t *testing.T) { require.Len(t, records, 2) row := records[1] - assert.Equal(t, "", row[0]) // Cluster - assert.Equal(t, "", row[1]) // Node - assert.Equal(t, "", row[2]) // Component - assert.Equal(t, "", row[3]) // ComponentVersion - assert.Equal(t, "", row[4]) // CVE - assert.Equal(t, "false", row[5]) // Fixable - assert.Equal(t, "", row[6]) // FixedBy - assert.Equal(t, "UNKNOWN", row[7]) // Severity - assert.Equal(t, "0.00", row[8]) // CVSS - assert.Equal(t, "Not Available", row[9]) // First System Occurrence - assert.Equal(t, "", row[10]) // Link + assert.Equal(t, "", row[0]) // Cluster + assert.Equal(t, "", row[1]) // Node + assert.Equal(t, "Not Available", row[2]) // Operating System + assert.Equal(t, "", row[3]) // Component + assert.Equal(t, "", row[4]) // ComponentVersion + assert.Equal(t, "", row[5]) // CVE + assert.Equal(t, "false", row[6]) // Fixable + assert.Equal(t, "", row[7]) // FixedBy + assert.Equal(t, "UNKNOWN", row[8]) // Severity + assert.Equal(t, "0.00", row[9]) // CVSS + assert.Equal(t, "Not Available", row[10]) // First System Occurrence + assert.Equal(t, "", row[11]) // Link +} + +func TestGenerateCSV_EmptyOperatingSystemProducesDefault(t *testing.T) { + emptyOS := "" + responses := []*NodeCVEQueryResponse{ + {OperatingSystem: &emptyOS}, + } + + buf, err := generateCSV(responses, "empty-os-test") + require.NoError(t, err) + + reader, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) + require.NoError(t, err) + + rc, err := reader.File[0].Open() + require.NoError(t, err) + defer func() { _ = rc.Close() }() + + csvReader := csv.NewReader(rc) + records, err := csvReader.ReadAll() + require.NoError(t, err) + require.Len(t, records, 2) + + assert.Equal(t, "Not Available", records[1][2]) } diff --git a/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go index c6b40ecbc6684..af815384121a8 100644 --- a/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/node/report_gen_impl.go @@ -37,6 +37,7 @@ var ( Selects: []*v1.QuerySelect{ search.NewQuerySelect(search.Cluster).Proto(), search.NewQuerySelect(search.Node).Proto(), + search.NewQuerySelect(search.OperatingSystem).Proto(), search.NewQuerySelect(search.Component).Proto(), search.NewQuerySelect(search.ComponentVersion).Proto(), search.NewQuerySelect(search.CVEID).Proto(), diff --git a/central/reports/scheduler/v2/reportgenerator/node/types.go b/central/reports/scheduler/v2/reportgenerator/node/types.go index 97bb3b624544d..7ae1c5418e752 100644 --- a/central/reports/scheduler/v2/reportgenerator/node/types.go +++ b/central/reports/scheduler/v2/reportgenerator/node/types.go @@ -10,6 +10,7 @@ import ( type NodeCVEQueryResponse struct { Cluster *string `db:"cluster"` Node *string `db:"node"` + OperatingSystem *string `db:"operating_system"` Component *string `db:"component"` ComponentVersion *string `db:"component_version"` CVEID *string `db:"cve_id"` @@ -37,6 +38,15 @@ func (r *NodeCVEQueryResponse) GetNode() string { return *r.Node } +func (r *NodeCVEQueryResponse) GetOperatingSystem() string { + // Nodes scanned without OS metadata come back as an empty string rather than + // NULL, so both cases need the placeholder. + if r.OperatingSystem == nil || *r.OperatingSystem == "" { + return "Not Available" + } + return *r.OperatingSystem +} + func (r *NodeCVEQueryResponse) GetComponent() string { if r.Component == nil { return "" From b40a840df88f65c59c248732ad2f5bba02ec7125 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:28:52 +0200 Subject: [PATCH 298/638] ROX-28254: Stamp inactive on runtime alerts after delete (#22775) --- .../alertmanager/alert_manager_impl.go | 52 +++++++++-- .../alertmanager/alert_manager_impl_test.go | 90 +++++++++++++++++++ .../RuntimeViolationLifecycleTest.groovy | 20 ++++- 3 files changed, 149 insertions(+), 13 deletions(-) diff --git a/central/detection/alertmanager/alert_manager_impl.go b/central/detection/alertmanager/alert_manager_impl.go index 2662875455e4b..1891a4fa08e4a 100644 --- a/central/detection/alertmanager/alert_manager_impl.go +++ b/central/detection/alertmanager/alert_manager_impl.go @@ -374,6 +374,12 @@ func mergeAlerts(old, newAlert *storage.Alert) *storage.Alert { } newAlert.FirstOccurred = old.GetFirstOccurred() + // Sensor's payload does not set Inactive; keep the stored flag on the merged alert. + if oldDep := old.GetDeployment(); oldDep != nil && oldDep.GetInactive() { + if newDep := newAlert.GetDeployment(); newDep != nil { + newDep.Inactive = true + } + } return newAlert } @@ -427,7 +433,7 @@ func (d *alertManagerImpl) mergeManyAlerts( } // Phase 2: fetch full alerts for matched keys and merge. - mergedAlertIDs, mergedNew, mergedUpdated, mergeErr := d.fetchAndMergeCandidates(ctx, mergeCandidates) + _, mergedNew, mergedUpdated, mergeErr := d.fetchAndMergeCandidates(ctx, mergeCandidates) if mergeErr != nil { err = mergeErr return @@ -435,7 +441,6 @@ func (d *alertManagerImpl) mergeManyAlerts( newAlerts = append(newAlerts, mergedNew...) updatedAlerts = append(updatedAlerts, mergedUpdated...) - // Find old alerts no longer being produced, and identify inactive deployments. deploymentsBeingRemoved := set.NewStringSet() for _, f := range oldAlertFilters { if depID := f.removedDeploymentID(); depID != "" { @@ -443,6 +448,20 @@ func (d *alertManagerImpl) mergeManyAlerts( } } + for _, a := range newAlerts { + d.stampInactiveIfDeploymentGone(a, deploymentsBeingRemoved) + } + for _, a := range updatedAlerts { + d.stampInactiveIfDeploymentGone(a, deploymentsBeingRemoved) + } + + // IDs already in updatedAlerts were stamped in memory. Re-fetching them + // would upsert the stored blob and drop merged processes. + alreadyUpdated := set.NewStringSet() + for _, a := range updatedAlerts { + alreadyUpdated.Add(a.GetId()) + } + var needInactiveIDs []string for _, key := range previousKeys { if d.shouldMarkAlertResolved(key, incomingAlerts, oldAlertFilters...) { @@ -454,10 +473,10 @@ func (d *alertManagerImpl) mergeManyAlerts( if key.GetLifecycleStage() == storage.LifecycleStage_RUNTIME || key.GetState() == storage.ViolationState_ATTEMPTED { - if mergedAlertIDs.Contains(key.GetId()) { - continue - } if key.HasDeployment() && !key.IsDeploymentInactive() { + if alreadyUpdated.Contains(key.GetId()) { + continue + } depID := key.GetDeploymentId() if deploymentsBeingRemoved.Contains(depID) || d.runtimeDetector.DeploymentInactive(depID) { needInactiveIDs = append(needInactiveIDs, key.GetId()) @@ -466,7 +485,7 @@ func (d *alertManagerImpl) mergeManyAlerts( } } - // Mark deployments inactive for alerts not already handled by the merge phase. + // Stored runtime alerts with no incoming write still need an inactive stamp. inactiveUpdates, inactiveErr := d.markDeploymentsInactive(ctx, needInactiveIDs) if inactiveErr != nil { err = inactiveErr @@ -479,9 +498,8 @@ func (d *alertManagerImpl) mergeManyAlerts( } // fetchAndMergeCandidates fetches full alerts for merge candidates and merges -// them with incoming alerts. Returns the set of merged alert IDs (to skip in -// inactive marking), any alerts treated as new (deleted between phases), and -// any updated alerts from merging. +// them with incoming alerts. Returns merged IDs, alerts treated as new +// (deleted between phases), and updated alerts from merging. func (d *alertManagerImpl) fetchAndMergeCandidates(ctx context.Context, candidates []mergeCandidate) (mergedIDs set.StringSet, newAlerts, updatedAlerts []*storage.Alert, err error) { mergedIDs = set.NewStringSet() if len(candidates) == 0 { @@ -546,6 +564,22 @@ func (d *alertManagerImpl) markDeploymentsInactive(ctx context.Context, alertIDs return updated, nil } +// stampInactiveIfDeploymentGone sets deployment.Inactive when the deployment is +// being removed or is already gone. Sensor can still send runtime alerts after that. +func (d *alertManagerImpl) stampInactiveIfDeploymentGone(alert *storage.Alert, deploymentsBeingRemoved set.StringSet) { + if alert.GetLifecycleStage() != storage.LifecycleStage_RUNTIME && + alert.GetState() != storage.ViolationState_ATTEMPTED { + return + } + dep := alert.GetDeployment() + if dep == nil || dep.GetInactive() { + return + } + if deploymentsBeingRemoved.Contains(dep.GetId()) || d.runtimeDetector.DeploymentInactive(dep.GetId()) { + dep.Inactive = true + } +} + func (d *alertManagerImpl) shouldMarkAlertResolved(old alertviews.AlertMatcher, incomingAlerts []*storage.Alert, oldAlertFilters ...AlertFilterOption) bool { // Do not mark any attempted alerts as stale. All attempted alerts must be resolved by users. if old.GetState() == storage.ViolationState_ATTEMPTED { diff --git a/central/detection/alertmanager/alert_manager_impl_test.go b/central/detection/alertmanager/alert_manager_impl_test.go index 4d2114b15e46d..d5aadcd1eb135 100644 --- a/central/detection/alertmanager/alert_manager_impl_test.go +++ b/central/detection/alertmanager/alert_manager_impl_test.go @@ -171,6 +171,26 @@ func getFakeRuntimeAlert(indicators ...*storage.ProcessIndicator) *storage.Alert } } +func runtimeDeploymentAlert(id, depID string, inactive bool, process *storage.ProcessIndicator) *storage.Alert { + v := &storage.Alert_ProcessViolation{Processes: []*storage.ProcessIndicator{process}} + printer.UpdateProcessAlertViolationMessage(v) + return &storage.Alert{ + Id: id, + Policy: getPolicies()[0], + Entity: &storage.Alert_Deployment_{ + Deployment: &storage.Alert_Deployment{ + Id: depID, + Name: depID, + Inactive: inactive, + }, + }, + LifecycleStage: storage.LifecycleStage_RUNTIME, + State: storage.ViolationState_ACTIVE, + Time: protocompat.GetProtoTimestampFromSeconds(100), + ProcessViolation: v, + } +} + func getFakeResourceRuntimeAlert(resourceType storage.Alert_Resource_ResourceType, resourceName, clusterID, namespaceID, namespace string) *storage.Alert { return &storage.Alert{ LifecycleStage: storage.LifecycleStage_RUNTIME, @@ -710,6 +730,49 @@ func (suite *AlertManagerTestSuite) TestDeploymentMarkedInactiveOnRemoval() { suite.True(modified.Contains("dep-to-remove"), "removed deployment should appear in modified set") } +// TestNewRuntimeAlertMarkedInactiveWhenDeploymentGone covers a first runtime +// alert arriving after the deployment is already gone. +func (suite *AlertManagerTestSuite) TestNewRuntimeAlertMarkedInactiveWhenDeploymentGone() { + incoming := runtimeDeploymentAlert("runtime-alert-new", "dep-gone", false, nowProcess) + + suite.runtimeDetectorMock.EXPECT().DeploymentInactive("dep-gone").Return(true).AnyTimes() + suite.alertsMock.EXPECT().SearchAlertMatchKeys(suite.ctx, gomock.Any(), true).Return(nil, nil) + suite.alertsMock.EXPECT().UpsertAlert(suite.ctx, gomock.Any()).DoAndReturn(func(_ context.Context, a *storage.Alert) error { + suite.True(a.GetDeployment().GetInactive(), "new runtime alert should be marked inactive") + return nil + }) + suite.notifierMock.EXPECT().ProcessAlert(gomock.Any(), gomock.Any()).Return() + + modified, err := suite.alertManager.AlertAndNotify(suite.ctx, []*storage.Alert{incoming}) + suite.NoError(err) + suite.True(modified.Contains("dep-gone")) +} + +// TestMergedRuntimeAlertMarkedInactiveWhenDeploymentGone covers a later runtime +// alert merging into a stored one after the deployment is already gone. +func (suite *AlertManagerTestSuite) TestMergedRuntimeAlertMarkedInactiveWhenDeploymentGone() { + previous := runtimeDeploymentAlert("runtime-alert-1", "dep-gone", false, yesterdayProcess) + incoming := runtimeDeploymentAlert("runtime-alert-incoming", "dep-gone", false, nowProcess) + + suite.runtimeDetectorMock.EXPECT().DeploymentInactive("dep-gone").Return(true).AnyTimes() + suite.runtimeDetectorMock.EXPECT().PolicySet().Return(suite.policySet).AnyTimes() + + suite.alertsMock.EXPECT().SearchAlertMatchKeys(suite.ctx, gomock.Any(), true). + Return(alertsToMatchKeys([]*storage.Alert{previous}), nil) + suite.alertsMock.EXPECT().SearchRawAlerts(suite.ctx, gomock.Any(), false).Return([]*storage.Alert{previous.CloneVT()}, nil) + + suite.alertsMock.EXPECT().UpsertAlert(suite.ctx, gomock.Any()).DoAndReturn(func(_ context.Context, a *storage.Alert) error { + suite.True(a.GetDeployment().GetInactive(), "merged runtime alert should be marked inactive") + suite.Len(a.GetProcessViolation().GetProcesses(), 2, "inactive stamp must not replace the merged processes") + return nil + }) + suite.notifierMock.EXPECT().ProcessAlert(gomock.Any(), gomock.Any()).Return() + + modified, err := suite.alertManager.AlertAndNotify(suite.ctx, []*storage.Alert{incoming}) + suite.NoError(err) + suite.True(modified.Contains("dep-gone")) +} + func (suite *AlertManagerTestSuite) TestResolvedDeploymentAlertReturnsDeploymentID() { alerts := getAlerts() @@ -731,6 +794,33 @@ func (suite *AlertManagerTestSuite) TestResolvedDeploymentAlertReturnsDeployment "resolved deployment alert's deployment ID should appear in modified set") } +func TestMergeAlertsPreservesDeploymentInactive(t *testing.T) { + cases := map[string]struct { + oldInactive bool + newInactive bool + want bool + }{ + "stored inactive is kept when Sensor sends a new process": { + oldInactive: true, + newInactive: false, + want: true, + }, + "active stays active when both sides are active": { + oldInactive: false, + newInactive: false, + want: false, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + old := runtimeDeploymentAlert("alert-1", "dep", c.oldInactive, yesterdayProcess) + newAlert := runtimeDeploymentAlert("alert-incoming", "dep", c.newInactive, nowProcess) + merged := mergeAlerts(old, newAlert) + assert.Equal(t, c.want, merged.GetDeployment().GetInactive()) + }) + } +} + func TestMergeProcessesFromOldIntoNew(t *testing.T) { for _, c := range []struct { desc string diff --git a/qa-tests-backend/src/test/groovy/RuntimeViolationLifecycleTest.groovy b/qa-tests-backend/src/test/groovy/RuntimeViolationLifecycleTest.groovy index 9102aaf7bbd5f..dd0d27ce75880 100644 --- a/qa-tests-backend/src/test/groovy/RuntimeViolationLifecycleTest.groovy +++ b/qa-tests-backend/src/test/groovy/RuntimeViolationLifecycleTest.groovy @@ -51,6 +51,21 @@ class RuntimeViolationLifecycleTest extends BaseSpecification { return disappearedFromStackRox } + // waitForAlertsInactive polls because Central deletes the deployment before + // the alerts pipeline stamps Inactive on remaining runtime alerts. + def waitForAlertsInactive(violations) { + Timer t = new Timer(60, 1) + while (t.IsValid()) { + if (violations.every { violation -> + def alert = AlertService.getViolation(violation.getId()) + alert.getDeployment()?.getInactive() + }) { + return true + } + } + return false + } + def assertAlertExistsForDeploymentUidAndGetViolations(String policyName, String deploymentUid) { checkPolicyExists(APTGETPOLICY) def violations = Services.getViolationsByDeploymentID(deploymentUid, policyName, false, 66) @@ -215,10 +230,7 @@ class RuntimeViolationLifecycleTest extends BaseSpecification { def newViolations = assertAlertExistsForDeploymentUidAndGetViolations(APTGETPOLICY, DEPLOYMENT.getDeploymentUid()) assert (newViolations*.id).toSet().containsAll(violations*.id) - for (def violation: newViolations) { - def alert = AlertService.getViolation(violation.getId()) - assert alert.getDeployment() != null && alert.getDeployment().getInactive() - } + assert waitForAlertsInactive(newViolations) cleanup: if (!deploymentDeleted) { From 526285ae5a2a15d8fdeb467bed9253791c91da5a Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:52:56 +0200 Subject: [PATCH 299/638] fix(CHANGELOG): Add entry for scanner v2 removal (#22819) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49896cbc26fda..cce7b3c521751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Removed Features - Compliance container no longer collects Scanner V2 node inventories. Node scanning continues via Scanner V4 index reports, as long as Scanner V4 is enabled. +- ROX-36654: Removed legacy scanner (StackRox Scanner) across all installation methods. ### Deprecated Features From 241cf27c485ff3e6f758fda06ed8a4a4555879a8 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Mon, 14 Sep 2026 13:54:21 +0200 Subject: [PATCH 300/638] ROX-36823: forbid CRD schema changes on release branches (#22816) Co-authored-by: Claude Opus 4.8 (1M context) --- .../check-crd-frozen-on-release.yaml | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 .github/workflows/check-crd-frozen-on-release.yaml diff --git a/.github/workflows/check-crd-frozen-on-release.yaml b/.github/workflows/check-crd-frozen-on-release.yaml new file mode 100644 index 0000000000000..fa64eb4493aec --- /dev/null +++ b/.github/workflows/check-crd-frozen-on-release.yaml @@ -0,0 +1,140 @@ +# On release branches that already shipped their first (.0) release, CRD schemas +# must stay frozen: no changes to the platform (Central/SecuredCluster) and +# config (SecurityPolicy) CRDs are allowed in a patch release. +# +# The freeze only kicks in once the first release of the branch has been tagged. +# The tag is derived from the branch name: release-X.Y -> X.Y.0. Before X.Y.0 is +# tagged the branch is still stabilizing toward its first release, so CRD changes +# are still permitted and this check is skipped. +# +# When the freeze is active: +# - For PRs targeting a release branch, we diff the release branch (target) +# against the PR tip. +# - For commits pushed to a release branch, we diff the first release tag (X.Y.0) +# against the current commit, so only changes made after the first release are +# considered. +# +# See https://redhat.atlassian.net/browse/ROX-36823 +name: Check CRDs Frozen on Release Branches + +on: + push: + branches: + - release-* + pull_request: + # Only PRs whose base (target) branch is a release branch. + branches: + - release-* + types: + - opened + - reopened + - synchronize + +permissions: + contents: read + +jobs: + check-crds-frozen: + runs-on: ubuntu-latest + steps: + - name: Checkout stackrox/stackrox + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 + with: + # Full history so we can compute the point from which the release branch + # (or the PR) diverged. + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Ensure CRD schemas are unchanged + env: + EVENT_NAME: ${{ github.event_name }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + shell: bash + run: | + set -euo pipefail + + # Determine the release branch this event concerns. + if [[ "$EVENT_NAME" == "pull_request" ]]; then + branch="$BASE_REF" + else + branch="$GITHUB_REF_NAME" + fi + + # Derive the first release tag from the branch name: release-X.Y -> X.Y.0. + # Anything that is not a plain release-X.Y branch (e.g. a scanner branch) + # is out of scope for this check. + if [[ ! "$branch" =~ ^release-([0-9]+)\.([0-9]+)$ ]]; then + echo "Branch '$branch' is not a standard release-X.Y branch; skipping CRD freeze check." + exit 0 + fi + first_release_tag="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.0" + + # Only freeze CRDs for patch releases, i.e. once the first (.0) release of + # this branch has been tagged. Before that the branch is still + # stabilizing and CRD changes are allowed. + # + # `git ls-remote --exit-code` distinguishes its outcomes: 0 = the tag + # exists, 2 = the tag is definitively absent, any other code = a + # transport/other error. We must not treat a transient network failure as + # "not tagged" (that would silently disable the freeze), so we retry and + # fail closed if we cannot get an authoritative answer. + tag_ref="refs/tags/${first_release_tag}" + rc=0 + for attempt in 1 2 3; do + rc=0 + git ls-remote --exit-code --tags origin "$tag_ref" >/dev/null 2>&1 || rc=$? + if [[ "$rc" -eq 0 || "$rc" -eq 2 ]]; then + break + fi + echo "git ls-remote for '${tag_ref}' failed (exit $rc); retry ${attempt}/3..." >&2 + sleep 5 + done + + case "$rc" in + 0) + echo "First release '${first_release_tag}' is tagged; enforcing CRD freeze for patch releases." + ;; + 2) + echo "First release '${first_release_tag}' is not tagged yet; branch is still stabilizing. CRD changes are allowed." + exit 0 + ;; + *) + echo "::error::Could not determine whether '${first_release_tag}' is tagged (git ls-remote exit $rc). Failing closed." + exit 1 + ;; + esac + + # Platform and config CRD manifests that must remain frozen on release + # branches. + crd_files=( + operator/config/crd/bases/platform.stackrox.io_centrals.yaml + operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml + config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml + ) + + if [[ "$EVENT_NAME" == "pull_request" ]]; then + # Diff the release branch (target) against the PR tip. + base="$BASE_SHA" + head="$HEAD_SHA" + echo "Comparing release branch target ($base) against PR tip ($head)" + else + # Diff the first release tag (X.Y.0) against the current commit, so + # only changes made after the first release are considered. + head="$GITHUB_SHA" + git fetch --no-tags --quiet origin "refs/tags/${first_release_tag}:refs/tags/${first_release_tag}" + base="$first_release_tag" + echo "Comparing first release tag $base against current commit ($head)" + fi + + diff="$(git diff "$base" "$head" -- "${crd_files[@]}")" + + if [[ -n "$diff" ]]; then + echo "::error::CRD schema changes are not allowed on release branches (ROX-36823)." + echo "The following platform/config CRD changes were detected:" + echo "$diff" + exit 1 + fi + + echo "No platform/config CRD schema changes detected. CRDs are frozen. ✅" From e89ea2929d51a66b6f557f2642b2cae9eef950fb Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:49:24 +0200 Subject: [PATCH 301/638] ROX-32329: Clean RELATED_IMAGE_SCANNER* V2 env variables (#22551) --- .../rhacs-operator.clusterserviceversion.yaml | 4 --- .../bundle_helpers/pkg/csv/patcher_test.go | 28 +++++++++---------- operator/config/manager/manager.yaml | 4 --- .../values/translation/image_overrides.go | 2 -- operator/internal/images/env.go | 16 ++++------- operator/internal/images/overrides_test.go | 10 +++---- .../values/translation/image_overrides.go | 12 ++++---- .../values/translation/translation_test.go | 10 ------- 8 files changed, 29 insertions(+), 57 deletions(-) diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index bddf14416dc89..fd2bb21690add 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -1821,10 +1821,6 @@ spec: - --leader-elect env: - name: RELATED_IMAGE_MAIN - - name: RELATED_IMAGE_SCANNER - - name: RELATED_IMAGE_SCANNER_SLIM - - name: RELATED_IMAGE_SCANNER_DB - - name: RELATED_IMAGE_SCANNER_DB_SLIM - name: RELATED_IMAGE_COLLECTOR - name: RELATED_IMAGE_FACT - name: RELATED_IMAGE_ROXCTL diff --git a/operator/bundle_helpers/pkg/csv/patcher_test.go b/operator/bundle_helpers/pkg/csv/patcher_test.go index 6083e23deee88..098cb0beada0e 100644 --- a/operator/bundle_helpers/pkg/csv/patcher_test.go +++ b/operator/bundle_helpers/pkg/csv/patcher_test.go @@ -20,7 +20,6 @@ func mustReadValues(t *testing.T, s string) chartutil.Values { func TestPatchCSV_BasicVersionPatching(t *testing.T) { t.Setenv("RELATED_IMAGE_MAIN", "quay.io/rhacs-eng/main:4.0.0") - t.Setenv("RELATED_IMAGE_SCANNER", "quay.io/rhacs-eng/scanner:4.0.0") input := mustReadValues(t, ` metadata: @@ -54,7 +53,6 @@ spec: func TestPatchCSV_ReplacesVersionCalculation(t *testing.T) { t.Setenv("RELATED_IMAGE_MAIN", "quay.io/rhacs-eng/main:4.0.0") - t.Setenv("RELATED_IMAGE_SCANNER", "quay.io/rhacs-eng/scanner:4.0.0") input := mustReadValues(t, ` metadata: @@ -80,8 +78,8 @@ spec: func TestInjectRelatedImageEnvVars(t *testing.T) { t.Setenv("RELATED_IMAGE_MAIN", "quay.io/rhacs-eng/main:4.5.0") - t.Setenv("RELATED_IMAGE_SCANNER", "quay.io/rhacs-eng/scanner:4.5.0") - t.Setenv("RELATED_IMAGE_SCANNER_DB", "quay.io/rhacs-eng/scanner-db:4.5.0") + t.Setenv("RELATED_IMAGE_SCANNER_V4", "quay.io/rhacs-eng/scanner-v4:4.5.0") + t.Setenv("RELATED_IMAGE_SCANNER_V4_DB", "quay.io/rhacs-eng/scanner-v4-db:4.5.0") spec := mustReadValues(t, ` install: @@ -93,8 +91,8 @@ install: containers: - env: - name: RELATED_IMAGE_MAIN - - name: RELATED_IMAGE_SCANNER - - name: RELATED_IMAGE_SCANNER_DB + - name: RELATED_IMAGE_SCANNER_V4 + - name: RELATED_IMAGE_SCANNER_V4_DB `) err := injectRelatedImageEnvVars(spec) @@ -108,8 +106,8 @@ install: env := container["env"].([]any) assert.Equal(t, "quay.io/rhacs-eng/main:4.5.0", env[0].(map[string]any)["value"]) - assert.Equal(t, "quay.io/rhacs-eng/scanner:4.5.0", env[1].(map[string]any)["value"]) - assert.Equal(t, "quay.io/rhacs-eng/scanner-db:4.5.0", env[2].(map[string]any)["value"]) + assert.Equal(t, "quay.io/rhacs-eng/scanner-v4:4.5.0", env[1].(map[string]any)["value"]) + assert.Equal(t, "quay.io/rhacs-eng/scanner-v4-db:4.5.0", env[2].(map[string]any)["value"]) } func TestInjectRelatedImageEnvVars_MissingEnvVar(t *testing.T) { @@ -162,7 +160,7 @@ install: func TestConstructRelatedImages_MultipleEnvVars(t *testing.T) { t.Setenv("RELATED_IMAGE_MAIN", "quay.io/rhacs-eng/main:4.5.0") - t.Setenv("RELATED_IMAGE_SCANNER", "quay.io/rhacs-eng/scanner:4.5.0") + t.Setenv("RELATED_IMAGE_SCANNER_V4", "quay.io/rhacs-eng/scanner-v4:4.5.0") spec := map[string]any{} managerImage := "quay.io/rhacs-eng/rhacs-operator:4.5.0" @@ -173,7 +171,7 @@ func TestConstructRelatedImages_MultipleEnvVars(t *testing.T) { // Verify relatedImages was created relatedImages, ok := spec["relatedImages"].([]map[string]any) require.True(t, ok, "relatedImages should be a []map[string]any") - require.GreaterOrEqual(t, len(relatedImages), 3, "should have at least 3 entries (main, scanner, manager)") + require.GreaterOrEqual(t, len(relatedImages), 3, "should have at least 3 entries (main, scanner_v4, manager)") // Find entries by name imagesByName := make(map[string]string) @@ -185,7 +183,7 @@ func TestConstructRelatedImages_MultipleEnvVars(t *testing.T) { // Verify entries assert.Equal(t, "quay.io/rhacs-eng/main:4.5.0", imagesByName["main"]) - assert.Equal(t, "quay.io/rhacs-eng/scanner:4.5.0", imagesByName["scanner"]) + assert.Equal(t, "quay.io/rhacs-eng/scanner-v4:4.5.0", imagesByName["scanner_v4"]) assert.Equal(t, "quay.io/rhacs-eng/rhacs-operator:4.5.0", imagesByName["manager"]) } @@ -215,7 +213,7 @@ func TestConstructRelatedImages_NoEnvVars(t *testing.T) { } func TestConstructRelatedImages_NameTransformation(t *testing.T) { - t.Setenv("RELATED_IMAGE_SCANNER_DB_SLIM", "quay.io/rhacs-eng/scanner-db-slim:4.5.0") + t.Setenv("RELATED_IMAGE_SCANNER_V4_DB", "quay.io/rhacs-eng/scanner-v4-db:4.5.0") spec := map[string]any{} managerImage := "quay.io/rhacs-eng/rhacs-operator:4.5.0" @@ -230,13 +228,13 @@ func TestConstructRelatedImages_NameTransformation(t *testing.T) { // Find the scanner_db_slim entry found := false for _, img := range relatedImages { - if img["name"].(string) == "scanner_db_slim" { + if img["name"].(string) == "scanner_v4_db" { found = true - assert.Equal(t, "quay.io/rhacs-eng/scanner-db-slim:4.5.0", img["image"]) + assert.Equal(t, "quay.io/rhacs-eng/scanner-v4-db:4.5.0", img["image"]) break } } - assert.True(t, found, "should have scanner_db_slim entry with lowercase name") + assert.True(t, found, "should have scanner_v4_db entry with lowercase name") } func TestPatchCSV_EnrichesSecurityPolicyCRD(t *testing.T) { diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index f76b3eb173abd..3f9b9e5a52d7a 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -60,10 +60,6 @@ spec: - --leader-elect env: - name: RELATED_IMAGE_MAIN ## tags: uses-main-tag,image-name=main - - name: RELATED_IMAGE_SCANNER - - name: RELATED_IMAGE_SCANNER_SLIM - - name: RELATED_IMAGE_SCANNER_DB - - name: RELATED_IMAGE_SCANNER_DB_SLIM - name: RELATED_IMAGE_COLLECTOR - name: RELATED_IMAGE_FACT - name: RELATED_IMAGE_ROXCTL diff --git a/operator/internal/central/values/translation/image_overrides.go b/operator/internal/central/values/translation/image_overrides.go index d6d563b1e972d..4715f359f812a 100644 --- a/operator/internal/central/values/translation/image_overrides.go +++ b/operator/internal/central/values/translation/image_overrides.go @@ -6,8 +6,6 @@ var ( imageOverrides = images.Overrides{ images.Main: "central.image.fullRef", images.CentralDB: "central.db.image.fullRef", - images.Scanner: "scanner.image.fullRef", - images.ScannerDB: "scanner.dbImage.fullRef", images.ScannerV4DB: "scannerV4.db.image.fullRef", images.ScannerV4: "scannerV4.image.fullRef", } diff --git a/operator/internal/images/env.go b/operator/internal/images/env.go index 97b6c5138a0f2..4cd2d68272902 100644 --- a/operator/internal/images/env.go +++ b/operator/internal/images/env.go @@ -4,14 +4,10 @@ import "github.com/stackrox/rox/pkg/env" // Environment variable settings for related image overrides. var ( - Main = env.RegisterSetting("RELATED_IMAGE_MAIN") - CentralDB = env.RegisterSetting("RELATED_IMAGE_CENTRAL_DB") - Scanner = env.RegisterSetting("RELATED_IMAGE_SCANNER") - ScannerSlim = env.RegisterSetting("RELATED_IMAGE_SCANNER_SLIM") - ScannerDB = env.RegisterSetting("RELATED_IMAGE_SCANNER_DB") - ScannerSlimDB = env.RegisterSetting("RELATED_IMAGE_SCANNER_DB_SLIM") - Collector = env.RegisterSetting("RELATED_IMAGE_COLLECTOR") - ScannerV4DB = env.RegisterSetting("RELATED_IMAGE_SCANNER_V4_DB") - ScannerV4 = env.RegisterSetting("RELATED_IMAGE_SCANNER_V4") - Fact = env.RegisterSetting("RELATED_IMAGE_FACT") + Main = env.RegisterSetting("RELATED_IMAGE_MAIN") + CentralDB = env.RegisterSetting("RELATED_IMAGE_CENTRAL_DB") + Collector = env.RegisterSetting("RELATED_IMAGE_COLLECTOR") + ScannerV4DB = env.RegisterSetting("RELATED_IMAGE_SCANNER_V4_DB") + ScannerV4 = env.RegisterSetting("RELATED_IMAGE_SCANNER_V4") + Fact = env.RegisterSetting("RELATED_IMAGE_FACT") ) diff --git a/operator/internal/images/overrides_test.go b/operator/internal/images/overrides_test.go index 99c8a82d25652..7de72717b6959 100644 --- a/operator/internal/images/overrides_test.go +++ b/operator/internal/images/overrides_test.go @@ -10,14 +10,14 @@ import ( func TestToValues(t *testing.T) { testOverrides := Overrides{ - Main: "central.image.fullRef", - Scanner: "scanner.image.fullRef", - ScannerDB: "scanner.dbImage.fullRef", + Main: "central.image.fullRef", + ScannerV4: "scannerV4.image.fullRef", + ScannerV4DB: "scannerV4.db.image.fullRef", } t.Setenv(Main.EnvVar(), "override-main") - t.Setenv(Scanner.EnvVar(), "") - t.Setenv(ScannerDB.EnvVar(), "") + t.Setenv(ScannerV4.EnvVar(), "") + t.Setenv(ScannerV4DB.EnvVar(), "") vals, err := testOverrides.ToValues() require.NoError(t, err) diff --git a/operator/internal/securedcluster/values/translation/image_overrides.go b/operator/internal/securedcluster/values/translation/image_overrides.go index b80c19695677e..9b60dbc43c859 100644 --- a/operator/internal/securedcluster/values/translation/image_overrides.go +++ b/operator/internal/securedcluster/values/translation/image_overrides.go @@ -4,12 +4,10 @@ import "github.com/stackrox/rox/operator/internal/images" var ( imageOverrides = images.Overrides{ - images.Main: "image.main.fullRef", - images.Collector: "image.collector.fullRef", - images.ScannerSlim: "image.scanner.fullRef", - images.ScannerSlimDB: "image.scannerDb.fullRef", - images.ScannerV4DB: "image.scannerV4DB.fullRef", - images.ScannerV4: "image.scannerV4.fullRef", - images.Fact: "image.fact.fullRef", + images.Main: "image.main.fullRef", + images.Collector: "image.collector.fullRef", + images.ScannerV4DB: "image.scannerV4DB.fullRef", + images.ScannerV4: "image.scannerV4.fullRef", + images.Fact: "image.fact.fullRef", } ) diff --git a/operator/internal/securedcluster/values/translation/translation_test.go b/operator/internal/securedcluster/values/translation/translation_test.go index d28669872e6d7..f047f65085cbf 100644 --- a/operator/internal/securedcluster/values/translation/translation_test.go +++ b/operator/internal/securedcluster/values/translation/translation_test.go @@ -41,8 +41,6 @@ func TestTranslation(t *testing.T) { } func (s *TranslationTestSuite) TestImageOverrides() { - s.T().Setenv(images.ScannerSlim.EnvVar(), "stackrox/scanner:1.0.0") - s.T().Setenv(images.ScannerSlimDB.EnvVar(), "stackrox/scanner-db:1.0.0") s.T().Setenv(images.ScannerV4DB.EnvVar(), "stackrox/scanner-v4-db:1.0.0") s.T().Setenv(images.ScannerV4.EnvVar(), "stackrox/scanner-v4:1.0.0") @@ -61,14 +59,6 @@ func (s *TranslationTestSuite) TestImageOverrides() { vals, err := translator.Translate(context.Background(), u) s.Require().NoError(err) - scannerImage, err := vals.PathValue("image.scanner.fullRef") - s.Require().NoError(err) - s.Equal("stackrox/scanner:1.0.0", scannerImage) - - scannerDbImage, err := vals.PathValue("image.scannerDb.fullRef") - s.Require().NoError(err) - s.Equal("stackrox/scanner-db:1.0.0", scannerDbImage) - scannerV4DbImage, err := vals.PathValue("image.scannerV4DB.fullRef") s.Require().NoError(err) s.Equal("stackrox/scanner-v4-db:1.0.0", scannerV4DbImage) From fd56a6779c1cbc1243a91c19abc664dcc3cee9e1 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Mon, 14 Sep 2026 17:30:02 +0200 Subject: [PATCH 302/638] chore(deps): migrate mapstructure from mitchellh to go-viper/v2 (#21263) Co-authored-by: Claude Sonnet 4.5 --- go.mod | 3 +-- pkg/scannerv4/version.go | 2 +- scanner/config/config.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e5208a4a5444e..d9c87c83ca3b5 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.4 github.com/go-logr/logr v1.4.4 github.com/go-logr/zapr v1.3.0 + github.com/go-viper/mapstructure/v2 v2.5.0 github.com/gobwas/glob v0.2.3 github.com/godbus/dbus/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -86,7 +87,6 @@ require ( github.com/mailru/easyjson v0.9.2 github.com/mdlayher/vsock v1.3.0 github.com/mitchellh/go-wordwrap v1.0.1 - github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c github.com/np-guard/cluster-topology-analyzer/v2 v2.3.0 github.com/np-guard/netpol-analyzer v1.4.4 github.com/nxadm/tail v1.4.11 @@ -307,7 +307,6 @@ require ( github.com/go-openapi/swag/yamlutils v0.28.0 // indirect github.com/go-openapi/validate v0.26.3 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect diff --git a/pkg/scannerv4/version.go b/pkg/scannerv4/version.go index fb73588a0da45..10e51b54b188b 100644 --- a/pkg/scannerv4/version.go +++ b/pkg/scannerv4/version.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" ) const ( diff --git a/scanner/config/config.go b/scanner/config/config.go index b0b87fca11c4c..bbe1139dc3b40 100644 --- a/scanner/config/config.go +++ b/scanner/config/config.go @@ -15,8 +15,8 @@ import ( "strings" "time" + "github.com/go-viper/mapstructure/v2" "github.com/jackc/pgx/v5/pgxpool" - "github.com/mitchellh/mapstructure" "github.com/spf13/viper" "github.com/stackrox/rox/pkg/buildinfo" "github.com/stackrox/rox/pkg/utils" From 8b1e8b8ecf08e3c228e70f2c135b7b2237aaf6f3 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Mon, 14 Sep 2026 09:51:03 -0700 Subject: [PATCH 303/638] ROX-36457: Add image created time to csv report (#22375) --- central/reports/scheduler/v2/reportgenerator/csv_gen.go | 3 +++ .../reports/scheduler/v2/reportgenerator/csv_gen_test.go | 8 +++++--- .../scheduler/v2/reportgenerator/report_gen_impl.go | 2 ++ .../report_gen_transaction_integration_test.go | 7 ++++--- central/reports/scheduler/v2/reportgenerator/types.go | 8 ++++++++ 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 8d9b6419ce731..45da7e5234165 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -32,6 +32,7 @@ var ( "NVDCVSS", "EPSS Probability Percentage", "Discovered At", + "Image Created Date", "Reference", "Advisory Name", "Advisory Link", @@ -96,6 +97,7 @@ func formatCSVRow(r *ImageCVEQueryResponse) []string { csvRow = append(csvRow, r.GetDiscoveredAtImage(), + r.GetImageCreatedAt(), r.Link, r.GetAdvisoryName(), r.GetAdvisoryLink(), @@ -167,6 +169,7 @@ func GenerateCSV(cveResponses []*ImageCVEQueryResponse, configName string) (*byt } row = append(row, r.GetDiscoveredAtImage(), + r.GetImageCreatedAt(), r.Link, r.GetAdvisoryName(), r.GetAdvisoryLink(), diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go index be141ddc0b71b..76f479f02c936 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go @@ -71,7 +71,8 @@ func TestFormatCSVRow(t *testing.T) { assert.Equal(t, "true", row[7]) assert.Equal(t, "CRITICAL", row[9]) assert.Equal(t, "95.000", row[12]) - assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[14]) + assert.Equal(t, "Not Available", row[14]) // Image Created Date (nil → "Not Available") + assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[15]) } func TestFormatCSVRow_NilFields(t *testing.T) { @@ -79,8 +80,9 @@ func TestFormatCSVRow_NilFields(t *testing.T) { row := formatCSVRow(r) assert.Equal(t, len(csvHeader), len(row)) assert.Equal(t, "", row[0]) - assert.Equal(t, "Not Available", row[12]) - assert.Equal(t, "Not Available", row[13]) + assert.Equal(t, "Not Available", row[12]) // EPSS Probability Percentage + assert.Equal(t, "Not Available", row[13]) // Discovered At + assert.Equal(t, "Not Available", row[14]) // Image Created Date } func TestCsvReportName(t *testing.T) { diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go index ae8fc26443bfe..ce4e4f4b144b2 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_impl.go @@ -755,6 +755,7 @@ func getSelectsWatchedImages() []*v1.QuerySelect { search.NewQuerySelect(search.CVSS).Proto(), search.NewQuerySelect(search.NVDCVSS).Proto(), search.NewQuerySelect(search.FirstImageOccurrenceTimestamp).Proto(), + search.NewQuerySelect(search.ImageCreatedTime).Proto(), search.NewQuerySelect(search.EPSSProbablity).Proto(), search.NewQuerySelect(search.CisaKev).Proto(), search.NewQuerySelect(search.KnownRansomwareCampaign).Proto(), @@ -778,6 +779,7 @@ func getSelectsDeployedImages() []*v1.QuerySelect { search.NewQuerySelect(search.CVSS).Proto(), search.NewQuerySelect(search.NVDCVSS).Proto(), search.NewQuerySelect(search.FirstImageOccurrenceTimestamp).Proto(), + search.NewQuerySelect(search.ImageCreatedTime).Proto(), search.NewQuerySelect(search.Cluster).Proto(), search.NewQuerySelect(search.Namespace).Proto(), search.NewQuerySelect(search.DeploymentName).Proto(), diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go index b0b64b8c3635d..837ba00486a36 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go @@ -35,9 +35,10 @@ const ( colNVDCVSS = 11 colEPSS = 12 colDiscoveredAt = 13 - colReference = 14 - colAdvName = 15 - colAdvLink = 16 + colImageCreated = 14 + colReference = 15 + colAdvName = 16 + colAdvLink = 17 ) // collectColumn extracts a single column from all rows. diff --git a/central/reports/scheduler/v2/reportgenerator/types.go b/central/reports/scheduler/v2/reportgenerator/types.go index 1b3b53035cef1..2a1c5b2493859 100644 --- a/central/reports/scheduler/v2/reportgenerator/types.go +++ b/central/reports/scheduler/v2/reportgenerator/types.go @@ -48,6 +48,7 @@ type ImageCVEQueryResponse struct { CisaKev *bool `db:"cisa_kev"` KnownRansomwareCampaign *bool `db:"known_ransomware_campaign"` DiscoveredAtImage *time.Time `db:"first_image_occurrence_timestamp"` + ImageCreatedAt *time.Time `db:"image_created_time"` AdvisoryName *string `db:"advisory_name"` AdvisoryLink *string `db:"advisory_link"` Origin *storage.VulnOrigin `db:"cve_origin"` @@ -186,6 +187,13 @@ func (res *ImageCVEQueryResponse) GetDiscoveredAtImage() string { return res.DiscoveredAtImage.Format("January 02, 2006") } +func (res *ImageCVEQueryResponse) GetImageCreatedAt() string { + if res.ImageCreatedAt == nil { + return "Not Available" + } + return res.ImageCreatedAt.Format("2006-01-02") +} + // ReportQueryParts contains the parts used to build the report query type ReportQueryParts struct { Schema *walker.Schema From c3a6420a79da67f5fc43b86f41930a4a72f34d7c Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 14 Sep 2026 16:08:56 -0400 Subject: [PATCH 304/638] ROX-36813: Enable ROX_NODE_VULNERABILITY_REPORTS (#22657) --- pkg/features/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/features/list.go b/pkg/features/list.go index 4d4d02fb66097..3631aef643ceb 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -132,7 +132,7 @@ var ( VulnerabilityReportStreamingDownload = registerFeature("Enables streaming downloadable vulnerability reports to blob storage", "ROX_VULNERABILITY_REPORT_STREAMING_DOWNLOAD", enabled) // NodeVulnerabilityReports enables interface for (future) node vulnerability reports to develop in parallel with image vulnerability reports - NodeVulnerabilityReports = registerFeature("Enables interface for scheduled node vulnerability reports", "ROX_NODE_VULNERABILITY_REPORTS") + NodeVulnerabilityReports = registerFeature("Enables interface for scheduled node vulnerability reports", "ROX_NODE_VULNERABILITY_REPORTS", enabled) // VirtualMachinesEnhancedDataModel enables usage of the enhanced and more flexible data model VirtualMachinesEnhancedDataModel = registerFeature("Enables virtual machine enhanced data model", "ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL", enabled) From 116d16d150c9924d669a2908197cecdd57743486 Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:21:04 -0400 Subject: [PATCH 305/638] feat(central): enable central-worker by default (#22813) --- CHANGELOG.md | 1 + deploy/common/env.sh | 8 +++-- deploy/common/k8sbased.sh | 4 +-- .../internal/config-shape.yaml | 2 +- .../internal/defaults.yaml.htpl | 1 + operator/api/v1alpha1/central_types.go | 2 +- .../platform.stackrox.io_centrals.yaml | 2 +- .../rhacs-operator.clusterserviceversion.yaml | 2 +- .../bases/platform.stackrox.io_centrals.yaml | 2 +- .../rhacs-operator.clusterserviceversion.yaml | 2 +- operator/internal/central/defaults/static.go | 2 +- .../helmtest/central-worker.test.yaml | 29 +++++++++++++++++++ 12 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index cce7b3c521751..1845e3cc8ea68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-34488: Added support for cosign signature discovery via OCI 1.1 referrers, including DSSE envelope verification for sigstore bundle-format signatures. - ROX-36858: Added more supported labels to the image and node vulnerability central custom metrics. +- Pruning (garbage collection) and vulnerability report scheduling now run in a separate `central-worker` deployment by default instead of inside Central. Disable with the Helm value `centralWorker.enabled=false` or `spec.centralWorker.enabled: false` in the Central CR. ### Removed Features diff --git a/deploy/common/env.sh b/deploy/common/env.sh index 7d105df97857c..0b50d6c1d6ac6 100644 --- a/deploy/common/env.sh +++ b/deploy/common/env.sh @@ -88,10 +88,12 @@ echo "ROX_DECLARATIVE_CONFIGURATION is set to ${ROX_DECLARATIVE_CONFIGURATION}" export ROX_MANAGED_CENTRAL="${ROX_MANAGED_CENTRAL:-false}" echo "ROX_MANAGED_CENTRAL is set to ${ROX_MANAGED_CENTRAL}" -export ROX_CENTRAL_WORKER_ENABLED="${ROX_CENTRAL_WORKER_ENABLED:-false}" +# central-worker is enabled by default in the central-services chart. Disabling it is only +# possible through Helm values, which the kubectl output format does not support. +export ROX_CENTRAL_WORKER_ENABLED="${ROX_CENTRAL_WORKER_ENABLED:-true}" echo "ROX_CENTRAL_WORKER_ENABLED is set to ${ROX_CENTRAL_WORKER_ENABLED}" -if [[ "${ROX_CENTRAL_WORKER_ENABLED}" == "true" && "${OUTPUT_FORMAT}" != "helm" ]]; then - echo "ROX_CENTRAL_WORKER_ENABLED requires OUTPUT_FORMAT=helm, overriding" +if [[ "${ROX_CENTRAL_WORKER_ENABLED}" == "false" && "${OUTPUT_FORMAT}" != "helm" ]]; then + echo "ROX_CENTRAL_WORKER_ENABLED=false requires OUTPUT_FORMAT=helm, overriding" export OUTPUT_FORMAT="helm" fi diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index bbef8350d1bf2..9a946e7ca0c29 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -457,9 +457,9 @@ function launch_central { fi fi - if [[ "${ROX_CENTRAL_WORKER_ENABLED:-}" == "true" ]]; then + if [[ -n "${ROX_CENTRAL_WORKER_ENABLED:-}" ]]; then helm_args+=( - --set centralWorker.enabled=true + --set "centralWorker.enabled=${ROX_CENTRAL_WORKER_ENABLED}" ) fi diff --git a/image/templates/helm/stackrox-central/internal/config-shape.yaml b/image/templates/helm/stackrox-central/internal/config-shape.yaml index d0d96cd6ccda9..96dc0fc23ae7c 100644 --- a/image/templates/helm/stackrox-central/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-central/internal/config-shape.yaml @@ -125,7 +125,7 @@ central: none: null # bool extraMounts: null # [dict] centralWorker: - enabled: false # bool + enabled: true # bool nodeSelector: null # string | dict tolerations: null # [dict] hostAliases: null # [dict] diff --git a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl index b008adbca4019..79b9ced9d35f6 100644 --- a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl +++ b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl @@ -92,6 +92,7 @@ defaults: memory: "16Gi" cpu: "8" centralWorker: + enabled: true affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: diff --git a/operator/api/v1alpha1/central_types.go b/operator/api/v1alpha1/central_types.go index a33c3769d82f3..e5e6443bb9707 100644 --- a/operator/api/v1alpha1/central_types.go +++ b/operator/api/v1alpha1/central_types.go @@ -643,7 +643,7 @@ const ( // CentralWorkerSpec defines the central-worker configuration. type CentralWorkerSpec struct { // If set to true, pruning and reporting jobs run in a separate Deployment. - // The default is: false. + // The default is: true. //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1,displayName="Enabled" Enabled *bool `json:"enabled,omitempty"` diff --git a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml index 157f231ecac11..554c25569a3f1 100644 --- a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml @@ -1608,7 +1608,7 @@ spec: enabled: description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. type: boolean hostAliases: description: HostAliases allows configuring additional hostnames diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index fd2bb21690add..f91fb9857fa0a 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -516,7 +516,7 @@ spec: path: central.telemetry.storage.key - description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. displayName: Enabled path: centralWorker.enabled - description: |- diff --git a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml index 4dd017d30ccea..4bade613ee669 100644 --- a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml @@ -1607,7 +1607,7 @@ spec: enabled: description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. type: boolean hostAliases: description: HostAliases allows configuring additional hostnames diff --git a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml index ec980c0ddc385..f234bc4a9cf4c 100644 --- a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml @@ -126,7 +126,7 @@ spec: path: central.telemetry.storage.endpoint - description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. displayName: Enabled path: centralWorker.enabled - description: |- diff --git a/operator/internal/central/defaults/static.go b/operator/internal/central/defaults/static.go index 2617c971296b4..3bb381ca7e2a9 100644 --- a/operator/internal/central/defaults/static.go +++ b/operator/internal/central/defaults/static.go @@ -79,7 +79,7 @@ var staticDefaults = platform.CentralSpec{ Policies: new(platform.NetworkPoliciesEnabled), }, CentralWorker: &platform.CentralWorkerSpec{ - Enabled: new(false), + Enabled: new(true), }, ConfigAsCode: &platform.ConfigAsCodeSpec{ ComponentPolicy: new(platform.ConfigAsCodeComponentEnabled), diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml new file mode 100644 index 0000000000000..ce2ae05e8ad72 --- /dev/null +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml @@ -0,0 +1,29 @@ +values: + imagePullSecrets: + allowNone: true +tests: +- name: "central-worker is enabled by default" + expect: | + .deployments["central-worker"] | assertThat(. != null) + .serviceaccounts["central-worker"] | assertThat(. != null) + container(.deployments["central-worker"]; "central-worker") | .command | assertThat(. == ["/stackrox/central-worker"]) + container(.deployments["central-worker"]; "central-worker") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED") | assertThat(.value == "true") + container(.deployments.central; "central") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED") | assertThat(.value == "true") + tests: + - name: "with network policies" + values: + network: + enableNetworkPolicies: true + expect: | + .networkpolicys["central-worker"] | assertThat(. != null) + .networkpolicys["central-db"].spec.ingress[].from[].podSelector.matchLabels.app | select(. == "central-worker") | assertThat(. == "central-worker") + +- name: "central-worker can be disabled" + values: + centralWorker: + enabled: false + expect: | + .deployments["central-worker"] | assertThat(. == null) + .serviceaccounts["central-worker"] | assertThat(. == null) + .networkpolicys["central-worker"] | assertThat(. == null) + [container(.deployments.central; "central") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED")] | assertThat(length == 0) From 4ce5f0f2051c3fb306e38362f1a7735507f2409d Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:13:57 +0200 Subject: [PATCH 306/638] ROX-36925: Fix VM search by Guest OS (#22806) --- .../internaltostorage/virtual_machine_v2.go | 8 +++---- .../virtual_machine_v2_test.go | 4 ++-- .../convert/storagetov2/virtual_machine_v2.go | 5 ++--- .../storagetov2/virtual_machine_v2_test.go | 14 ++++++++++--- .../pipeline/virtualmachineindex/pipeline.go | 4 +++- .../virtualmachineindex/pipeline_test.go | 18 +++++++++++++--- pkg/virtualmachine/facts.go | 4 ++-- tests/vm_scanning_test.go | 19 +++++++++++++++++ tests/vmhelpers/central.go | 6 ++++++ tests/vmhelpers/central_test.go | 7 +++++++ tests/vmhelpers/central_v2.go | 13 +++++++++--- tests/vmhelpers/central_v2_test.go | 21 +++++++++++++++++++ 12 files changed, 102 insertions(+), 21 deletions(-) diff --git a/central/convert/internaltostorage/virtual_machine_v2.go b/central/convert/internaltostorage/virtual_machine_v2.go index 95bdfbd32b16b..34af48f85c62a 100644 --- a/central/convert/internaltostorage/virtual_machine_v2.go +++ b/central/convert/internaltostorage/virtual_machine_v2.go @@ -12,9 +12,9 @@ func VirtualMachineV2(vm *virtualMachineV1.VirtualMachine) *storage.VirtualMachi return nil } - guestOS := vm.GetFacts()[pkgVM.GuestOSKey] - if guestOS == "" { - guestOS = pkgVM.UnknownGuestOS + informerOS := vm.GetFacts()[pkgVM.GuestOSKey] + if informerOS == "" { + informerOS = pkgVM.UnknownGuestOS } return &storage.VirtualMachineV2{ @@ -23,7 +23,7 @@ func VirtualMachineV2(vm *virtualMachineV1.VirtualMachine) *storage.VirtualMachi Namespace: vm.GetNamespace(), ClusterId: vm.GetClusterId(), Facts: vm.GetFacts(), - GuestOs: guestOS, + GuestOs: pkgVM.DisplayGuestOS(vm.GetFacts(), informerOS), State: convertVirtualMachineV2State(vm.GetState()), VsockCid: vm.GetVsockCid(), } diff --git a/central/convert/internaltostorage/virtual_machine_v2_test.go b/central/convert/internaltostorage/virtual_machine_v2_test.go index d2b0b46e2dd13..63047a6b157d7 100644 --- a/central/convert/internaltostorage/virtual_machine_v2_test.go +++ b/central/convert/internaltostorage/virtual_machine_v2_test.go @@ -49,7 +49,7 @@ func TestVirtualMachineV2(t *testing.T) { }, }, { - name: "copies agent facts and keeps informer GuestOs column", + name: "writes detected guest OS into GuestOs column", input: &virtualMachineV1.VirtualMachine{ Id: "VM-ID-3", Namespace: "ns", @@ -72,7 +72,7 @@ func TestVirtualMachineV2(t *testing.T) { pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", pkgVM.AgentVersionKey: "4.10.0", }, - GuestOs: "Red Hat Enterprise Linux", + GuestOs: "Red Hat Enterprise Linux 9.2", State: storage.VirtualMachineV2_RUNNING, }, }, diff --git a/central/convert/storagetov2/virtual_machine_v2.go b/central/convert/storagetov2/virtual_machine_v2.go index baccb91cecc71..c93dbcf03efbd 100644 --- a/central/convert/storagetov2/virtual_machine_v2.go +++ b/central/convert/storagetov2/virtual_machine_v2.go @@ -7,7 +7,6 @@ import ( "github.com/stackrox/rox/central/views/common" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" - pkgVM "github.com/stackrox/rox/pkg/virtualmachine" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -22,7 +21,7 @@ func VirtualMachineV2ToListItem(vm *storage.VirtualMachineV2) *v2.VMListItem { Namespace: vm.GetNamespace(), ClusterId: vm.GetClusterId(), ClusterName: vm.GetClusterName(), - GuestOs: pkgVM.DisplayGuestOS(vm.GetFacts(), vm.GetGuestOs()), + GuestOs: vm.GetGuestOs(), State: convertVirtualMachineV2State(vm.GetState()), LastUpdated: vm.GetLastUpdated(), } @@ -43,7 +42,7 @@ func VirtualMachineV2ToDetail(vm *storage.VirtualMachineV2) *v2.VMDetail { Namespace: vm.GetNamespace(), ClusterId: vm.GetClusterId(), ClusterName: vm.GetClusterName(), - GuestOs: pkgVM.DisplayGuestOS(vm.GetFacts(), vm.GetGuestOs()), + GuestOs: vm.GetGuestOs(), State: convertVirtualMachineV2State(vm.GetState()), LastUpdated: vm.GetLastUpdated(), Facts: vm.GetFacts(), diff --git a/central/convert/storagetov2/virtual_machine_v2_test.go b/central/convert/storagetov2/virtual_machine_v2_test.go index a50880281c2c0..8381abddf867a 100644 --- a/central/convert/storagetov2/virtual_machine_v2_test.go +++ b/central/convert/storagetov2/virtual_machine_v2_test.go @@ -98,19 +98,27 @@ func TestVirtualMachineV2GuestOsDisplay(t *testing.T) { storedOS string want string }{ - "prefers detected guest OS": { + "returns stored guest OS": { facts: map[string]string{ pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", pkgVM.GuestOSKey: "Red Hat Enterprise Linux", }, - storedOS: "Red Hat Enterprise Linux", + storedOS: "Red Hat Enterprise Linux 9.2", want: "Red Hat Enterprise Linux 9.2", }, - "falls back to stored guest OS": { + "returns stored guest OS when detected is absent": { facts: map[string]string{pkgVM.GuestOSKey: "Red Hat Enterprise Linux"}, storedOS: "Red Hat Enterprise Linux", want: "Red Hat Enterprise Linux", }, + "does not overlay detected onto the stored column": { + facts: map[string]string{ + pkgVM.DetectedGuestOSKey: "Red Hat Enterprise Linux 9.2", + pkgVM.GuestOSKey: "Red Hat Enterprise Linux", + }, + storedOS: "Red Hat Enterprise Linux", + want: "Red Hat Enterprise Linux", + }, } for name, tt := range tests { diff --git a/central/sensor/service/pipeline/virtualmachineindex/pipeline.go b/central/sensor/service/pipeline/virtualmachineindex/pipeline.go index 1390263358832..e11ebef4f189d 100644 --- a/central/sensor/service/pipeline/virtualmachineindex/pipeline.go +++ b/central/sensor/service/pipeline/virtualmachineindex/pipeline.go @@ -210,6 +210,8 @@ func (p *pipelineImpl) storeV2Scan(ctx context.Context, clusterID string, vm *st return nil } +// lookupGuestOS returns facts["guestOS"] so scan identity stays on the KubeVirt +// family name rather than the versioned display string in guest_os. func (p *pipelineImpl) lookupGuestOS(ctx context.Context, vmID string) string { if features.VirtualMachinesEnhancedDataModel.Enabled() { vm, found, err := p.virtualMachineV2Store.GetVirtualMachine(ctx, vmID) @@ -220,7 +222,7 @@ func (p *pipelineImpl) lookupGuestOS(ctx context.Context, vmID string) string { if !found { return "" } - return vm.GetGuestOs() + return vm.GetFacts()[pkgVM.GuestOSKey] } vm, found, err := p.virtualMachineStore.GetVirtualMachine(ctx, vmID) if err != nil { diff --git a/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go b/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go index 2ea1974c21746..1f22c58aa9416 100644 --- a/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go +++ b/central/sensor/service/pipeline/virtualmachineindex/pipeline_test.go @@ -891,9 +891,21 @@ func TestLookupGuestOS(t *testing.T) { }, "v2 guest OS populates scan": { v2Enabled: true, - v2VM: &storage.VirtualMachineV2{GuestOs: "Red Hat Enterprise Linux 9"}, - v2Found: true, - wantOS: "Red Hat Enterprise Linux 9", + v2VM: &storage.VirtualMachineV2{ + GuestOs: "Red Hat Enterprise Linux 9", + Facts: map[string]string{pkgVM.GuestOSKey: "Red Hat Enterprise Linux 9"}, + }, + v2Found: true, + wantOS: "Red Hat Enterprise Linux 9", + }, + "v2 stamps scan from informer fact not display column": { + v2Enabled: true, + v2VM: &storage.VirtualMachineV2{ + GuestOs: "Red Hat Enterprise Linux 9.2", + Facts: map[string]string{pkgVM.GuestOSKey: "Red Hat Enterprise Linux"}, + }, + v2Found: true, + wantOS: "Red Hat Enterprise Linux", }, "v1 VM not found leaves scan OS empty": { v1Found: false, diff --git a/pkg/virtualmachine/facts.go b/pkg/virtualmachine/facts.go index c036d16701ed5..7b6f7b1f330df 100644 --- a/pkg/virtualmachine/facts.go +++ b/pkg/virtualmachine/facts.go @@ -32,8 +32,8 @@ const ( DNFMetadataStatusUnavailable = "unavailable" ) -// DisplayGuestOS prefers the agent-detected OS so callers can show a versioned -// string while storage.GuestOs remains the informer value used for scan stamping. +// DisplayGuestOS is the guest OS written to storage.guest_os: agent-detected +// when present, otherwise fallback (the KubeVirt informer value). func DisplayGuestOS(facts map[string]string, fallback string) string { return cmp.Or(facts[DetectedGuestOSKey], fallback) } diff --git a/tests/vm_scanning_test.go b/tests/vm_scanning_test.go index a2aeef87a59ec..28d44ff6f9ae8 100644 --- a/tests/vm_scanning_test.go +++ b/tests/vm_scanning_test.go @@ -125,6 +125,25 @@ func (s *VMScanningSuite) TestScanPipeline() { "GetVM.guest_os should prefer facts.detectedGuestOS") }) + t.Run("VirtualMachineV2GuestOSSearch", func(t *testing.T) { + s.skipUnlessV2VMAPI(t) + detail := s.mustGetVMV2(snapshot.ID) + guestOS := detail.GetGuestOs() + require.Regexp(t, `^Red Hat Enterprise Linux \d`, guestOS, + "guest_os must be versioned so quoted informer search can miss") + + found, err := vmhelpers.ListV2VMByNamespaceNameGuestOS(s.ctx, s.vmV2Client, vm.Namespace, vm.Name, guestOS) + require.NoError(t, err) + require.NotNil(t, found, "ListVMs Guest OS:%q should find this VM", guestOS) + require.Equal(t, snapshot.ID, found.GetId()) + + const informerGuestOS = "Red Hat Enterprise Linux" + miss, err := vmhelpers.ListV2VMByNamespaceNameGuestOS(s.ctx, s.vmV2Client, vm.Namespace, vm.Name, informerGuestOS) + require.NoError(t, err) + require.Nil(t, miss, + "quoted informer Guest OS must not match a versioned guest_os column") + }) + t.Run("VirtualMachineV2ListVMs", func(t *testing.T) { s.skipUnlessV2VMAPI(t) listed := s.mustListV2VMByNamespaceAndName(vm.Namespace, vm.Name) diff --git a/tests/vmhelpers/central.go b/tests/vmhelpers/central.go index cfe33bec43826..3aa777c7fa5a6 100644 --- a/tests/vmhelpers/central.go +++ b/tests/vmhelpers/central.go @@ -145,6 +145,12 @@ func rawListQueryNamespaceAndName(namespace, name string) string { return fmt.Sprintf("%s:%q+%s:%q", search.Namespace, namespace, search.VirtualMachineName, name) } +// rawListQueryNamespaceNameGuestOS adds an exact Guest OS match so search +// hits the same string List/GetVM return. +func rawListQueryNamespaceNameGuestOS(namespace, name, guestOS string) string { + return fmt.Sprintf("%s+%s:%q", rawListQueryNamespaceAndName(namespace, name), search.GuestOS, guestOS) +} + // ListVMByNamespaceName returns the first VirtualMachine in Central whose namespace and name // match the given values. Returns (nil, nil) when no match is found. func ListVMByNamespaceName(ctx context.Context, client v2.VirtualMachineServiceClient, namespace, name string) (*v2.VirtualMachine, error) { diff --git a/tests/vmhelpers/central_test.go b/tests/vmhelpers/central_test.go index c8c16a8a009f5..d7e34d710043c 100644 --- a/tests/vmhelpers/central_test.go +++ b/tests/vmhelpers/central_test.go @@ -24,6 +24,13 @@ func TestRawListQueryNamespaceAndName(t *testing.T) { require.Contains(t, q, `Virtual Machine Name:"vm:special+name"`) } +func TestRawListQueryNamespaceNameGuestOS(t *testing.T) { + q := rawListQueryNamespaceNameGuestOS("ns", "vm-rhel", "Red Hat Enterprise Linux 8.10") + require.Contains(t, q, `Namespace:"ns"`) + require.Contains(t, q, `Virtual Machine Name:"vm-rhel"`) + require.Contains(t, q, `Guest OS:"Red Hat Enterprise Linux 8.10"`) +} + type stubVirtualMachineClient struct { listFn func(ctx context.Context, req *v2.ListVirtualMachinesRequest) (*v2.ListVirtualMachinesResponse, error) getFn func(ctx context.Context, req *v2.GetVirtualMachineRequest) (*v2.VirtualMachine, error) diff --git a/tests/vmhelpers/central_v2.go b/tests/vmhelpers/central_v2.go index 92bf0cd2cba4b..eb3448cea1ac2 100644 --- a/tests/vmhelpers/central_v2.go +++ b/tests/vmhelpers/central_v2.go @@ -21,10 +21,17 @@ const ( // ListV2VMByNamespaceName returns the first VM matching namespace and name. // Returns (nil, nil) when no match is found. func ListV2VMByNamespaceName(ctx context.Context, client v2.VirtualMachineV2ServiceClient, namespace, name string) (*v2.VMListItem, error) { + return listV2VM(ctx, client, rawListQueryNamespaceAndName(namespace, name)) +} + +// ListV2VMByNamespaceNameGuestOS is ListV2VMByNamespaceName plus an exact Guest OS match. +func ListV2VMByNamespaceNameGuestOS(ctx context.Context, client v2.VirtualMachineV2ServiceClient, namespace, name, guestOS string) (*v2.VMListItem, error) { + return listV2VM(ctx, client, rawListQueryNamespaceNameGuestOS(namespace, name, guestOS)) +} + +func listV2VM(ctx context.Context, client v2.VirtualMachineV2ServiceClient, query string) (*v2.VMListItem, error) { resp, err := client.ListVMs(ctx, &v2.ListVMsRequest{ - Query: &v2.RawQuery{ - Query: rawListQueryNamespaceAndName(namespace, name), - }, + Query: &v2.RawQuery{Query: query}, }) if err != nil { return nil, fmt.Errorf("list vms: %w", err) diff --git a/tests/vmhelpers/central_v2_test.go b/tests/vmhelpers/central_v2_test.go index 51a983322a1a1..666271ce1ef39 100644 --- a/tests/vmhelpers/central_v2_test.go +++ b/tests/vmhelpers/central_v2_test.go @@ -104,6 +104,27 @@ func TestWaitForV2VMPresentInCentral_UsesListVMs(t *testing.T) { require.Contains(t, sawQuery, `Virtual Machine Name:"vm1"`) } +func TestListV2VMByNamespaceNameGuestOS_Query(t *testing.T) { + ctx := t.Context() + var sawQuery string + client := &stubV2Client{ + listVMsFn: func(_ context.Context, req *v2.ListVMsRequest) (*v2.ListVMsResponse, error) { + sawQuery = req.GetQuery().GetQuery() + return &v2.ListVMsResponse{ + Vms: []*v2.VMListItem{ + {Id: "id-1", Namespace: "ns1", Name: "vm1", GuestOs: "Red Hat Enterprise Linux 8.10"}, + }, + }, nil + }, + } + vm, err := ListV2VMByNamespaceNameGuestOS(ctx, client, "ns1", "vm1", "Red Hat Enterprise Linux 8.10") + require.NoError(t, err) + require.Equal(t, "id-1", vm.GetId()) + require.Contains(t, sawQuery, `Guest OS:"Red Hat Enterprise Linux 8.10"`) + require.Contains(t, sawQuery, `Namespace:"ns1"`) + require.Contains(t, sawQuery, `Virtual Machine Name:"vm1"`) +} + func TestWaitForV2VMLatestScan(t *testing.T) { ctx := t.Context() opts := WaitOptions{Timeout: 150 * time.Millisecond, PollInterval: 5 * time.Millisecond} From 59b63da46f3330eb1beb3ab8561ae4f851b2b9c5 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Tue, 15 Sep 2026 12:33:50 +0200 Subject: [PATCH 307/638] ROX-36833: fix TestCancelNodeReport race on cancel status check (#22736) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tests/node_report_test.go | 42 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/node_report_test.go b/tests/node_report_test.go index df5421a76a7ac..8d1d586d0d35f 100644 --- a/tests/node_report_test.go +++ b/tests/node_report_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) @@ -509,24 +511,36 @@ func (s *NodeReportSuite) TestCancelNodeReport() { s.T().Logf("Cancel returned error (job may have already finished): %v", err) } - statusCtx, statusCancel := context.WithTimeout(s.ctx, 30*time.Second) - defer statusCancel() - - statusResp, err := s.service.GetNodeReportStatus(statusCtx, &apiV2.ResourceByID{Id: runResp.GetReportId()}) - if err != nil { - s.T().Logf("Report not found after cancel: %v", err) - return + // Cancellation is processed asynchronously; poll until the report reaches a terminal state. + var finalStatus *apiV2.ReportStatus + s.Require().Eventually(func() bool { + ctx, cancel := context.WithTimeout(s.ctx, 10*time.Second) + defer cancel() + statusResp, err := s.service.GetNodeReportStatus(ctx, &apiV2.ResourceByID{Id: runResp.GetReportId()}) + if err != nil { + if status.Code(err) == codes.NotFound { + s.T().Logf("Report not found after cancel: %v", err) + return true + } + s.T().Logf("Error checking report status after cancel: %v", err) + return false + } + finalStatus = statusResp.GetStatus() + state := finalStatus.GetRunState() + s.T().Logf("Report state after cancel: %s", state) + return state == apiV2.ReportStatus_FAILURE || + state == apiV2.ReportStatus_GENERATED || + state == apiV2.ReportStatus_DELIVERED + }, 2*time.Minute, 5*time.Second, "report did not reach terminal state after cancel") + + if finalStatus == nil { + return // not-found case handled above } - - reportStatus := statusResp.GetStatus() - s.T().Logf("Report state after cancel: %s", reportStatus.GetRunState()) - switch reportStatus.GetRunState() { + switch finalStatus.GetRunState() { case apiV2.ReportStatus_FAILURE: - s.Equal("report cancelled by user", reportStatus.GetErrorMsg(), "cancelled report should have cancellation message") + s.Equal("report cancelled by user", finalStatus.GetErrorMsg(), "cancelled report should have cancellation message") case apiV2.ReportStatus_GENERATED, apiV2.ReportStatus_DELIVERED: s.T().Log("report completed before cancel took effect") - default: - s.Failf("unexpected report state after cancel", "got %s: %s", reportStatus.GetRunState(), reportStatus.GetErrorMsg()) } } From 51600ba89f1a6d5587fdaec8c9c4e5b45c6fd933 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Tue, 15 Sep 2026 13:59:40 +0200 Subject: [PATCH 308/638] ROX-36975: Fix race (#22827) --- pkg/queue/queue_test.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/queue/queue_test.go b/pkg/queue/queue_test.go index e4fbaf825f6b8..cb6d3b974bf81 100644 --- a/pkg/queue/queue_test.go +++ b/pkg/queue/queue_test.go @@ -430,16 +430,10 @@ func testEmptyPullAfterSignal(t *testing.T, consumerName string, startConsumer f <-consumerStarted synctest.Wait() - q.Push(99) - pulled := q.Pull() - assert.Equal(t, 99, pulled) - + // Simulate waking after another consumer has already taken the item. + // Using Push followed by Pull would race with the waiting consumer. + q.notEmptySignal.Signal() synctest.Wait() - select { - case <-itemReceived: - t.Fatalf("%s should not have received the pulled item", consumerName) - default: - } q.Push(42) synctest.Wait() From e5e49cca9c83c90d030b2b89988518b1ec9d13b8 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Tue, 15 Sep 2026 15:40:57 +0200 Subject: [PATCH 309/638] ROX-36982: Make count deterministic (#22841) --- .../common/detector/detector_network_flow_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sensor/common/detector/detector_network_flow_test.go b/sensor/common/detector/detector_network_flow_test.go index 0509469c72aad..c772a89b52015 100644 --- a/sensor/common/detector/detector_network_flow_test.go +++ b/sensor/common/detector/detector_network_flow_test.go @@ -208,7 +208,7 @@ func TestNetworkFlowPipelineDropsWhenFull(t *testing.T) { require.NoError(t, d.Start()) defer d.Stop() - for i := range totalEvents { + sendFlow := func(idSuffix int) { d.ProcessNetworkFlow(context.Background(), &storage.NetworkFlow{ Props: &storage.NetworkFlowProperties{ SrcEntity: &storage.NetworkEntityInfo{ @@ -217,13 +217,22 @@ func TestNetworkFlowPipelineDropsWhenFull(t *testing.T) { }, DstEntity: &storage.NetworkEntityInfo{ Type: storage.NetworkEntityInfo_INTERNET, - Id: fmt.Sprintf("internet-%d", i), + Id: fmt.Sprintf("internet-%d", idSuffix), }, DstPort: 80, L4Protocol: storage.L4Protocol_L4_PROTOCOL_TCP, }, }) } + + // PubSub: the BufferedConsumer's run() goroutine pulls this one + // event and PARKS in handleEvent. That makes count deterministic. + sendFlow(totalEvents + 1) + synctest.Wait() + + for i := range totalEvents { + sendFlow(i) + } synctest.Wait() select { From 755b2aeb9038de341224ac89c51957da1c77bff5 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:33:21 +0200 Subject: [PATCH 310/638] ROX-32329: Remove Scanner V2 from CI (#22822) --- .github/CODEOWNERS | 1 - .github/labeler.yml | 2 +- .github/workflows/e2e-dispatch.yaml | 4 -- .github/workflows/release-ci.yaml | 19 +---- .../workflows/scan-image-vulnerabilities.yaml | 8 --- .github/workflows/style.yaml | 13 +--- .../workflows/update_scanner_periodic.yaml | 49 ------------- .tekton/create-custom-snapshot.yaml | 20 ------ .tekton/images-mirror-set.yaml | 12 ---- .tekton/operator-bundle-pipeline.yaml | 72 ------------------- .tekton/retag-scanner-db-slim.yaml | 60 ---------------- .tekton/retag-scanner-db.yaml | 60 ---------------- .tekton/retag-scanner-slim.yaml | 60 ---------------- .tekton/retag-scanner.yaml | 60 ---------------- operator/konflux.bundle.Dockerfile | 16 ----- scripts/ci/bats/lib_release_version_test.bats | 22 +----- scripts/ci/lib.sh | 22 ------ tests/upgrade/lib.sh | 5 +- tests/upgrade/postgres_run.sh | 1 - 19 files changed, 8 insertions(+), 498 deletions(-) delete mode 100644 .github/workflows/update_scanner_periodic.yaml delete mode 100644 .tekton/retag-scanner-db-slim.yaml delete mode 100644 .tekton/retag-scanner-db.yaml delete mode 100644 .tekton/retag-scanner-slim.yaml delete mode 100644 .tekton/retag-scanner.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3c1d2f7d70746..d318cf685d35c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -71,7 +71,6 @@ tests/roxctl/bats-tests/local/central-migrate-to-operator.bats @stackrox/insta # Scanner team's responsibilities include anything related to the scanner itself and scanning utilities # such as vulnerability uploading and image integrations. /.github/workflows/scanner* @stackrox/scanner -/.github/workflows/update_scanner_periodic.yaml @stackrox/scanner /central/image/service/ @stackrox/scanner /central/imageintegration/service/ @stackrox/scanner /central/imageintegration/store/defaults.go @stackrox/scanner diff --git a/.github/labeler.yml b/.github/labeler.yml index 73a8695553f5c..7a345d52c9ba6 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -60,7 +60,7 @@ ci-all-qa-tests: - changed-files: - any-glob-to-any-file: - COLLECTOR_VERSION - - SCANNER_VERSION + - FACT_VERSION backport: - base-branch: 'release-*' diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index 102623bc8400b..50696c5561e63 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -190,12 +190,8 @@ jobs: rhacs-eng/${{ env.IMAGE_PREFIX }}fact:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.IMAGE_PREFIX }}main:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.IMAGE_PREFIX }}roxctl:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} - rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-db-slim:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} - rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-db:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} - rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-slim:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-v4-db:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.IMAGE_PREFIX }}scanner-v4:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} - rhacs-eng/${{ env.IMAGE_PREFIX }}scanner:${{ env.IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.OPERATOR_IMAGE_PREFIX }}operator-bundle:v${{ env.OPERATOR_IMAGE_TAG }}${{ env.TAG_SUFFIX }} rhacs-eng/${{ env.OPERATOR_IMAGE_PREFIX }}operator:${{ env.OPERATOR_IMAGE_TAG }}${{ env.TAG_SUFFIX }} diff --git a/.github/workflows/release-ci.yaml b/.github/workflows/release-ci.yaml index 70403c26c9ec1..ea7cc7238d2b6 100644 --- a/.github/workflows/release-ci.yaml +++ b/.github/workflows/release-ci.yaml @@ -39,19 +39,6 @@ jobs: echo "is_release=false" | tee -a "$GITHUB_OUTPUT" fi - check-scanner-version: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - with: - fetch-depth: 0 - ref: ${{ github.ref_name }} - - name: Check scanner version - run: | - scripts/ci/lib.sh \ - check_scanner_version - check-collector-version: runs-on: ubuntu-latest steps: @@ -79,7 +66,7 @@ jobs: check_fact_version publish-helm-charts: - needs: [build, check-scanner-version, check-collector-version, check-fact-version, check-is-release] + needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' runs-on: ubuntu-latest steps: @@ -108,7 +95,7 @@ jobs: # Publish `roxagent` and `roxctl`. publish-cli: - needs: [build, check-scanner-version, check-collector-version, check-fact-version, check-is-release] + needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' runs-on: ubuntu-latest steps: @@ -134,7 +121,7 @@ jobs: publish_cli "${STACKROX_TAG}" publish-openapispec: - needs: [build, check-scanner-version, check-collector-version, check-fact-version, check-is-release] + needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/scan-image-vulnerabilities.yaml b/.github/workflows/scan-image-vulnerabilities.yaml index 9f17c2138f0e7..2ee393ab6b219 100644 --- a/.github/workflows/scan-image-vulnerabilities.yaml +++ b/.github/workflows/scan-image-vulnerabilities.yaml @@ -62,10 +62,6 @@ jobs: "fact", "main", "roxctl", - "scanner", - "scanner-db", - "scanner-db-slim", - "scanner-slim", "scanner-v4", "scanner-v4-db", "stackrox-operator", @@ -118,10 +114,6 @@ jobs: "release-fact", "release-main", "release-roxctl", - "release-scanner", - "release-scanner-db", - "release-scanner-db-slim", - "release-scanner-slim", "release-scanner-v4", "release-scanner-v4-db", "release-operator", diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml index 392824d9d3503..0cfe9f54c7978 100644 --- a/.github/workflows/style.yaml +++ b/.github/workflows/style.yaml @@ -205,7 +205,7 @@ jobs: check-dependent-images-exist: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - # This job ensures that COLLECTOR_VERSION, FACT_VERSION or SCANNER_VERSION files cannot be updated to a version + # This job ensures that COLLECTOR_VERSION or FACT_VERSION files cannot be updated to a version # for which the image was not successfully built on Konflux (suffix "-fast"). It also verifies that GHA-built image # is there (no suffix) so that the failure also happens in this job. runs-on: ubuntu-latest @@ -217,12 +217,11 @@ jobs: with: gcp-account: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} - - name: Get image tags from COLLECTOR|FACT|SCANNER_VERSION file + - name: Get image tags from COLLECTOR_|FACT_VERSION file run: | { echo "collector_tag=$(make --quiet --no-print-directory collector-tag)" echo "fact_tag=$(make --quiet --no-print-directory fact-tag)" - echo "scanner_tag=$(make --quiet --no-print-directory scanner-tag)" } >> "$GITHUB_ENV" - name: Check GHA-built image exists @@ -236,10 +235,6 @@ jobs: image: | rhacs-eng/collector:${{ env.collector_tag }} rhacs-eng/fact:${{ env.fact_tag }} - rhacs-eng/scanner:${{ env.scanner_tag }} - rhacs-eng/scanner-slim:${{ env.scanner_tag }} - rhacs-eng/scanner-db:${{ env.scanner_tag }} - rhacs-eng/scanner-db-slim:${{ env.scanner_tag }} - name: Check Konflux-built image exists uses: stackrox/actions/release/wait-for-image@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/release/wait-for-image@v1 @@ -252,10 +247,6 @@ jobs: image: | rhacs-eng/release-collector:${{ env.collector_tag }}-fast rhacs-eng/release-fact:${{ env.fact_tag }}-fast - rhacs-eng/release-scanner:${{ env.scanner_tag }}-fast - rhacs-eng/release-scanner-slim:${{ env.scanner_tag }}-fast - rhacs-eng/release-scanner-db:${{ env.scanner_tag }}-fast - rhacs-eng/release-scanner-db-slim:${{ env.scanner_tag }}-fast github-actions-lint: needs: detect-changes diff --git a/.github/workflows/update_scanner_periodic.yaml b/.github/workflows/update_scanner_periodic.yaml deleted file mode 100644 index 6b5a72c198e8e..0000000000000 --- a/.github/workflows/update_scanner_periodic.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Update scanner version -on: - workflow_dispatch: - schedule: - - cron: 0 5 * * 1 -jobs: - update-scanner: - if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - with: - ref: master # this is our target branch - fetch-depth: 0 # we need to fetch all branches - - name: Checkout scanner repo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - with: - repository: stackrox/scanner - path: deps/scanner - fetch-depth: 0 # we need to fetch tags - - name: Update version - run: make -sC deps/scanner tag | tee SCANNER_VERSION - - name: Create Pull Request - id: cpr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # ratchet:peter-evans/create-pull-request@v8 - with: - token: '${{ secrets.RHACS_BOT_GITHUB_TOKEN }}' - commit-message: Update SCANNER_VERSION - committer: '${{ secrets.RHACS_BOT_GITHUB_USERNAME }} <${{ secrets.RHACS_BOT_GITHUB_EMAIL }}>' - author: '${{ secrets.RHACS_BOT_GITHUB_USERNAME }} <${{ secrets.RHACS_BOT_GITHUB_EMAIL }}>' - branch: update_scanner_version - signoff: false - delete-branch: true - title: 'chore(scanner): Update SCANNER_VERSION' - body: | - Weekly update of SCANNER_VERSION to latest master version - labels: | - ci-all-qa-tests - dependencies - konflux-build - team-reviewers: scanner - draft: false - - name: Enable Pull Request Automerge - if: steps.cpr.outputs.pull-request-operation == 'created' - uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # ratchet:peter-evans/enable-pull-request-automerge@v3 - with: - token: '${{ secrets.RHACS_BOT_GITHUB_TOKEN }}' - pull-request-number: '${{ steps.cpr.outputs.pull-request-number }}' - merge-method: squash diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 330d4dcf5ec03..3c5d495406e3d 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -299,26 +299,6 @@ spec: "internalRepo": "quay.io/rhacs-eng/release-roxctl", "component": "roxctl" }, - { - "externalRepo": "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9", - "internalRepo": "quay.io/rhacs-eng/release-scanner-db", - "component": "scanner-db" - }, - { - "externalRepo": "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-slim-rhel9", - "internalRepo": "quay.io/rhacs-eng/release-scanner-db-slim", - "component": "scanner-db-slim" - }, - { - "externalRepo": "registry.redhat.io/advanced-cluster-security/rhacs-scanner-rhel9", - "internalRepo": "quay.io/rhacs-eng/release-scanner", - "component": "scanner" - }, - { - "externalRepo": "registry.redhat.io/advanced-cluster-security/rhacs-scanner-slim-rhel9", - "internalRepo": "quay.io/rhacs-eng/release-scanner-slim", - "component": "scanner-slim" - }, { "externalRepo": "registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-db-rhel9", "internalRepo": "quay.io/rhacs-eng/release-scanner-v4-db", diff --git a/.tekton/images-mirror-set.yaml b/.tekton/images-mirror-set.yaml index 22352382973d0..f8f22c411a424 100644 --- a/.tekton/images-mirror-set.yaml +++ b/.tekton/images-mirror-set.yaml @@ -29,18 +29,6 @@ spec: - source: registry.redhat.io/advanced-cluster-security/rhacs-roxctl-rhel9 mirrors: - quay.io/rhacs-eng/release-roxctl - - source: registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9 - mirrors: - - quay.io/rhacs-eng/release-scanner-db - - source: registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-slim-rhel9 - mirrors: - - quay.io/rhacs-eng/release-scanner-db-slim - - source: registry.redhat.io/advanced-cluster-security/rhacs-scanner-rhel9 - mirrors: - - quay.io/rhacs-eng/release-scanner - - source: registry.redhat.io/advanced-cluster-security/rhacs-scanner-slim-rhel9 - mirrors: - - quay.io/rhacs-eng/release-scanner-slim - source: registry.redhat.io/advanced-cluster-security/rhacs-scanner-v4-db-rhel9 mirrors: - quay.io/rhacs-eng/release-scanner-v4-db diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index aa6ddc7c9985c..33c9e19716946 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -120,42 +120,6 @@ spec: type: string default: "registry.redhat.io/advanced-cluster-security/rhacs-main-rhel9" - - name: scanner-image-build-repo - description: Repository where the (unreleased) Scanner V2 image is pushed by its build pipeline. - type: string - default: "quay.io/rhacs-eng/release-scanner" - - name: scanner-image-catalog-repo - description: Repository within the Red Hat Container Catalog where the Scanner V2 image is pushed to during the release. - type: string - default: "registry.redhat.io/advanced-cluster-security/rhacs-scanner-rhel9" - - - name: scanner-db-image-build-repo - description: Repository where the (unreleased) Scanner V2 DB image is pushed by its build pipeline. - type: string - default: "quay.io/rhacs-eng/release-scanner-db" - - name: scanner-db-image-catalog-repo - description: Repository within the Red Hat Container Catalog where the Scanner V2 DB image is pushed to during the release. - type: string - default: "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-rhel9" - - - name: scanner-slim-image-build-repo - description: Repository where the (unreleased) Scanner V2 slim image is pushed by its build pipeline. - type: string - default: "quay.io/rhacs-eng/release-scanner-slim" - - name: scanner-slim-image-catalog-repo - description: Repository within the Red Hat Container Catalog where the Scanner V2 slim image is pushed to during the release. - type: string - default: "registry.redhat.io/advanced-cluster-security/rhacs-scanner-slim-rhel9" - - - name: scanner-db-slim-image-build-repo - description: Repository where the (unreleased) Scanner V2 DB slim image is pushed by its build pipeline. - type: string - default: "quay.io/rhacs-eng/release-scanner-db-slim" - - name: scanner-db-slim-image-catalog-repo - description: Repository within the Red Hat Container Catalog where the Scanner V2 DB slim image is pushed to during the release. - type: string - default: "registry.redhat.io/advanced-cluster-security/rhacs-scanner-db-slim-rhel9" - - name: scanner-v4-image-build-repo description: Repository where the (unreleased) Scanner V4 image is pushed by its build pipeline. type: string @@ -380,38 +344,6 @@ spec: # This timeout must be the same as the pipeline timeout in `main-build.yaml`. timeout: 2h40m - - name: wait-for-scanner-image - params: - - name: IMAGE - value: "$(params.scanner-image-build-repo):$(tasks.determine-image-tag.results.IMAGE_TAG)" - taskRef: *wait-for-image-ref - # This timeout must be the same as the pipeline timeout in `scanner-retag.yaml` - timeout: 40m - - - name: wait-for-scanner-db-image - params: - - name: IMAGE - value: "$(params.scanner-db-image-build-repo):$(tasks.determine-image-tag.results.IMAGE_TAG)" - taskRef: *wait-for-image-ref - # This timeout must be the same as the pipeline timeout in `scanner-db-retag.yaml` - timeout: 40m - - - name: wait-for-scanner-slim-image - params: - - name: IMAGE - value: "$(params.scanner-slim-image-build-repo):$(tasks.determine-image-tag.results.IMAGE_TAG)" - taskRef: *wait-for-image-ref - # This timeout must be the same as the pipeline timeout in `scanner-slim-retag.yaml` - timeout: 40m - - - name: wait-for-scanner-db-slim-image - params: - - name: IMAGE - value: "$(params.scanner-db-slim-image-build-repo):$(tasks.determine-image-tag.results.IMAGE_TAG)" - taskRef: *wait-for-image-ref - # This timeout must be the same as the pipeline timeout in `scanner-db-slim-retag.yaml` - timeout: 40m - - name: wait-for-scanner-v4-image params: - name: IMAGE @@ -482,10 +414,6 @@ spec: - OPERATOR_IMAGE_TAG=$(tasks.determine-image-tag.results.IMAGE_TAG) - OPERATOR_IMAGE_REF=$(params.operator-image-catalog-repo)@$(tasks.wait-for-operator-image.results.IMAGE_DIGEST) - RELATED_IMAGE_MAIN=$(params.main-image-catalog-repo)@$(tasks.wait-for-main-image.results.IMAGE_DIGEST) - - RELATED_IMAGE_SCANNER=$(params.scanner-image-catalog-repo)@$(tasks.wait-for-scanner-image.results.IMAGE_DIGEST) - - RELATED_IMAGE_SCANNER_DB=$(params.scanner-db-image-catalog-repo)@$(tasks.wait-for-scanner-db-image.results.IMAGE_DIGEST) - - RELATED_IMAGE_SCANNER_SLIM=$(params.scanner-slim-image-catalog-repo)@$(tasks.wait-for-scanner-slim-image.results.IMAGE_DIGEST) - - RELATED_IMAGE_SCANNER_DB_SLIM=$(params.scanner-db-slim-image-catalog-repo)@$(tasks.wait-for-scanner-db-slim-image.results.IMAGE_DIGEST) - RELATED_IMAGE_SCANNER_V4=$(params.scanner-v4-image-catalog-repo)@$(tasks.wait-for-scanner-v4-image.results.IMAGE_DIGEST) - RELATED_IMAGE_SCANNER_V4_DB=$(params.scanner-v4-db-image-catalog-repo)@$(tasks.wait-for-scanner-v4-db-image.results.IMAGE_DIGEST) - RELATED_IMAGE_COLLECTOR=$(params.collector-image-catalog-repo)@$(tasks.wait-for-collector-image.results.IMAGE_DIGEST) diff --git a/.tekton/retag-scanner-db-slim.yaml b/.tekton/retag-scanner-db-slim.yaml deleted file mode 100644 index b9554d16be051..0000000000000 --- a/.tekton/retag-scanner-db-slim.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: tekton.dev/v1 -kind: PipelineRun - -metadata: - annotations: - build.appstudio.openshift.io/repo: https://github.com/stackrox/stackrox?rev={{revision}} - build.appstudio.redhat.com/commit_sha: '{{revision}}' - build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' - build.appstudio.redhat.com/target_branch: '{{target_branch}}' - pipelinesascode.tekton.dev/max-keep-runs: "500" - pipelinesascode.tekton.dev/on-comment: "/konflux-retest retag-scanner-db-slim" - # TODO(ROX-21073): re-enable for all PR branches - pipelinesascode.tekton.dev/on-cel-expression: | - ( - event == "push" && target_branch.matches("^(master|release-.*|refs/tags/.*)$") - ) || ( - event == "pull_request" && ( - target_branch.startsWith("release-") || - source_branch.matches("(konflux|renovate|appstudio|rhtap)") || - (has(body.pull_request) && has(body.pull_request.labels) && body.pull_request.labels.exists(l, l.name == "konflux-build")) - ) && body.action != "ready_for_review" - ) - # The empty `on-label` annotation is a workaround to make sure the pipeline gets triggered when the label gets first - # added to the PR. See the Slack tread linked from ROX-30580. - pipelinesascode.tekton.dev/on-label: "[]" - labels: - appstudio.openshift.io/application: acs - name: retag-scanner-db-slim - namespace: rh-acs-tenant - -spec: - - params: - - name: git-url - value: '{{source_url}}' - - name: revision - value: '{{revision}}' - - name: input-image-repo - value: quay.io/rhacs-eng/release-scanner-db-slim - - name: input-image-tag-makefile-target - value: scanner-tag - - name: output-image-repo - value: quay.io/rhacs-eng/release-scanner-db-slim - - pipelineRef: - name: retag-pipeline - - taskRunTemplate: - serviceAccountName: build-pipeline-scanner-db-slim - - timeouts: - tasks: 30m - # Reserve time for final tasks to run. - finally: 10m - pipeline: 40m - - workspaces: - - name: git-auth - secret: - secretName: '{{ git_auth_secret }}' diff --git a/.tekton/retag-scanner-db.yaml b/.tekton/retag-scanner-db.yaml deleted file mode 100644 index 36865cf08e4b4..0000000000000 --- a/.tekton/retag-scanner-db.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: tekton.dev/v1 -kind: PipelineRun - -metadata: - annotations: - build.appstudio.openshift.io/repo: https://github.com/stackrox/stackrox?rev={{revision}} - build.appstudio.redhat.com/commit_sha: '{{revision}}' - build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' - build.appstudio.redhat.com/target_branch: '{{target_branch}}' - pipelinesascode.tekton.dev/max-keep-runs: "500" - pipelinesascode.tekton.dev/on-comment: "/konflux-retest retag-scanner-db" - # TODO(ROX-21073): re-enable for all PR branches - pipelinesascode.tekton.dev/on-cel-expression: | - ( - event == "push" && target_branch.matches("^(master|release-.*|refs/tags/.*)$") - ) || ( - event == "pull_request" && ( - target_branch.startsWith("release-") || - source_branch.matches("(konflux|renovate|appstudio|rhtap)") || - (has(body.pull_request) && has(body.pull_request.labels) && body.pull_request.labels.exists(l, l.name == "konflux-build")) - ) && body.action != "ready_for_review" - ) - # The empty `on-label` annotation is a workaround to make sure the pipeline gets triggered when the label gets first - # added to the PR. See the Slack tread linked from ROX-30580. - pipelinesascode.tekton.dev/on-label: "[]" - labels: - appstudio.openshift.io/application: acs - name: retag-scanner-db - namespace: rh-acs-tenant - -spec: - - params: - - name: git-url - value: '{{source_url}}' - - name: revision - value: '{{revision}}' - - name: input-image-repo - value: quay.io/rhacs-eng/release-scanner-db - - name: input-image-tag-makefile-target - value: scanner-tag - - name: output-image-repo - value: quay.io/rhacs-eng/release-scanner-db - - pipelineRef: - name: retag-pipeline - - taskRunTemplate: - serviceAccountName: build-pipeline-scanner-db - - timeouts: - tasks: 30m - # Reserve time for final tasks to run. - finally: 10m - pipeline: 40m - - workspaces: - - name: git-auth - secret: - secretName: '{{ git_auth_secret }}' diff --git a/.tekton/retag-scanner-slim.yaml b/.tekton/retag-scanner-slim.yaml deleted file mode 100644 index dcac863485fa7..0000000000000 --- a/.tekton/retag-scanner-slim.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: tekton.dev/v1 -kind: PipelineRun - -metadata: - annotations: - build.appstudio.openshift.io/repo: https://github.com/stackrox/stackrox?rev={{revision}} - build.appstudio.redhat.com/commit_sha: '{{revision}}' - build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' - build.appstudio.redhat.com/target_branch: '{{target_branch}}' - pipelinesascode.tekton.dev/max-keep-runs: "500" - pipelinesascode.tekton.dev/on-comment: "/konflux-retest retag-scanner-slim" - # TODO(ROX-21073): re-enable for all PR branches - pipelinesascode.tekton.dev/on-cel-expression: | - ( - event == "push" && target_branch.matches("^(master|release-.*|refs/tags/.*)$") - ) || ( - event == "pull_request" && ( - target_branch.startsWith("release-") || - source_branch.matches("(konflux|renovate|appstudio|rhtap)") || - (has(body.pull_request) && has(body.pull_request.labels) && body.pull_request.labels.exists(l, l.name == "konflux-build")) - ) && body.action != "ready_for_review" - ) - # The empty `on-label` annotation is a workaround to make sure the pipeline gets triggered when the label gets first - # added to the PR. See the Slack tread linked from ROX-30580. - pipelinesascode.tekton.dev/on-label: "[]" - labels: - appstudio.openshift.io/application: acs - name: retag-scanner-slim - namespace: rh-acs-tenant - -spec: - - params: - - name: git-url - value: '{{source_url}}' - - name: revision - value: '{{revision}}' - - name: input-image-repo - value: quay.io/rhacs-eng/release-scanner-slim - - name: input-image-tag-makefile-target - value: scanner-tag - - name: output-image-repo - value: quay.io/rhacs-eng/release-scanner-slim - - pipelineRef: - name: retag-pipeline - - taskRunTemplate: - serviceAccountName: build-pipeline-scanner-slim - - timeouts: - tasks: 30m - # Reserve time for final tasks to run. - finally: 10m - pipeline: 40m - - workspaces: - - name: git-auth - secret: - secretName: '{{ git_auth_secret }}' diff --git a/.tekton/retag-scanner.yaml b/.tekton/retag-scanner.yaml deleted file mode 100644 index 8875548f55572..0000000000000 --- a/.tekton/retag-scanner.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: tekton.dev/v1 -kind: PipelineRun - -metadata: - annotations: - build.appstudio.openshift.io/repo: https://github.com/stackrox/stackrox?rev={{revision}} - build.appstudio.redhat.com/commit_sha: '{{revision}}' - build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' - build.appstudio.redhat.com/target_branch: '{{target_branch}}' - pipelinesascode.tekton.dev/max-keep-runs: "500" - pipelinesascode.tekton.dev/on-comment: "/konflux-retest retag-scanner" - # TODO(ROX-21073): re-enable for all PR branches - pipelinesascode.tekton.dev/on-cel-expression: | - ( - event == "push" && target_branch.matches("^(master|release-.*|refs/tags/.*)$") - ) || ( - event == "pull_request" && ( - target_branch.startsWith("release-") || - source_branch.matches("(konflux|renovate|appstudio|rhtap)") || - (has(body.pull_request) && has(body.pull_request.labels) && body.pull_request.labels.exists(l, l.name == "konflux-build")) - ) && body.action != "ready_for_review" - ) - # The empty `on-label` annotation is a workaround to make sure the pipeline gets triggered when the label gets first - # added to the PR. See the Slack tread linked from ROX-30580. - pipelinesascode.tekton.dev/on-label: "[]" - labels: - appstudio.openshift.io/application: acs - name: retag-scanner - namespace: rh-acs-tenant - -spec: - - params: - - name: git-url - value: '{{source_url}}' - - name: revision - value: '{{revision}}' - - name: input-image-repo - value: quay.io/rhacs-eng/release-scanner - - name: input-image-tag-makefile-target - value: scanner-tag - - name: output-image-repo - value: quay.io/rhacs-eng/release-scanner - - pipelineRef: - name: retag-pipeline - - taskRunTemplate: - serviceAccountName: build-pipeline-scanner - - timeouts: - tasks: 30m - # Reserve time for final tasks to run. - finally: 10m - pipeline: 40m - - workspaces: - - name: git-auth - secret: - secretName: '{{ git_auth_secret }}' diff --git a/operator/konflux.bundle.Dockerfile b/operator/konflux.bundle.Dockerfile index 49cbd4ade5bb6..aee48825bd605 100644 --- a/operator/konflux.bundle.Dockerfile +++ b/operator/konflux.bundle.Dockerfile @@ -13,22 +13,6 @@ ARG RELATED_IMAGE_MAIN ENV RELATED_IMAGE_MAIN=$RELATED_IMAGE_MAIN RUN echo "Checking required RELATED_IMAGE_MAIN"; [[ "${RELATED_IMAGE_MAIN}" != "" ]] -ARG RELATED_IMAGE_SCANNER -ENV RELATED_IMAGE_SCANNER=$RELATED_IMAGE_SCANNER -RUN echo "Checking required RELATED_IMAGE_SCANNER"; [[ "${RELATED_IMAGE_SCANNER}" != "" ]] - -ARG RELATED_IMAGE_SCANNER_DB -ENV RELATED_IMAGE_SCANNER_DB=$RELATED_IMAGE_SCANNER_DB -RUN echo "Checking required RELATED_IMAGE_SCANNER_DB"; [[ "${RELATED_IMAGE_SCANNER_DB}" != "" ]] - -ARG RELATED_IMAGE_SCANNER_SLIM -ENV RELATED_IMAGE_SCANNER_SLIM=$RELATED_IMAGE_SCANNER_SLIM -RUN echo "Checking required RELATED_IMAGE_SCANNER_SLIM"; [[ "${RELATED_IMAGE_SCANNER_SLIM}" != "" ]] - -ARG RELATED_IMAGE_SCANNER_DB_SLIM -ENV RELATED_IMAGE_SCANNER_DB_SLIM=$RELATED_IMAGE_SCANNER_DB_SLIM -RUN echo "Checking required RELATED_IMAGE_SCANNER_DB_SLIM"; [[ "${RELATED_IMAGE_SCANNER_DB_SLIM}" != "" ]] - ARG RELATED_IMAGE_SCANNER_V4 ENV RELATED_IMAGE_SCANNER_V4=$RELATED_IMAGE_SCANNER_V4 RUN echo "Checking required RELATED_IMAGE_SCANNER_V4"; [[ "${RELATED_IMAGE_SCANNER_V4}" != "" ]] diff --git a/scripts/ci/bats/lib_release_version_test.bats b/scripts/ci/bats/lib_release_version_test.bats index 07a602fb32faa..fe66082173aef 100644 --- a/scripts/ci/bats/lib_release_version_test.bats +++ b/scripts/ci/bats/lib_release_version_test.bats @@ -85,7 +85,7 @@ function setup() { assert_output "" } -# check_collector_version(), check_scanner_version() tests +# check_collector_version(), check_fact_version() tests function make() { echo "${tags[$3]}" @@ -130,23 +130,3 @@ function make() { run check_fact_version assert_success } - -@test "spots scanner tag is a master commit" { - declare -A tags=( [scanner-tag]="3.45.x-12-g8a2e05d0ec") - run check_scanner_version - assert_failure - assert_output --partial 'Scanner tag does not look like a release tag' -} - -@test "spots scanner tag is a release candidate" { - declare -A tags=( [scanner-tag]="3.45.1-rc.1") - run check_scanner_version - assert_failure - assert_output --partial 'Scanner tag does not look like a release tag' -} - -@test "spots scanner tag is a release" { - declare -A tags=( [scanner-tag]="3.45.1") - run check_scanner_version - assert_success -} diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index af27a420983ee..3f7096f605151 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -554,8 +554,6 @@ push_matching_collector_scanner_images() { local main_tag main_tag="$(make --quiet --no-print-directory tag)" - local scanner_version - scanner_version="$(make --quiet --no-print-directory scanner-tag)" local collector_version collector_version="$(make --quiet --no-print-directory collector-tag)" local fact_version @@ -563,11 +561,6 @@ push_matching_collector_scanner_images() { registry_rw_login "${registry}" - _retag "${registry}/scanner:${scanner_version}" "${registry}/scanner:${main_tag}" - _retag "${registry}/scanner-db:${scanner_version}" "${registry}/scanner-db:${main_tag}" - _retag "${registry}/scanner-slim:${scanner_version}" "${registry}/scanner-slim:${main_tag}" - _retag "${registry}/scanner-db-slim:${scanner_version}" "${registry}/scanner-db-slim:${main_tag}" - _retag "${registry}/collector:${collector_version}" "${registry}/collector:${main_tag}" _retag "${registry}/fact:${fact_version}" "${registry}/fact:${main_tag}" @@ -1029,8 +1022,6 @@ stackrox-operator-index ${operator_metadata_tag} main ${tag} central-db ${tag} collector ${tag} -scanner ${tag} -scanner-db ${tag} scanner-v4 ${tag} scanner-v4-db ${tag} END @@ -1059,8 +1050,6 @@ stackrox-operator-index ${operator_metadata_tag} main ${tag} central-db ${tag} collector ${tag} -scanner ${tag} -scanner-db ${tag} scanner-v4 ${tag} scanner-v4-db ${tag} roxctl ${tag} @@ -1075,8 +1064,6 @@ release-main ${operator_controller_tag} release-central-db ${operator_controller_tag} release-collector ${operator_controller_tag} release-fact ${operator_controller_tag} -release-scanner ${operator_controller_tag} -release-scanner-db ${operator_controller_tag} release-scanner-v4 ${operator_controller_tag} release-scanner-v4-db ${operator_controller_tag} release-roxctl ${operator_controller_tag} @@ -1089,8 +1076,6 @@ main ${tag} central-db ${tag} collector ${tag} fact ${tag} -scanner ${tag} -scanner-db ${tag} scanner-v4 ${tag} scanner-v4-db ${tag} roxctl ${tag} @@ -1144,13 +1129,6 @@ check_rhacs_eng_image_exists() { [[ "$(jq -r '.tags | first | .name' <<<"$check")" == "$tag" ]] } -check_scanner_version() { - if ! is_release_version "$(make --quiet --no-print-directory scanner-tag)"; then - echo "::error::Scanner tag does not look like a release tag. Please update SCANNER_VERSION file before releasing." - exit 1 - fi -} - check_collector_version() { if ! is_release_version "$(make --quiet --no-print-directory collector-tag)"; then echo "::error::Collector tag does not look like a release tag. Please update COLLECTOR_VERSION file before releasing." diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index 901b33e277b25..cbe1d1ee0997c 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -128,10 +128,7 @@ deploy_earlier_postgres_central() { --set central.exposure.loadBalancer.enabled=true \ --set system.enablePodSecurityPolicies=false \ --set central.image.tag="${EARLIER_TAG}" \ - --set central.db.image.tag="${EARLIER_TAG}" \ - --set scanner.image.tag="$(cat SCANNER_VERSION)" \ - --set scanner.dbImage.tag="$(cat SCANNER_VERSION)" \ - --set scanner.resources.limits.memory="6Gi" + --set central.db.image.tag="${EARLIER_TAG}" # Installing this way returns faster than the scripts but everything isn't running when it finishes like with # the scripts. So we will give it a minute for things to get started before we proceed diff --git a/tests/upgrade/postgres_run.sh b/tests/upgrade/postgres_run.sh index c64e3621826bb..8d954ee285d85 100755 --- a/tests/upgrade/postgres_run.sh +++ b/tests/upgrade/postgres_run.sh @@ -11,7 +11,6 @@ EARLIER_TAG="4.6.2" EARLIER_SHA="ecff2a443c8b9a2dc7bf606162da89da81dd8e9e" CURRENT_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" COLLECTOR_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory collector-tag)"}" -SCANNER_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory scanner-tag)"}" PREVIOUS_RELEASES=("4.6.10" "4.7.9" "4.8.11" "4.9.11" "4.10.7" "4.11.3") # shellcheck source=../../scripts/lib.sh From 72a43c16893cf08a620d3ab3ce8e26b5f0e0dcd8 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Tue, 15 Sep 2026 18:14:00 +0200 Subject: [PATCH 311/638] chore(deps): upgrade go-github from v60.0.0 to v88.0.0 (#21259) Signed-off-by: Tomasz Janiszewski Co-authored-by: Claude Sonnet 4.5 --- .github/workflows/retest_periodic.yml | 3 +++ Makefile | 16 +++++++++++++++- go.mod | 2 +- go.sum | 4 ++-- tools/retest/github.go | 8 ++++---- tools/retest/github_test.go | 2 +- tools/retest/main.go | 7 +++++-- tools/retest/main_integration_test.go | 20 +++++++++----------- 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/.github/workflows/retest_periodic.yml b/.github/workflows/retest_periodic.yml index 60c49dd15f891..d30e223e91813 100644 --- a/.github/workflows/retest_periodic.yml +++ b/.github/workflows/retest_periodic.yml @@ -24,6 +24,9 @@ jobs: with: go-version-file: go.mod + - name: Test + run: cd tools/retest && go test ./... + - name: Retest env: GITHUB_TOKEN: ${{ secrets.RHACS_BOT_GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 9b913f0d2e4e9..387480c0b6c8b 100644 --- a/Makefile +++ b/Makefile @@ -559,6 +559,13 @@ swagger-docs: $(MERGED_API_SWAGGER_SPEC) $(MERGED_API_SWAGGER_SPEC_V2) UNIT_TEST_PACKAGES ?= ./... +# Root-module test discovery must not pass tests from nested Go modules to the +# root module's go list. Those modules are tested separately in their own +# working directories. +NESTED_GO_MODULE_DIRS := $(shell find . -mindepth 2 -name go.mod -exec dirname {} \; | sed 's@^\./@@') +NESTED_GO_MODULE_REGEX := $(shell printf '%s\n' $(NESTED_GO_MODULE_DIRS) | paste -sd'|' -) +NESTED_GO_MODULES_WITH_TESTS := $(shell for dir in $(NESTED_GO_MODULE_DIRS); do git ls-files "$$dir/*_test.go" | grep -q . && echo "$$dir"; done) + .PHONY: test-prep test-prep: @echo "+ $@" @@ -568,8 +575,15 @@ test-prep: go-unit-tests: build-prep test-prep set -o pipefail ; \ CGO_ENABLED=1 GOEXPERIMENT=cgocheck2 MUTEX_WATCHDOG_TIMEOUT_SECS=30 GOTAGS=$(GOTAGS),test scripts/go-test.sh -timeout 25m -race -cover -coverprofile test-output/coverage.out -v \ - $(shell git ls-files -- '*_test.go' | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list| grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ + $(shell git ls-files -- '*_test.go' | grep -v -E '^($(NESTED_GO_MODULE_REGEX))(/|$$)' | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list| grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ | tee $(GO_TEST_OUTPUT_PATH) + # Nested modules have independent coverage profiles, so keep their coverage + # out of the root profile while including their verbose output in JUnit. + @set -o pipefail; \ + for module_dir in $(NESTED_GO_MODULES_WITH_TESTS); do \ + echo "Run tests in $$module_dir"; \ + (cd "$$module_dir" && go test -buildvcs=false -race -v ./...) 2>&1 | tee -a $(GO_TEST_OUTPUT_PATH); \ + done # Exercise the logging package for all supported logging levels to make sure that initialization works properly @echo "Run log tests" for encoding in console json; do \ diff --git a/go.mod b/go.mod index d9c87c83ca3b5..85d98692d25c6 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/google/gnostic-models v0.7.1 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.21.9 - github.com/google/go-github/v60 v60.0.0 + github.com/google/go-github/v91 v91.0.0 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.23.0 github.com/gorilla/schema v1.4.1 diff --git a/go.sum b/go.sum index c127259592b73..1692220505635 100644 --- a/go.sum +++ b/go.sum @@ -693,8 +693,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/go-containerregistry v0.21.9 h1:F+D4uZ3iA3DLMJLfhaqMdHJbzeqm/216WGQq2dokuLs= github.com/google/go-containerregistry v0.21.9/go.mod h1:dP5XNKcL7kMFF/TB3LfvWmVhAcv7iqkHb3oDK8aauTo= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= -github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= -github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk= +github.com/google/go-github/v91 v91.0.0 h1:fpulREh37uBvOj4kF0vLuzjwJ1HHmFcAfquNjJfmpYs= +github.com/google/go-github/v91 v91.0.0/go.mod h1:rHtn7haKvmkTLhUK/aCQypiIj/suWSB3rnS+a1FdH6M= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= diff --git a/tools/retest/github.go b/tools/retest/github.go index 776ea0fc13e98..8c838a1a5018d 100644 --- a/tools/retest/github.go +++ b/tools/retest/github.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/google/go-github/v60/github" + "github.com/google/go-github/v91/github" ) func createComment(ctx context.Context, client *github.Client, prNumber int, comment string) { @@ -17,8 +17,8 @@ func createComment(ctx context.Context, client *github.Client, prNumber int, com return } log.Printf("#%d will be commented with: %s", prNumber, comment) - issueComment := &github.IssueComment{ - Body: &comment, + issueComment := github.IssueCommentRequest{ + Body: comment, } c, _, err := client.Issues.CreateComment(ctx, s, s, prNumber, issueComment) if err != nil { @@ -135,7 +135,7 @@ func statusesForPR(ctx context.Context, client *github.Client, url string) (map[ if err != nil { return nil, err } - _, err = client.Do(ctx, statusRequest, &statuses) + _, err = client.Do(statusRequest.WithContext(ctx), &statuses) if err != nil { return nil, err } diff --git a/tools/retest/github_test.go b/tools/retest/github_test.go index fabff57738d4e..0eb035553ceae 100644 --- a/tools/retest/github_test.go +++ b/tools/retest/github_test.go @@ -3,7 +3,7 @@ package main import ( "testing" - "github.com/google/go-github/v60/github" + "github.com/google/go-github/v91/github" "github.com/stretchr/testify/assert" ) diff --git a/tools/retest/main.go b/tools/retest/main.go index 982da62a7407a..9c493bc0152d2 100644 --- a/tools/retest/main.go +++ b/tools/retest/main.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/google/go-github/v60/github" + "github.com/google/go-github/v91/github" ) var allowedCheckFailurePrefixes = []string{ @@ -52,7 +52,10 @@ const s = "stackrox" func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - client := github.NewClient(nil).WithAuthToken(os.Getenv("GITHUB_TOKEN")) + client, err := github.NewClient(github.WithAuthToken(os.Getenv("GITHUB_TOKEN"))) + if err != nil { + log.Fatal(err.Error()) + } if err := run(ctx, client); err != nil { log.Fatal(err.Error()) } diff --git a/tools/retest/main_integration_test.go b/tools/retest/main_integration_test.go index 185430b8db45e..cb3c591564272 100644 --- a/tools/retest/main_integration_test.go +++ b/tools/retest/main_integration_test.go @@ -6,11 +6,11 @@ import ( "io" "net/http" "net/http/httptest" - "net/url" "testing" - "github.com/google/go-github/v60/github" + "github.com/google/go-github/v91/github" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIntegration(t *testing.T) { @@ -198,10 +198,9 @@ func TestIntegration(t *testing.T) { server = httptest.NewServer(http.HandlerFunc(handler)) t.Cleanup(server.Close) - client := github.NewClient(server.Client()) - parse, err := url.Parse(server.URL + "/") - assert.NoError(t, err) - client.BaseURL = parse + baseURL := server.URL + "/" + client, err := github.NewClient(github.WithHTTPClient(server.Client()), github.WithURLs(&baseURL, nil)) + require.NoError(t, err) err = run(context.Background(), client) assert.NoError(t, err) @@ -221,12 +220,11 @@ func TestGetStatuses(t *testing.T) { server = httptest.NewServer(http.HandlerFunc(handler)) t.Cleanup(server.Close) - client := github.NewClient(server.Client()) - baseUrl, err := url.Parse(server.URL + "/") - assert.NoError(t, err) - client.BaseURL = baseUrl + baseURL2 := server.URL + "/" + client, err := github.NewClient(github.WithHTTPClient(server.Client()), github.WithURLs(&baseURL2, nil)) + require.NoError(t, err) - statuses, err := statusesForPR(context.Background(), client, baseUrl.String()) + statuses, err := statusesForPR(context.Background(), client, baseURL2) assert.NoError(t, err) assert.Equal(t, map[string]jobState{"gke-upgrade-tests": jobOK}, statuses) } From 6243a894f368f79791165cee4466dc7d2e9da90f Mon Sep 17 00:00:00 2001 From: Brad Lugo Date: Tue, 15 Sep 2026 11:07:46 -0700 Subject: [PATCH 312/638] chore(deps): bump claircore to 1.6.0 (#22752) --- go.mod | 22 ++--- go.sum | 57 ++++++----- pkg/scannerv4/mappers/mappers.go | 37 +++++++- pkg/scannerv4/mappers/mappers_test.go | 95 +++++++++++++++++++ scanner/services/validators/validators.go | 20 +++- .../services/validators/validators_test.go | 8 ++ 6 files changed, 190 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 85d98692d25c6..e40e100e9ac30 100644 --- a/go.mod +++ b/go.mod @@ -103,7 +103,7 @@ require ( github.com/openshift/runtime-utils v0.0.0-20230921210328-7bdb5b9c177b github.com/operator-framework/helm-operator-plugins v0.0.0-00010101000000-000000000000 github.com/owenrumney/go-sarif/v2 v2.3.3 - github.com/package-url/packageurl-go v0.1.6 + github.com/package-url/packageurl-go v0.1.7 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca @@ -111,8 +111,8 @@ require ( github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.70.1 - github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96 - github.com/quay/claircore/toolkit v1.6.1 + github.com/quay/claircore v1.6.0 + github.com/quay/claircore/toolkit v1.7.0 github.com/quay/zlog/v2 v2.1.1 github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 github.com/russellhaering/gosaml2 v0.12.0 @@ -314,9 +314,9 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.29.0 // indirect + github.com/google/cel-go v0.31.0 // indirect github.com/google/go-querystring v1.2.0 // indirect - github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect + github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect github.com/gorilla/css v1.0.0 // indirect @@ -356,7 +356,7 @@ require ( github.com/matryer/is v1.4.1 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mdlayher/socket v0.6.0 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect @@ -446,13 +446,13 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect @@ -473,10 +473,10 @@ require ( k8s.io/streaming v0.36.4 // indirect kubevirt.io/containerized-data-importer-api v1.64.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect - modernc.org/libc v1.73.4 // indirect + modernc.org/libc v1.74.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.53.0 // indirect + modernc.org/sqlite v1.57.0 // indirect oras.land/oras-go/v2 v2.6.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index 1692220505635..a900153530157 100644 --- a/go.sum +++ b/go.sum @@ -666,8 +666,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= -github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= @@ -724,8 +724,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -965,8 +965,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1106,8 +1106,8 @@ github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo2 github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/package-url/packageurl-go v0.1.6 h1:YO3p6u1XmCUliivUg/qWphaY8vI6hxSnnPv7Bfg3m5M= -github.com/package-url/packageurl-go v0.1.6/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= +github.com/package-url/packageurl-go v0.1.7 h1:iFWg6tzAjLA6F/qX3M5nZaiMHJgc+p2zxVyr/fY+sZY= +github.com/package-url/packageurl-go v0.1.7/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -1171,11 +1171,11 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96 h1:bpYvJiQTRbsyTPmdXX/a+05Hy5nHtCcjoswOPR/LgjM= -github.com/quay/claircore v1.5.55-0.20260720230557-3f07716d8b96/go.mod h1:hOp0JqY8vOZPdBakFRt3JwG8q4K+gnRkG9RGzt1DoB4= +github.com/quay/claircore v1.6.0 h1:ZJKNIzCSjV4AdZEJJmS0SzVa0M6CQKhDvrzlVSgCkI4= +github.com/quay/claircore v1.6.0/go.mod h1:xtW1WFz0Mn6w5xqqTwwkA57RLXwCfYk9BMWT3M8hGXA= github.com/quay/claircore/toolkit v1.0.0/go.mod h1:3ELtgf92x7o1JCTSKVOAqhcnCTXc4s5qiGaEDx62i20= -github.com/quay/claircore/toolkit v1.6.1 h1:a3X1v28n5bo05T94cBlaoPU8xYCErRGPM3l6AlmYWj4= -github.com/quay/claircore/toolkit v1.6.1/go.mod h1:dTzlp1pgNchc+CN73HJiG9+e0ygU54QPt397eXnGGo4= +github.com/quay/claircore/toolkit v1.7.0 h1:flobJ7hTofiArNSBs1SKq8vDZ9h57dW02RxLyYn+bMw= +github.com/quay/claircore/toolkit v1.7.0/go.mod h1:1AzB56krwmcpGUak0hAOVbSrtzGywDQu5OTooDlKcb0= github.com/quay/claircore/updater/driver v1.0.0 h1:w7dAUjO3GBK6RjNyTZ2Kwz0l/Wuic3ykKJWPB80uA94= github.com/quay/claircore/updater/driver v1.0.0/go.mod h1:My5aY1wBpgxcWaHQZ0VoPmmj/EzuH7fq4ntzJbos4OI= github.com/quay/goval-parser v0.8.8 h1:Uf+f9iF2GIR5GPUY2pGoa9il2+4cdES44ZlM0mWm4cA= @@ -1464,8 +1464,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -1490,8 +1490,8 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= @@ -1500,8 +1500,8 @@ go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5d go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= @@ -1782,7 +1782,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -2186,20 +2185,20 @@ kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9u kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= -modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= -modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= -modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= +modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= +modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= -modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= -modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= +modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k= +modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -2208,8 +2207,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= -modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg= +modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/pkg/scannerv4/mappers/mappers.go b/pkg/scannerv4/mappers/mappers.go index 2476267e13547..1b3edc59b8005 100644 --- a/pkg/scannerv4/mappers/mappers.go +++ b/pkg/scannerv4/mappers/mappers.go @@ -45,6 +45,11 @@ const ( redhatErrataURLPrefix = "https://access.redhat.com/errata/" rhelRepositoryKey = "rhel-cpe-repository" + + // unsetCPE is the bound form of an unset CPE. Every released StackRox + // component emits and accepts this form, so keep it on the wire instead of + // the empty string ClairCore now produces for unset CPEs. + unsetCPE = "cpe:2.3:*:*:*:*:*:*:*:*:*:*:*" ) var ( @@ -257,6 +262,10 @@ func v4Package(p *claircore.Package) (*v4.Package, error) { if err != nil { return nil, err } + cpeStr, err := toCPEString(p.CPE) + if err != nil { + return nil, fmt.Errorf("package %q: %w", p.ID, err) + } return &v4.Package{ Id: p.ID, Name: p.Name, @@ -268,7 +277,7 @@ func v4Package(p *claircore.Package) (*v4.Package, error) { RepositoryHint: p.RepositoryHint, Module: p.Module, Arch: p.Arch, - Cpe: toCPEString(p.CPE), + Cpe: cpeStr, }, nil } @@ -322,6 +331,10 @@ func v4Distribution(d *claircore.Distribution) (*v4.Distribution, error) { if d == nil { return nil, nil } + cpeStr, err := toCPEString(d.CPE) + if err != nil { + return nil, fmt.Errorf("distribution %q: %w", d.ID, err) + } return &v4.Distribution{ Id: d.ID, Did: d.DID, @@ -330,7 +343,7 @@ func v4Distribution(d *claircore.Distribution) (*v4.Distribution, error) { VersionCodeName: d.VersionCodeName, VersionId: VersionID(d), Arch: d.Arch, - Cpe: toCPEString(d.CPE), + Cpe: cpeStr, PrettyName: d.PrettyName, }, nil } @@ -339,12 +352,16 @@ func v4Repository(r *claircore.Repository) (*v4.Repository, error) { if r == nil { return nil, nil } + cpeStr, err := toCPEString(r.CPE) + if err != nil { + return nil, fmt.Errorf("repository %q: %w", r.ID, err) + } return &v4.Repository{ Id: r.ID, Name: r.Name, Key: r.Key, Uri: r.URI, - Cpe: toCPEString(r.CPE), + Cpe: cpeStr, }, nil } @@ -825,8 +842,15 @@ func toPackageKind(s string) types.PackageKind { return k } -func toCPEString(c cpe.WFN) string { - return c.BindFS() +func toCPEString(c cpe.WFN) (string, error) { + if errors.Is(c.Valid(), cpe.ErrUnset) { + return unsetCPE, nil + } + b, err := c.AppendText(nil) + if err != nil { + return "", err + } + return string(b), nil } func toDigestString(digest claircore.Digest) string { @@ -834,6 +858,9 @@ func toDigestString(digest claircore.Digest) string { } func toClairCoreCPE(s string) (cpe.WFN, error) { + if s == "" { + return cpe.WFN{}, nil + } c, err := cpe.Unbind(s) if err != nil { return c, fmt.Errorf("%q: %s", s, strings.TrimPrefix(err.Error(), "cpe: ")) diff --git a/pkg/scannerv4/mappers/mappers_test.go b/pkg/scannerv4/mappers/mappers_test.go index 23916fe6fd0c1..c4d734fe57111 100644 --- a/pkg/scannerv4/mappers/mappers_test.go +++ b/pkg/scannerv4/mappers/mappers_test.go @@ -3,6 +3,7 @@ package mappers import ( "encoding/json" "slices" + "strconv" "strings" "testing" "time" @@ -3261,3 +3262,97 @@ func Test_PackageKindRoundTrip(t *testing.T) { }) } } + +func Test_toCPEString(t *testing.T) { + tests := []struct { + name string + arg cpe.WFN + want string + wantErr string + }{ + { + name: "when zero value then wildcard", + arg: cpe.WFN{}, + want: emptyCPE, + }, + { + name: "when wildcard then wildcard", + arg: cpe.MustUnbind(emptyCPE), + want: emptyCPE, + }, + { + name: "when set then bound", + arg: cpe.MustUnbind("cpe:/a:redhat:openshift:4.12::el8"), + want: "cpe:2.3:a:redhat:openshift:4.12:*:el8:*:*:*:*:*", + }, + { + name: "when invalid then error", + arg: cpe.WFN{Attr: [cpe.NumAttr]cpe.Value{cpe.Part: {Kind: cpe.ValueSet, V: "x"}}}, + wantErr: "disallowed value", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := toCPEString(tt.arg) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_toClairCoreCPE(t *testing.T) { + tests := []struct { + name string + arg string + want cpe.WFN + wantErr string + }{ + { + name: "when empty then zero value", + arg: "", + want: cpe.WFN{}, + }, + { + name: "when wildcard then no error", + arg: emptyCPE, + want: cpe.MustUnbind(emptyCPE), + }, + { + name: "when set then unbound", + arg: "cpe:2.3:a:redhat:openshift:4.12:*:el8:*:*:*:*:*", + want: cpe.MustUnbind("cpe:2.3:a:redhat:openshift:4.12:*:el8:*:*:*:*:*"), + }, + { + name: "when invalid then error", + arg: "not a cpe", + wantErr: `"not a cpe": `, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := toClairCoreCPE(tt.arg) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_CPERoundTrip(t *testing.T) { + for _, s := range []string{"", emptyCPE} { + t.Run("from "+strconv.Quote(s), func(t *testing.T) { + ccCPE, err := toClairCoreCPE(s) + require.NoError(t, err) + got, err := toCPEString(ccCPE) + require.NoError(t, err) + assert.Equal(t, emptyCPE, got) + }) + } +} diff --git a/scanner/services/validators/validators.go b/scanner/services/validators/validators.go index d912f65eca071..d3b6e15c938bd 100644 --- a/scanner/services/validators/validators.go +++ b/scanner/services/validators/validators.go @@ -154,6 +154,20 @@ func validateContents(contents *v4.Contents) error { return nil } +// validateCPE reports an error if s is a non-empty string that isn't a valid +// bound CPE WFN. An empty string is treated as "no CPE" and is not an error, +// matching the same convention mappers.toClairCoreCPE uses for the wire +// format: ClairCore's own zero-value WFN and this package's empty string are +// both accepted as "unset", even though StackRox's own mappers now always +// serialize "unset" as the bound wildcard CPE rather than "". +func validateCPE(s string) error { + if s == "" { + return nil + } + _, err := cpe.Unbind(s) + return err +} + func validateMap[T hasIDAndCPE](m map[string]T, fieldName string, validateF func(T) error) error { for k, v := range m { if reflect.ValueOf(v).IsZero() { @@ -162,8 +176,7 @@ func validateMap[T hasIDAndCPE](m map[string]T, fieldName string, validateF func if v.GetId() == "" { return fmt.Errorf("%s element %q: ID is empty", fieldName, k) } - _, err := cpe.Unbind(v.GetCpe()) - if err != nil { + if err := validateCPE(v.GetCpe()); err != nil { return fmt.Errorf("%s element %q: invalid CPE: %w", fieldName, k, err) } if err := validateF(v); err != nil { @@ -182,8 +195,7 @@ func validateList[T hasIDAndCPE](l []T, fieldName string, validateF func(T) erro if o.GetId() == "" { return fmt.Errorf("%s element #%d: Id is empty", fieldName, n) } - _, err := cpe.Unbind(o.GetCpe()) - if err != nil { + if err := validateCPE(o.GetCpe()); err != nil { return fmt.Errorf("%s element #%d (id: %q): invalid CPE: %w", fieldName, n, o.GetId(), err) } if err := validateF(o); err != nil { diff --git a/scanner/services/validators/validators_test.go b/scanner/services/validators/validators_test.go index 5150f073f46cd..d64c289f316a0 100644 --- a/scanner/services/validators/validators_test.go +++ b/scanner/services/validators/validators_test.go @@ -131,6 +131,14 @@ func Test_validateGetVulnerabilitiesRequest(t *testing.T) { addPackageDEPRECATED(&v4.Package{Id: "foobar", Cpe: "cpe:2.3:*:*:*:*:*:*:*:*:*:*:*"}), }, }, + "when a package has an empty CPE": { + // An empty CPE is treated the same as an unset one (e.g. the bound + // wildcard), not an invalid CPE. See mappers.toClairCoreCPE. + argOpts: []opts{ + addPackage(&v4.Package{Id: "foobar", Cpe: ""}), + addPackageDEPRECATED(&v4.Package{Id: "foobar", Cpe: ""}), + }, + }, "when a package has a source package with another source package": { wantErr: `Contents.Packages element "foo": package ID="foo" has a source with a source`, argOpts: []opts{ From 80da6f9b46403c50dbae6d75035d832e70975ad4 Mon Sep 17 00:00:00 2001 From: Surabhi Kumar Date: Tue, 15 Sep 2026 11:24:10 -0700 Subject: [PATCH 313/638] fixes(bug bash): Remove immediate action from lightspeed prompt (#22723) --- central/deployment/service/ai_prompt.go | 23 ---------- .../service/risk_ai_summary_test.go | 44 +++++++++++++++---- central/deployment/service/service_impl.go | 15 +++++++ 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/central/deployment/service/ai_prompt.go b/central/deployment/service/ai_prompt.go index 69efe593c68e0..90fa2cc3c8bb0 100644 --- a/central/deployment/service/ai_prompt.go +++ b/central/deployment/service/ai_prompt.go @@ -6,7 +6,6 @@ Cluster Security. A security operator is investigating a deployment flagged for review. AUDIENCE: Kubernetes cluster admin managing thousands of deployments. -They have oc access and want to reduce risk fast. TONE: Brief incident report. Short declarative sentences. No filler. @@ -16,10 +15,6 @@ risk score, and a stat summary bar (policy violations, CVE count, image age, component count) in the UI. Process arguments are redacted. Do NOT restate any of that. Start with the insight. -If the deployment has no significant risk factors (normalized score -below 5), state that in one sentence. Do not generate a risk -breakdown or actions. - Use these exact section labels with no additional text: SUMMARY @@ -33,24 +28,6 @@ sentence per bullet, max 20 words. Group related findings under one bullet (e.g., image age + image CVEs). Skip factors scoring below 1.5. -IMMEDIATE ACTIONS -Max 4 numbered items. Concrete steps to reduce the risk score now. -Each action: one sentence describing what to do, followed by the -oc command on its own line. - -Rules for commands: -- Each command must be a single line, runnable as-is. -- Use oc (not kubectl). Scope to the correct namespace and - deployment name from the data. -- Only use real oc subcommands: patch, set resources, set image, - create, delete, annotate, label. -- If a value depends on information not in the data (e.g., a new - image tag), do NOT include a command. State the action in plain - text and note what the user must determine first. -- If an action could break the workload, prefix the description - with "⚠ May affect application behavior:" and state what to - verify before applying. - CONSTRAINTS: - Plain text only. No markdown: no **, no backticks, no #. - Do NOT explain ACS, risk scoring, or how the system works. diff --git a/central/deployment/service/risk_ai_summary_test.go b/central/deployment/service/risk_ai_summary_test.go index faf65f83c5a32..2273fbc66c9f3 100644 --- a/central/deployment/service/risk_ai_summary_test.go +++ b/central/deployment/service/risk_ai_summary_test.go @@ -179,7 +179,7 @@ func TestGetDeploymentRiskAISummary_SensitiveFieldsStripped(t *testing.T) { risk := &storage.Risk{ Id: "risk-2", - Score: 4.0, + Score: 6.0, Subject: &storage.RiskSubject{ Id: "dep-2", Namespace: "operations", @@ -270,7 +270,7 @@ func TestGetDeploymentRiskAISummary_OLSError(t *testing.T) { ClusterName: "test-cluster", Type: "Deployment", } - risk := &storage.Risk{Score: 2.0} + risk := &storage.Risk{Score: 8.0} mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-3").Return(deployment, true, nil) mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) @@ -311,19 +311,45 @@ func TestGetDeploymentRiskAISummary_NilRisk(t *testing.T) { mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-4").Return(deployment, true, nil) mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(nil, false, nil) - olsMock := &mockOLSClient{ - response: &olsClient.QueryResponse{Response: "No significant risk factors identified."}, + // OLS client should NOT be called for nil/low risk - no mock needed + svc := &serviceImpl{ + datastore: mockDS, + risks: mockRisks, } + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-4"}) + require.NoError(t, err) + // Nil risk returns a specific message without calling OLS + assert.Equal(t, "No risk data available for this deployment.", resp.GetSummary()) +} + +func TestGetDeploymentRiskAISummary_LowRiskScore(t *testing.T) { + ctrl := gomock.NewController(t) + mockDS := deploymentMocks.NewMockDataStore(ctrl) + mockRisks := riskMocks.NewMockDataStore(ctrl) + + deployment := &storage.Deployment{ + Id: "dep-5", + Name: "low-risk-app", + Namespace: "default", + ClusterName: "test-cluster", + Type: "Deployment", + } + + risk := &storage.Risk{Score: 3.5} // Below threshold of 5.0 + + mockDS.EXPECT().GetDeployment(gomock.Any(), "dep-5").Return(deployment, true, nil) + mockRisks.EXPECT().GetRiskForDeployment(gomock.Any(), deployment).Return(risk, true, nil) + + // OLS client should NOT be called for low risk scores svc := &serviceImpl{ - datastore: mockDS, - risks: mockRisks, - lightspeedClient: olsMock, + datastore: mockDS, + risks: mockRisks, } - resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-4"}) + resp, err := svc.GetDeploymentRiskAISummary(context.Background(), &v1.ResourceByID{Id: "dep-5"}) require.NoError(t, err) - assert.Equal(t, "No significant risk factors identified.", resp.GetSummary()) + assert.Equal(t, "Skipping AI summary since this is a low risk deployment with normalized risk score below 5.", resp.GetSummary()) } func TestBuildSanitizedRiskContext_FieldSelection(t *testing.T) { diff --git a/central/deployment/service/service_impl.go b/central/deployment/service/service_impl.go index e23fc453a496b..3ba4f12a378f5 100644 --- a/central/deployment/service/service_impl.go +++ b/central/deployment/service/service_impl.go @@ -37,6 +37,7 @@ import ( const ( maxDeploymentsReturned = 1000 + lowRiskThreshold = 5.0 ) var log = logging.LoggerForModule() @@ -303,6 +304,20 @@ func (s *serviceImpl) GetDeploymentRiskAISummary(ctx context.Context, request *v return nil, err } + // Skip LLM call for deployments without risk data. + if risk == nil { + return &v1.DeploymentRiskAISummaryResponse{ + Summary: "No risk data available for this deployment.", + }, nil + } + + // Skip LLM call for low-risk deployments. + if risk.GetScore() < lowRiskThreshold { + return &v1.DeploymentRiskAISummaryResponse{ + Summary: "Skipping AI summary since this is a low risk deployment with normalized risk score below 5.", + }, nil + } + contextJSON, err := buildSanitizedRiskContext(deployment, risk) if err != nil { return nil, errors.Wrap(err, "building risk context for AI summary") From b5497cac848f68e5ded267162c4bfc23cc729a14 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Tue, 15 Sep 2026 21:21:55 +0200 Subject: [PATCH 314/638] fix(reports): validate schedule bounds and intervals (#22788) Signed-off-by: Tomasz Janiszewski --- .../service/v2/node/service_impl_test.go | 24 ++++--- .../reports/service/v2/service_impl_test.go | 12 ++-- .../validation/entity_scope_boundary_test.go | 63 +++++++++++++++++++ .../validation/filter_enum_boundary_test.go | 55 ++++++++++++++++ .../validation/notifier_type_boundary_test.go | 24 +++++++ .../validation/schedule_boundary_test.go | 28 +++++++++ central/reports/validation/validate.go | 52 ++++++++++++++- .../validation/validate_comprehensive_test.go | 6 +- central/reports/validation/validate_test.go | 6 ++ 9 files changed, 251 insertions(+), 19 deletions(-) create mode 100644 central/reports/validation/entity_scope_boundary_test.go create mode 100644 central/reports/validation/filter_enum_boundary_test.go create mode 100644 central/reports/validation/notifier_type_boundary_test.go create mode 100644 central/reports/validation/schedule_boundary_test.go diff --git a/central/reports/service/v2/node/service_impl_test.go b/central/reports/service/v2/node/service_impl_test.go index f641f171e586e..8de5b2e97c062 100644 --- a/central/reports/service/v2/node/service_impl_test.go +++ b/central/reports/service/v2/node/service_impl_test.go @@ -23,6 +23,7 @@ import ( "github.com/stackrox/rox/pkg/grpc/authn" mockIdentity "github.com/stackrox/rox/pkg/grpc/authn/mocks" "github.com/stackrox/rox/pkg/grpc/testutils" + "github.com/stackrox/rox/pkg/notifiers" postgresMocks "github.com/stackrox/rox/pkg/postgres/mocks" pgNotify "github.com/stackrox/rox/pkg/postgres/notify" "github.com/stackrox/rox/pkg/protoassert" @@ -103,7 +104,7 @@ func (s *NodeReportServiceTestSuite) getValidNodeReportConfig() *apiV2.ReportCon Rules: []*apiV2.EntityScopeRule{ { Entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, - Field: apiV2.ScopeField_FIELD_ID, + Field: apiV2.ScopeField_FIELD_NAME, Values: []*apiV2.RuleValue{ { Value: "cluster-1", @@ -160,7 +161,8 @@ func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration() { mockRole.EXPECT().GetAccessScope().Return(accessScope).Times(1) mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).Times(1) - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). DoAndReturn(func(_ context.Context, cfg *storage.ReportConfiguration) (string, error) { @@ -202,7 +204,8 @@ func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_GetAfterCre requestConfig := s.getValidNodeReportConfig() ctx := s.getContextForUser(creator) - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). Return(requestConfig.GetId(), nil).Times(1) s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), requestConfig.GetId()). @@ -228,7 +231,8 @@ func (s *NodeReportServiceTestSuite) TestPostNodeReportConfiguration_ValidationE }, } - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) _, err := s.service.PostNodeReportConfiguration(ctx, invalidConfig) s.Error(err) @@ -595,7 +599,8 @@ func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfiguration() { mockRole.EXPECT().GetAccessScope().Return(accessScope).AnyTimes() mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).AnyTimes() - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) existingConfig := &storage.ReportConfiguration{ Id: updateConfig.GetId(), @@ -989,7 +994,8 @@ func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfiguration_NotFound( updateConfig := s.getValidNodeReportConfig() - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), updateConfig.GetId()). Return(nil, false, nil).Times(1) @@ -1215,7 +1221,8 @@ func (s *NodeReportServiceTestSuite) TestPostNodeReportConfigurationWithCentralW mockRole.EXPECT().GetAccessScope().Return(accessScope).Times(1) mockID.EXPECT().Roles().Return([]permissions.ResolvedRole{mockRole}).Times(1) - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) s.reportConfigDataStore.EXPECT().AddReportConfiguration(gomock.Any(), gomock.Any()). DoAndReturn(func(_ context.Context, cfg *storage.ReportConfiguration) (string, error) { return cfg.GetId(), nil @@ -1247,7 +1254,8 @@ func (s *NodeReportServiceTestSuite) TestUpdateNodeReportConfigurationWithCentra updateConfig := s.getValidNodeReportConfig() ctx := s.getContextForUser(creator) - s.notifierDataStore.EXPECT().Exists(gomock.Any(), "email-notifier-id").Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), "email-notifier-id"). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), updateConfig.GetId()). Return(&storage.ReportConfiguration{ Id: updateConfig.GetId(), diff --git a/central/reports/service/v2/service_impl_test.go b/central/reports/service/v2/service_impl_test.go index 94f63715281e1..5f574ba81d094 100644 --- a/central/reports/service/v2/service_impl_test.go +++ b/central/reports/service/v2/service_impl_test.go @@ -26,6 +26,7 @@ import ( "github.com/stackrox/rox/pkg/grpc/authn" mockIdentity "github.com/stackrox/rox/pkg/grpc/authn/mocks" "github.com/stackrox/rox/pkg/grpc/testutils" + "github.com/stackrox/rox/pkg/notifiers" postgresMocks "github.com/stackrox/rox/pkg/postgres/mocks" pgNotify "github.com/stackrox/rox/pkg/postgres/notify" "github.com/stackrox/rox/pkg/protoassert" @@ -158,7 +159,8 @@ func (s *ReportServiceTestSuite) TestUpdateReportConfiguration_RejectsNodeType() s.reportConfigDataStore.EXPECT().GetReportConfiguration(gomock.Any(), "node-config"). Return(protoReportConfig, true, nil).Times(1) s.collectionDataStore.EXPECT().Exists(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() - s.notifierDataStore.EXPECT().Exists(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes() + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), gomock.Any()). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).AnyTimes() requestConfig := fixtures.GetValidV2ReportConfigWithMultipleNotifiers() requestConfig.Id = "node-config" @@ -850,11 +852,11 @@ func (s *ReportServiceTestSuite) upsertReportConfigTestCases(isUpdate bool) []up func (s *ReportServiceTestSuite) mockNotifierStoreCalls(reqNotifier *apiV2.NotifierConfiguration, notifierIDExits, isValidationError, isUpdate bool) { if notifierIDExits { - s.notifierDataStore.EXPECT().Exists(gomock.Any(), reqNotifier.GetEmailConfig().GetNotifierId()). - Return(true, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), reqNotifier.GetEmailConfig().GetNotifierId()). + Return(&storage.Notifier{Type: notifiers.EmailType}, true, nil).Times(1) } else { - s.notifierDataStore.EXPECT().Exists(gomock.Any(), reqNotifier.GetEmailConfig().GetNotifierId()). - Return(false, nil).Times(1) + s.notifierDataStore.EXPECT().GetScrubbedNotifier(gomock.Any(), reqNotifier.GetEmailConfig().GetNotifierId()). + Return(nil, false, nil).Times(1) } if !isValidationError && !isUpdate { diff --git a/central/reports/validation/entity_scope_boundary_test.go b/central/reports/validation/entity_scope_boundary_test.go new file mode 100644 index 0000000000000..26ddadc18da91 --- /dev/null +++ b/central/reports/validation/entity_scope_boundary_test.go @@ -0,0 +1,63 @@ +package validation + +import ( + "testing" + + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/errox" + "github.com/stretchr/testify/require" +) + +func TestValidateEntityScopeRejectsUnsupportedRulesAndValues(t *testing.T) { + tests := map[string]struct { + entity apiV2.ScopeEntity + field apiV2.ScopeField + value *apiV2.RuleValue + }{ + "unsupported cluster ID": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_ID, + value: &apiV2.RuleValue{Value: "cluster-id"}, + }, + "unknown entity": { + entity: apiV2.ScopeEntity(99), + field: apiV2.ScopeField_FIELD_NAME, + value: &apiV2.RuleValue{Value: "prod"}, + }, + "unknown field": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField(99), + value: &apiV2.RuleValue{Value: "prod"}, + }, + "unknown match type": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_NAME, + value: &apiV2.RuleValue{Value: "prod", MatchType: apiV2.MatchType(99)}, + }, + "nil value": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_NAME, + }, + "empty scalar value": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_NAME, + value: &apiV2.RuleValue{}, + }, + "invalid regex key": { + entity: apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, + field: apiV2.ScopeField_FIELD_LABEL, + value: &apiV2.RuleValue{Value: "[=prod", MatchType: apiV2.MatchType_REGEX}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := validateEntityScope(&apiV2.EntityScope{Rules: []*apiV2.EntityScopeRule{{ + Entity: tc.entity, + Field: tc.field, + Values: []*apiV2.RuleValue{tc.value}, + }}}) + require.ErrorIs(t, err, errox.InvalidArgs) + }) + } +} diff --git a/central/reports/validation/filter_enum_boundary_test.go b/central/reports/validation/filter_enum_boundary_test.go new file mode 100644 index 0000000000000..b1e707cbbc4f6 --- /dev/null +++ b/central/reports/validation/filter_enum_boundary_test.go @@ -0,0 +1,55 @@ +package validation + +import ( + "testing" + + reportConfigDSMocks "github.com/stackrox/rox/central/reports/config/datastore/mocks" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/errox" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestValidateAndGenerateReportRequestRejectsInvalidNotificationMethodBeforeLookup(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + validator := &Validator{reportConfigDatastore: reportConfigDSMocks.NewMockDataStore(ctrl)} + + _, err := validator.ValidateAndGenerateReportRequest( + "config", + storage.ReportStatus_NotificationMethod(99), + storage.ReportStatus_ON_DEMAND, + nil, + ) + require.ErrorIs(t, err, errox.InvalidArgs) +} + +func TestValidateImageFiltersRejectsInvalidEnumsAndDates(t *testing.T) { + tests := map[string]*apiV2.VulnerabilityReportFilters{ + "image type": { + ImageTypes: []apiV2.VulnerabilityReportFilters_ImageType{apiV2.VulnerabilityReportFilters_ImageType(99)}, + CvesSince: &apiV2.VulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + "fixability": { + ImageTypes: []apiV2.VulnerabilityReportFilters_ImageType{apiV2.VulnerabilityReportFilters_DEPLOYED}, + Fixability: apiV2.VulnerabilityReportFilters_Fixability(99), + CvesSince: &apiV2.VulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + "severity": { + ImageTypes: []apiV2.VulnerabilityReportFilters_ImageType{apiV2.VulnerabilityReportFilters_DEPLOYED}, + Severities: []apiV2.VulnerabilityReportFilters_VulnerabilitySeverity{apiV2.VulnerabilityReportFilters_VulnerabilitySeverity(99)}, + CvesSince: &apiV2.VulnerabilityReportFilters_AllVuln{AllVuln: true}, + }, + "nil start date": { + ImageTypes: []apiV2.VulnerabilityReportFilters_ImageType{apiV2.VulnerabilityReportFilters_DEPLOYED}, + CvesSince: &apiV2.VulnerabilityReportFilters_SinceStartDate{}, + }, + } + + for name, filters := range tests { + t.Run(name, func(t *testing.T) { + require.ErrorIs(t, (&Validator{}).validateImageFilters(filters), errox.InvalidArgs) + }) + } +} diff --git a/central/reports/validation/notifier_type_boundary_test.go b/central/reports/validation/notifier_type_boundary_test.go new file mode 100644 index 0000000000000..56825c94b2cd3 --- /dev/null +++ b/central/reports/validation/notifier_type_boundary_test.go @@ -0,0 +1,24 @@ +package validation + +import ( + "testing" + + notifierDSMocks "github.com/stackrox/rox/central/notifier/datastore/mocks" + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/errox" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestValidateEmailConfigRejectsNonEmailNotifier(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + notifierDS := notifierDSMocks.NewMockDataStore(ctrl) + notifierDS.EXPECT().GetScrubbedNotifier(gomock.Any(), "notifier").Return(&storage.Notifier{Type: "slack"}, true, nil) + + err := (&Validator{notifierDatastore: notifierDS}).validateEmailConfig(&apiV2.EmailNotifierConfiguration{ + NotifierId: "notifier", MailingLists: []string{"reader@example.com"}, + }) + require.ErrorIs(t, err, errox.InvalidArgs) +} diff --git a/central/reports/validation/schedule_boundary_test.go b/central/reports/validation/schedule_boundary_test.go new file mode 100644 index 0000000000000..459d8a7ca4729 --- /dev/null +++ b/central/reports/validation/schedule_boundary_test.go @@ -0,0 +1,28 @@ +package validation + +import ( + "testing" + + apiV2 "github.com/stackrox/rox/generated/api/v2" + "github.com/stackrox/rox/pkg/errox" + "github.com/stretchr/testify/require" +) + +func TestValidateScheduleRejectsInvalidBoundsAndIntervals(t *testing.T) { + tests := map[string]*apiV2.ReportSchedule{ + "negative hour": {IntervalType: apiV2.ReportSchedule_DAILY, Hour: -1}, + "hour 24": {IntervalType: apiV2.ReportSchedule_DAILY, Hour: 24}, + "negative minute": {IntervalType: apiV2.ReportSchedule_DAILY, Minute: -1}, + "minute 60": {IntervalType: apiV2.ReportSchedule_DAILY, Minute: 60}, + "unknown interval": { + IntervalType: apiV2.ReportSchedule_IntervalType(99), + }, + } + + for name, schedule := range tests { + t.Run(name, func(t *testing.T) { + err := (&Validator{}).validateSchedule(&apiV2.ReportConfiguration{Schedule: schedule}) + require.ErrorIs(t, err, errox.InvalidArgs) + }) + } +} diff --git a/central/reports/validation/validate.go b/central/reports/validation/validate.go index fef9a6ea322fe..4c00f9431d603 100644 --- a/central/reports/validation/validate.go +++ b/central/reports/validation/validate.go @@ -23,6 +23,7 @@ import ( "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/grpc/authn" + "github.com/stackrox/rox/pkg/notifiers" "github.com/stackrox/rox/pkg/protocompat" "github.com/stackrox/rox/pkg/sac" "github.com/stackrox/rox/pkg/search" @@ -83,8 +84,14 @@ func (v *Validator) validateSchedule(config *apiV2.ReportConfiguration) error { if schedule == nil { return nil } + if schedule.GetHour() < 0 || schedule.GetHour() > 23 { + return errox.InvalidArgs.New("schedule hour must be within 0-23") + } + if schedule.GetMinute() < 0 || schedule.GetMinute() > 59 { + return errox.InvalidArgs.New("schedule minute must be within 0-59") + } switch schedule.GetIntervalType() { - case apiV2.ReportSchedule_UNSET: + default: return errox.InvalidArgs.New("report configuration schedule must be one of DAILY, WEEKLY, or MONTHLY") case apiV2.ReportSchedule_DAILY: if schedule.GetDaysOfWeek() != nil || schedule.GetDaysOfMonth() != nil { @@ -158,13 +165,16 @@ func (v *Validator) validateEmailConfig(emailConfig *apiV2.EmailNotifierConfigur } // Use allAccessCtx since report creator/updater might not have permissions for integrationSAC - exists, err := v.notifierDatastore.Exists(allAccessCtx, emailConfig.GetNotifierId()) + notifier, exists, err := v.notifierDatastore.GetScrubbedNotifier(allAccessCtx, emailConfig.GetNotifierId()) if err != nil { return errors.Errorf("Error looking up attached notifier, Notifier ID: %s, Error: %s", emailConfig.GetNotifierId(), err) } if !exists { return errors.Wrapf(errox.NotFound, "Notifier with ID %s not found.", emailConfig.GetNotifierId()) } + if notifier.GetType() != notifiers.EmailType && notifier.GetType() != notifiers.ACSCSEmailType { + return errox.InvalidArgs.New("report configuration requires an email notifier") + } return nil } @@ -249,12 +259,19 @@ func validateEntityScope(es *apiV2.EntityScope) error { } seen := set.NewSet[entityFieldKey]() for _, rule := range es.GetRules() { - if rule.GetEntity() == apiV2.ScopeEntity_SCOPE_ENTITY_UNSET { + switch rule.GetEntity() { + case apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, apiV2.ScopeEntity_SCOPE_ENTITY_NAMESPACE, apiV2.ScopeEntity_SCOPE_ENTITY_DEPLOYMENT: + default: return errox.InvalidArgs.Newf("unexpected entity scope rule: %s", rule.GetEntity()) } if rule.GetField() == apiV2.ScopeField_FIELD_UNSET { return errox.InvalidArgs.Newf("unexpected entity in scope rule for %s with an unset field", rule.GetEntity()) } + switch rule.GetField() { + case apiV2.ScopeField_FIELD_NAME, apiV2.ScopeField_FIELD_LABEL, apiV2.ScopeField_FIELD_ANNOTATION: + default: + return errox.InvalidArgs.Newf("unsupported field %s for entity scope", rule.GetField()) + } // Cluster annotation is not indexed and therefore unsupported. if rule.GetEntity() == apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER && rule.GetField() == apiV2.ScopeField_FIELD_ANNOTATION { return errox.InvalidArgs.New("annotation field is not supported for cluster entity scope rules") @@ -271,6 +288,12 @@ func validateEntityScope(es *apiV2.EntityScope) error { isMapField := rule.GetField() == apiV2.ScopeField_FIELD_LABEL || rule.GetField() == apiV2.ScopeField_FIELD_ANNOTATION for _, rv := range rule.GetValues() { valOfValue := rv.GetValue() + if valOfValue == "" { + return errox.InvalidArgs.New("entity scope rule values must not be empty") + } + if rv.GetMatchType() != apiV2.MatchType_EXACT && rv.GetMatchType() != apiV2.MatchType_REGEX { + return errox.InvalidArgs.Newf("unsupported match type %s", rv.GetMatchType()) + } if isMapField { mapKey, mapValue, found := strings.Cut(valOfValue, "=") if !found { @@ -281,6 +304,8 @@ func validateEntityScope(es *apiV2.EntityScope) error { if errs := k8sValidation.IsLabelKey(mapKey); len(errs) > 0 { return errox.InvalidArgs.Newf("invalid %v key %q: %s", rule.GetField(), mapKey, strings.Join(errs, "; ")) } + } else if _, err := regexp.Compile(mapKey); err != nil { + return errox.InvalidArgs.CausedByf("invalid regex key %q: %v", mapKey, err) } valOfValue = mapValue } @@ -314,6 +339,24 @@ func (v *Validator) validateImageFilters(filters *apiV2.VulnerabilityReportFilte return errox.InvalidArgs.New("vulnerability report filters should specify which image types to scan for CVEs; " + "the valid options are 'DEPLOYED' and 'WATCHED'") } + for _, imageType := range filters.GetImageTypes() { + if _, ok := apiV2.VulnerabilityReportFilters_ImageType_name[int32(imageType)]; !ok { + return errox.InvalidArgs.Newf("unsupported image type %d", imageType) + } + } + if _, ok := apiV2.VulnerabilityReportFilters_Fixability_name[int32(filters.GetFixability())]; !ok { + return errox.InvalidArgs.Newf("unsupported fixability %d", filters.GetFixability()) + } + for _, severity := range filters.GetSeverities() { + if _, ok := apiV2.VulnerabilityReportFilters_VulnerabilitySeverity_name[int32(severity)]; !ok { + return errox.InvalidArgs.Newf("unsupported severity %d", severity) + } + } + if since, ok := filters.GetCvesSince().(*apiV2.VulnerabilityReportFilters_SinceStartDate); ok { + if since.SinceStartDate == nil || since.SinceStartDate.CheckValid() != nil { + return errox.InvalidArgs.New("invalid CVE start date") + } + } if filters.GetCvesSince() == nil { return errox.InvalidArgs.New("vulnerability report filters must specify how far back in time to look for CVEs; " + @@ -363,6 +406,9 @@ func (v *Validator) ValidateAndGenerateReportRequest( requestType storage.ReportStatus_RunMethod, requesterID authn.Identity, ) (*reportGen.ReportRequest, error) { + if notificationMethod != storage.ReportStatus_EMAIL && notificationMethod != storage.ReportStatus_DOWNLOAD { + return nil, errox.InvalidArgs.Newf("unsupported notification method %d", notificationMethod) + } config, found, err := v.reportConfigDatastore.GetReportConfiguration(allAccessCtx, configID) if err != nil { return nil, errors.Wrapf(err, "Error finding report configuration %s", configID) diff --git a/central/reports/validation/validate_comprehensive_test.go b/central/reports/validation/validate_comprehensive_test.go index 868227e87a24f..809b8c9a66be6 100644 --- a/central/reports/validation/validate_comprehensive_test.go +++ b/central/reports/validation/validate_comprehensive_test.go @@ -161,7 +161,7 @@ func TestValidateNotifiers(t *testing.T) { }, }, setupMocks: func() { - notifierDS.EXPECT().Exists(gomock.Any(), "notifier-1").Return(true, nil) + notifierDS.EXPECT().GetScrubbedNotifier(gomock.Any(), "notifier-1").Return(&storage.Notifier{Type: "email"}, true, nil) }, expectError: false, }, @@ -221,7 +221,7 @@ func TestValidateEmailConfig(t *testing.T) { MailingLists: []string{"user@example.com"}, }, setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) { - notifierDS.EXPECT().Exists(gomock.Any(), "missing-notifier").Return(false, nil) + notifierDS.EXPECT().GetScrubbedNotifier(gomock.Any(), "missing-notifier").Return(nil, false, nil) }, expectError: true, errContains: "not found", @@ -234,7 +234,7 @@ func TestValidateEmailConfig(t *testing.T) { CustomBody: "Report body", }, setupMocks: func(notifierDS *notifierDSMocks.MockDataStore) { - notifierDS.EXPECT().Exists(gomock.Any(), "notifier-1").Return(true, nil) + notifierDS.EXPECT().GetScrubbedNotifier(gomock.Any(), "notifier-1").Return(&storage.Notifier{Type: "email"}, true, nil) }, expectError: false, }, diff --git a/central/reports/validation/validate_test.go b/central/reports/validation/validate_test.go index 3328409deffcd..da51f83371d26 100644 --- a/central/reports/validation/validate_test.go +++ b/central/reports/validation/validate_test.go @@ -52,6 +52,12 @@ func TestValidateEntityScope(t *testing.T) { ), expectError: false, }, + "label key with empty value is valid": { + scope: makeEntityScope( + makeRule(apiV2.ScopeEntity_SCOPE_ENTITY_NAMESPACE, apiV2.ScopeField_FIELD_LABEL, exactValue("required=")), + ), + expectError: false, + }, "valid multiple rules different entities": { scope: makeEntityScope( makeRule(apiV2.ScopeEntity_SCOPE_ENTITY_CLUSTER, apiV2.ScopeField_FIELD_NAME, exactValue("prod")), From dd5ae71f3fbb350fcef74566f45101010cd0029d Mon Sep 17 00:00:00 2001 From: David Vail Date: Tue, 15 Sep 2026 15:55:36 -0400 Subject: [PATCH 315/638] chore(ui): Mark Collections as deprecated in the UI (#22313) --- .../Containers/Collections/CollectionsTablePage.tsx | 10 ++++++++++ .../MainPage/Navigation/NavigationSidebar.tsx | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/apps/platform/src/Containers/Collections/CollectionsTablePage.tsx b/ui/apps/platform/src/Containers/Collections/CollectionsTablePage.tsx index ebc8a4e53437d..2bb533d8852f5 100644 --- a/ui/apps/platform/src/Containers/Collections/CollectionsTablePage.tsx +++ b/ui/apps/platform/src/Containers/Collections/CollectionsTablePage.tsx @@ -81,6 +81,16 @@ function CollectionsTablePage({ hasWriteAccessForCollections }: CollectionsTable return ( <> + + Collection scope is deprecated for scheduled image vulnerability reports. To prepare + for future updates, we recommend updating each scheduled report in Vulnerability + reporting to use Custom scope. + diff --git a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx index d5012bbeaae06..ba6e410238455 100644 --- a/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx +++ b/ui/apps/platform/src/Containers/MainPage/Navigation/NavigationSidebar.tsx @@ -247,7 +247,9 @@ function getNavDescriptions(isFeatureFlagEnabled: IsFeatureFlagEnabled): NavDesc }, { type: 'link', - content: 'Collections', + content: ( + Collections + ), path: collectionsBasePath, routeKey: 'collections', }, From a9a70f174ba946404e7c8d5ba6b796fea194d276 Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:56:42 +0200 Subject: [PATCH 316/638] ROX-33973: Remove OIDC AuthProvider with role resolution from ACM (#22842) --- central/main.go | 4 -- .../oidc/backend_factory_impl.go | 4 -- pkg/auth/authproviders/registry_impl.go | 49 +++---------------- .../AuthProviders/AuthProviderForm.tsx | 43 +++++++--------- .../AuthProviders/ConfigurationFormFields.tsx | 2 +- .../AuthProviders/authProviders.utils.ts | 6 +-- .../platform/src/constants/accessControl.ts | 1 - .../src/services/AuthService/AuthService.ts | 3 +- 8 files changed, 30 insertions(+), 82 deletions(-) diff --git a/central/main.go b/central/main.go index 11bf834c78731..6b375b4956f24 100755 --- a/central/main.go +++ b/central/main.go @@ -590,10 +590,6 @@ func startGRPCServer() { } } - if features.ACMAccessControlDelegation.Enabled() { - authProviderBackendFactories[oidc.TypeNameWithACMAccessControlDelegation] = oidc.NewFactory - } - for typeName, factoryCreator := range authProviderBackendFactories { if err := registry.RegisterBackendFactory(authProviderRegisteringCtx, typeName, factoryCreator); err != nil { log.Panicf("Could not register %s auth provider factory: %v", typeName, err) diff --git a/pkg/auth/authproviders/oidc/backend_factory_impl.go b/pkg/auth/authproviders/oidc/backend_factory_impl.go index e3f16b348f5d1..2e745e545063a 100644 --- a/pkg/auth/authproviders/oidc/backend_factory_impl.go +++ b/pkg/auth/authproviders/oidc/backend_factory_impl.go @@ -20,10 +20,6 @@ const ( // TypeName is the standard type name for OIDC auth providers. TypeName = "oidc" - // TypeNameWithACMAccessControlDelegation is the type name for OIDC auth providers - // with role resolution from ACM. - TypeNameWithACMAccessControlDelegation = authproviders.OIDCTypeNameWithACMRoles - callbackRelativePath = "callback" nonceTTL = 1 * time.Minute diff --git a/pkg/auth/authproviders/registry_impl.go b/pkg/auth/authproviders/registry_impl.go index c09c10178af9d..2e01d81d1a329 100644 --- a/pkg/auth/authproviders/registry_impl.go +++ b/pkg/auth/authproviders/registry_impl.go @@ -24,7 +24,6 @@ import ( const ( OpenShiftTypeNameWithACMAccessControlDelegation = "openshift-with-acm-roles" - OIDCTypeNameWithACMRoles = "oidc-with-acm-roles" ) var ( @@ -382,12 +381,6 @@ func (r *registryImpl) issueTokenForResponse(ctx context.Context, provider Provi return nil, nil, err } roxClaims.InternalRoles = roles - case OIDCTypeNameWithACMRoles: - roles, err := getRolesForOIDCResponse(ctx, authResp, r.clusterResolver) - if err != nil { - return nil, nil, err - } - roxClaims.InternalRoles = roles default: roxClaims.ExternalUser = authResp.Claims } @@ -460,45 +453,13 @@ func getRolesForOpenshiftResponse( if err != nil { return nil, errox.InvalidArgs.CausedByf("failed to parse token: %v", err) } - acmClientObj, err := getACMClientForToken(tokenData.AccessToken) - if err != nil { - return nil, errors.Wrap(err, "failed to get ACM client") - } - roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) - if err != nil { - return nil, errors.Wrap(err, "failed to fetch role information") - } - return roles, nil -} - -func getRolesForOIDCResponse( - ctx context.Context, - authResp *AuthResponse, - clusterIDResolver tokens.ClusterResolver, -) ([]*tokens.InternalRole, error) { - if authResp == nil { - return nil, errox.InvalidArgs.CausedBy("auth response must not be nil") - } - acmClientObj, err := getACMClientForToken(authResp.IdpToken) - if err != nil { - return nil, errors.Wrap(err, "failed to get ACM client") - } - roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) - if err != nil { - return nil, errors.Wrap(err, "failed to fetch role information") - } - return roles, nil - -} - -func getACMClientForToken(token string) (*acm.Client, error) { // Retrieve OpenShift cluster config cfg, err := rest.InClusterConfig() if err != nil { return nil, errors.Wrap(err, "failed to get cluster config") } // Enrich the config with the OpenShift Auth Token - cfg.BearerToken = token + cfg.BearerToken = tokenData.AccessToken // Clear BearerTokenFile so that BearerToken takes precedence. // InClusterConfig() sets BearerTokenFile to the service account token, // which would override the user's OAuth token we just set. @@ -507,15 +468,17 @@ func getACMClientForToken(token string) (*acm.Client, error) { if err != nil { return nil, errors.Wrap(err, "failed to instantiate ACM client") } - return acmClientObj, nil + roles, err := externalrolebroker.GetResolvedRolesFromACM(ctx, acmClientObj, clusterIDResolver) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch role information") + } + return roles, nil } func isACMRoleDelegationProvider(provider Provider) bool { switch provider.Type() { case OpenShiftTypeNameWithACMAccessControlDelegation: return true - case OIDCTypeNameWithACMRoles: - return true } return false } diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx index 58a1e051e6a98..620d4ca201152 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/AuthProviderForm.tsx @@ -94,8 +94,7 @@ function testModeSupported(provider) { provider.type === 'oidc' || provider.type === 'saml' || provider.type === 'openshift' || - provider.type === 'openshift-with-acm-roles' || - provider.type === 'oidc-with-acm-roles' + provider.type === 'openshift-with-acm-roles' ); } @@ -168,7 +167,7 @@ function AuthProviderForm({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .when('type', { - is: (type) => type === 'oidc' || type === 'oidc-with-acm-roles', + is: (type) => type === 'oidc', then: (configSchema) => configSchema.shape({ client_id: yup.string().required('A client ID is required.'), @@ -533,8 +532,7 @@ function AuthProviderForm({

    - {(selectedAuthProvider.type === 'oidc' || - selectedAuthProvider.type === 'oidc-with-acm-roles') && ( + {selectedAuthProvider.type === 'oidc' && ( )} - {(selectedAuthProvider.type === 'oidc' || - selectedAuthProvider.type === 'oidc-with-acm-roles') && ( + {selectedAuthProvider.type === 'oidc' && ( )} - {selectedAuthProvider.type !== 'openshift-with-acm-roles' && - selectedAuthProvider.type !== 'oidc-with-acm-roles' && ( - - - - )} - {(selectedAuthProvider.type === 'openshift-with-acm-roles' || - selectedAuthProvider.type === 'oidc-with-acm-roles') && ( + {selectedAuthProvider.type !== 'openshift-with-acm-roles' && ( + + + + )} + {selectedAuthProvider.type === 'openshift-with-acm-roles' && (
    )} - {(type === 'oidc' || type === 'oidc-with-acm-roles') && ( + {type === 'oidc' && ( <> diff --git a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts index bce738d2ff708..d0112a639b1f9 100644 --- a/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts +++ b/ui/apps/platform/src/Containers/AccessControl/AuthProviders/authProviders.utils.ts @@ -19,7 +19,7 @@ export function transformInitialValues( // TODO-ivan: eventually logic for different auth provider type should live // with the form component that renders form for the corresponding auth provider // type, probably makes sense to refactor after moving away from redux-form - if (initialValues.type === 'oidc' || initialValues.type === 'oidc-with-acm-roles') { + if (initialValues.type === 'oidc') { const alteredConfig = { ...initialValues.config }; // backend doesn't return the exact value for the client secret for the security reasons, @@ -73,7 +73,7 @@ function populateDefaultValues(authProvider: AuthProvider): AuthProvider { const newInitialValues: DisplayedAuthProvider = { ...authProvider }; newInitialValues.uiEndpoint = window.location.host; newInitialValues.enabled = true; - if (authProvider.type === 'oidc' || authProvider.type === 'oidc-with-acm-roles') { + if (authProvider.type === 'oidc') { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore newInitialValues.config = { @@ -128,7 +128,7 @@ export function transformValuesBeforeSaving( | Group[] | undefined > { - if (values.type === 'oidc' || values.type === 'oidc-with-acm-roles') { + if (values.type === 'oidc') { const alteredConfig = { ...(values.config as AuthProviderConfig) }; // if client secret is stored on the backend and user didn't enter any value, diff --git a/ui/apps/platform/src/constants/accessControl.ts b/ui/apps/platform/src/constants/accessControl.ts index a3d3fcda998f0..ade3865244ec7 100644 --- a/ui/apps/platform/src/constants/accessControl.ts +++ b/ui/apps/platform/src/constants/accessControl.ts @@ -15,7 +15,6 @@ export const authProviderLabels = { iap: 'Google IAP', openshift: 'OpenShift Auth', 'openshift-with-acm-roles': 'OpenShift Auth with ACM Roles', - 'oidc-with-acm-roles': 'OpenID Connect with ACM Roles', }; export const oidcCallbackModes = [ diff --git a/ui/apps/platform/src/services/AuthService/AuthService.ts b/ui/apps/platform/src/services/AuthService/AuthService.ts index 14aa760c44d68..ca9a50a4d82ef 100644 --- a/ui/apps/platform/src/services/AuthService/AuthService.ts +++ b/ui/apps/platform/src/services/AuthService/AuthService.ts @@ -53,8 +53,7 @@ export type AuthProviderType = | 'userpki' | 'iap' | 'openshift' - | 'openshift-with-acm-roles' - | 'oidc-with-acm-roles'; + | 'openshift-with-acm-roles'; export type AuthProviderConfig = Record< string, From c12feff0d7f3f6ef1a5c7c138cf964d9107d3452 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:31:03 -0500 Subject: [PATCH 317/638] ROX-36974: Prevent reports wizard crash (#22844) --- .../Wizard/ImageVulnerabilityReportWizard.tsx | 6 +++--- .../Configurations/Wizard/NodeVulnerabilityReportWizard.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx index 59343c86727a5..4cece0984fbcb 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/ImageVulnerabilityReports/Wizard/ImageVulnerabilityReportWizard.tsx @@ -154,11 +154,11 @@ function ImageVulnerabilityReportWizard({ }, }); - const { dirty, isSubmitting, isValid, submitForm, validateForm, values } = formik; + const { dirty, isSubmitting, isValid, submitForm, validateForm } = formik; useEffect(() => { - validateForm(values).catch(() => {}); - }, [stepId, validateForm, values]); + validateForm().catch(() => {}); + }, [stepId, validateForm]); function isStepValid(id: WizardStepId): boolean { try { diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizard.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizard.tsx index 9ed5630937179..ca8d425bf12b5 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizard.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/Reports/NodeVulnerabilityReports/Configurations/Wizard/NodeVulnerabilityReportWizard.tsx @@ -134,11 +134,11 @@ function NodeVulnerabilityReportWizard({ }, }); - const { dirty, isSubmitting, isValid, submitForm, validateForm, values } = formik; + const { dirty, isSubmitting, isValid, submitForm, validateForm } = formik; useEffect(() => { - validateForm(values).catch(() => {}); - }, [stepId, validateForm, values]); + validateForm().catch(() => {}); + }, [stepId, validateForm]); function isStepValid(id: WizardStepId): boolean { try { From f9225dee613806f4acf0cd3908fac474e2b21ddc Mon Sep 17 00:00:00 2001 From: Brad Lugo Date: Tue, 15 Sep 2026 14:15:47 -0700 Subject: [PATCH 318/638] ROX-28373: Enable CISA KEV reporting by default (#22835) Co-authored-by: AJ Heflin --- .../scheduler/v2/reportgenerator/csv_gen.go | 8 ++- .../v2/reportgenerator/csv_gen_test.go | 72 ++++++++++++++----- ...report_gen_transaction_integration_test.go | 52 +++++++------- pkg/features/list.go | 2 +- 4 files changed, 91 insertions(+), 43 deletions(-) diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen.go b/central/reports/scheduler/v2/reportgenerator/csv_gen.go index 45da7e5234165..490b53aa6ed9f 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen.go @@ -92,7 +92,13 @@ func formatCSVRow(r *ImageCVEQueryResponse) []string { } else { cisaKev = "Not Available" } - csvRow = append(csvRow, cisaKev) + var knownRansomware string + if r.GetKnownRansomwareCampaign() != nil { + knownRansomware = strconv.FormatBool(*r.GetKnownRansomwareCampaign()) + } else { + knownRansomware = "Not Available" + } + csvRow = append(csvRow, cisaKev, knownRansomware) } csvRow = append(csvRow, diff --git a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go index 76f479f02c936..3de7b466a8b13 100644 --- a/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go +++ b/central/reports/scheduler/v2/reportgenerator/csv_gen_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -62,27 +63,64 @@ func TestFormatCSVRow(t *testing.T) { Link: "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", } - row := formatCSVRow(r) - assert.Equal(t, len(csvHeader), len(row), "row should have same number of columns as header") - assert.Equal(t, "test-cluster", row[0]) - assert.Equal(t, "test-ns", row[1]) - assert.Equal(t, "test-deploy", row[2]) - assert.Equal(t, "CVE-2024-1234", row[6]) - assert.Equal(t, "true", row[7]) - assert.Equal(t, "CRITICAL", row[9]) - assert.Equal(t, "95.000", row[12]) - assert.Equal(t, "Not Available", row[14]) // Image Created Date (nil → "Not Available") - assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[15]) + t.Run("KEV disabled", func(t *testing.T) { + t.Setenv(features.KnownExploitedVulnerabilities.EnvVar(), "false") + + row := formatCSVRow(r) + assert.Equal(t, len(formatCol()), len(row), "row should have same number of columns as header") + assert.Equal(t, "test-cluster", row[0]) + assert.Equal(t, "test-ns", row[1]) + assert.Equal(t, "test-deploy", row[2]) + assert.Equal(t, "CVE-2024-1234", row[6]) + assert.Equal(t, "true", row[7]) + assert.Equal(t, "CRITICAL", row[9]) + assert.Equal(t, "95.000", row[12]) + assert.Equal(t, "Not Available", row[14]) // Image Created Date (nil → "Not Available") + assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[15]) + }) + + t.Run("KEV enabled", func(t *testing.T) { + t.Setenv(features.KnownExploitedVulnerabilities.EnvVar(), "true") + + // The two KEV columns are inserted after EPSS (index 12), shifting all + // trailing columns right by two. + row := formatCSVRow(r) + assert.Equal(t, len(formatCol()), len(row), "row should have same number of columns as header") + assert.Equal(t, "test-cluster", row[0]) + assert.Equal(t, "95.000", row[12]) + assert.Equal(t, "Not Available", row[13]) // CISA KEV (nil → "Not Available") + assert.Equal(t, "Not Available", row[14]) // Known Ransomware Campaign (nil → "Not Available") + assert.Equal(t, "Not Available", row[16]) // Image Created Date (nil → "Not Available") + assert.Equal(t, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234", row[17]) + }) } func TestFormatCSVRow_NilFields(t *testing.T) { r := &ImageCVEQueryResponse{} - row := formatCSVRow(r) - assert.Equal(t, len(csvHeader), len(row)) - assert.Equal(t, "", row[0]) - assert.Equal(t, "Not Available", row[12]) // EPSS Probability Percentage - assert.Equal(t, "Not Available", row[13]) // Discovered At - assert.Equal(t, "Not Available", row[14]) // Image Created Date + + t.Run("KEV disabled", func(t *testing.T) { + t.Setenv(features.KnownExploitedVulnerabilities.EnvVar(), "false") + + row := formatCSVRow(r) + assert.Equal(t, len(formatCol()), len(row)) + assert.Equal(t, "", row[0]) + assert.Equal(t, "Not Available", row[12]) // EPSS Probability Percentage + assert.Equal(t, "Not Available", row[13]) // Discovered At + assert.Equal(t, "Not Available", row[14]) // Image Created Date + }) + + t.Run("KEV enabled", func(t *testing.T) { + t.Setenv(features.KnownExploitedVulnerabilities.EnvVar(), "true") + + row := formatCSVRow(r) + assert.Equal(t, len(formatCol()), len(row)) + assert.Equal(t, "", row[0]) + assert.Equal(t, "Not Available", row[12]) // EPSS Probability Percentage + assert.Equal(t, "Not Available", row[13]) // CISA KEV + assert.Equal(t, "Not Available", row[14]) // Known Ransomware Campaign + assert.Equal(t, "Not Available", row[15]) // Discovered At + assert.Equal(t, "Not Available", row[16]) // Image Created Date + }) } func TestCsvReportName(t *testing.T) { diff --git a/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go index 837ba00486a36..d34af5f597329 100644 --- a/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go +++ b/central/reports/scheduler/v2/reportgenerator/report_gen_transaction_integration_test.go @@ -8,6 +8,7 @@ import ( "context" "encoding/csv" "io" + "slices" "time" blobDS "github.com/stackrox/rox/central/blob/datastore" @@ -19,28 +20,31 @@ import ( "github.com/stackrox/rox/pkg/uuid" ) -// CSV column indices matching csvHeader in csv_gen.go. +// CSV column indices for the fixed prefix that precedes any feature-flag-gated +// columns (see csvHeader in csv_gen.go). Columns after EPSS can shift depending +// on enabled features, so look those up with colOf instead of hardcoding them. const ( - colCluster = 0 - colNamespace = 1 - colDeployment = 2 - colImage = 3 - colComponent = 4 - colCompVersion = 5 - colCVE = 6 - colFixable = 7 - colFixedBy = 8 - colSeverity = 9 - colCVSS = 10 - colNVDCVSS = 11 - colEPSS = 12 - colDiscoveredAt = 13 - colImageCreated = 14 - colReference = 15 - colAdvName = 16 - colAdvLink = 17 + colCluster = 0 + colNamespace = 1 + colDeployment = 2 + colImage = 3 + colComponent = 4 + colCompVersion = 5 + colCVE = 6 + colFixable = 7 + colFixedBy = 8 + colSeverity = 9 + colCVSS = 10 + colNVDCVSS = 11 + colEPSS = 12 ) +// colOf returns the index of the named column in the current CSV layout, which +// depends on which feature-flag-gated columns formatCol includes. +func colOf(name string) int { + return slices.Index(formatCol(), name) +} + // collectColumn extracts a single column from all rows. func collectColumn(rows [][]string, col int) []string { out := make([]string, len(rows)) @@ -163,7 +167,7 @@ func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_A rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) s.Require().Len(rows, 9, "1 header + 8 data rows") - s.Equal(csvHeader, rows[0]) + s.Equal(formatCol(), rows[0]) dataRows := rows[1:] s.ElementsMatch([]string{ @@ -240,7 +244,7 @@ func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_E rows := s.readBlobCSV(blobStore, snap.GetReportConfigurationId(), snap.GetReportId()) s.Require().Len(rows, 1, "only header row for empty result") - s.Equal(csvHeader, rows[0]) + s.Equal(formatCol(), rows[0]) } // TestGenerateReportTransaction_CSVFieldValues spot-checks individual CSV field @@ -276,9 +280,9 @@ func (s *NewDataModelEnhancedReportingTestSuite) TestGenerateReportTransaction_C s.Equal("9.00", row[colCVSS]) s.Equal("10.00", row[colNVDCVSS]) s.Equal("70.000", row[colEPSS]) - s.NotEqual("Not Available", row[colDiscoveredAt]) - s.Equal("RHSA-2025-CVE-fixable", row[colAdvName]) - s.Equal("test-rhsa-link", row[colAdvLink]) + s.NotEqual("Not Available", row[colOf("Discovered At")]) + s.Equal("RHSA-2025-CVE-fixable", row[colOf("Advisory Name")]) + s.Equal("test-rhsa-link", row[colOf("Advisory Link")]) } // TestGenerateReportTransaction_ReportStatusUpdated verifies that the report diff --git a/pkg/features/list.go b/pkg/features/list.go index 3631aef643ceb..dc632d82f305f 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -99,7 +99,7 @@ var ( // KnownExploitedVulnerabilities enables support for CISA Known Exploited Vulnerabilities (KEV) data. // // This must be enabled in Central and Scanner V4 Matcher to have any effect. - KnownExploitedVulnerabilities = registerFeature("Display CISA Known Exploited Vulnerabilities (KEV) data", "ROX_CISA_KEV") + KnownExploitedVulnerabilities = registerFeature("Display CISA Known Exploited Vulnerabilities (KEV) data", "ROX_CISA_KEV", enabled) // Display Compliance Dashboard (Deprecated) in user interface (not displayed by default starting in 4.11 release). DeprecatedComplianceDashboard = registerFeature("Display Compliance Dashboard (Deprecated) in user interface", "ROX_DEPRECATED_COMPLIANCE_DASHBOARD") From cb14fc39f29cac0ee515c6a5e619f3a23b2ec817 Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Tue, 15 Sep 2026 18:55:25 -0700 Subject: [PATCH 319/638] ROX-36762: SBOM button disabled for images with unidentifiable base os (#22762) Co-authored-by: Claude Opus 4.8 --- .../WorkloadCves/Image/ImagePage.tsx | 23 ++++--- .../Tables/ImageOverviewTable.tsx | 10 ++- .../components/GenerateSbomModal.tsx | 18 ------ .../getSbomGenerationStatusMessage.test.ts | 61 +++++++++++++++++++ .../utils/getSbomGenerationStatusMessage.ts | 28 +++++++++ 5 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/utils/getSbomGenerationStatusMessage.test.ts create mode 100644 ui/apps/platform/src/Containers/Vulnerabilities/utils/getSbomGenerationStatusMessage.ts diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx index d3897e9ff80f5..82662b930a8f2 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx @@ -39,9 +39,8 @@ import { runImageViewBasedReport } from 'services/ReportsService'; import { vulnerabilityImageViewBasedJobsPath } from 'routePaths'; import HeaderLoadingSkeleton from '../../components/HeaderLoadingSkeleton'; -import GenerateSbomModal, { - getSbomGenerationStatusMessage, -} from '../../components/GenerateSbomModal'; +import GenerateSbomModal from '../../components/GenerateSbomModal'; +import { getSbomGenerationStatusMessage } from '../../utils/getSbomGenerationStatusMessage'; import useInvalidateVulnerabilityQueries from '../../hooks/useInvalidateVulnerabilityQueries'; import ImagePageVulnerabilities from './ImagePageVulnerabilities'; import ImagePageResources from './ImagePageResources'; @@ -184,8 +183,16 @@ function ImagePage({ imageData && imageName ? `${imageName.registry}/${getImageBaseNameDisplay(imageData.id, imageName)}` : 'NAME UNKNOWN'; - const scanMessage = getImageScanMessage(imageData?.notes ?? [], imageData?.scanNotes ?? []); + const imageNotes = imageData?.notes ?? []; + const scanNotes = imageData?.scanNotes ?? []; + const scanMessage = getImageScanMessage(imageNotes, scanNotes); const hasScanMessage = !isEmpty(scanMessage); + // SBOM generation is only blocked when the image has no scan data at all; + // CVE-accuracy caveats (e.g. no base OS) must not disable it (ROX-36762). + const sbomGenerationStatusMessage = getSbomGenerationStatusMessage({ + isScannerV4Enabled, + imageNotes, + }); const workloadCveOverviewImagePath = urlBuilder.imageList('OBSERVED'); @@ -235,10 +242,7 @@ function ImagePage({ {hasWriteAccessForImage && ( - {showFeedbackModal && } } diff --git a/ui/apps/platform/src/images/feedback_illo.svg b/ui/apps/platform/src/images/feedback_illo.svg deleted file mode 100644 index a96f52cf57b51..0000000000000 --- a/ui/apps/platform/src/images/feedback_illo.svg +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ui/apps/platform/src/reducers/feedback.ts b/ui/apps/platform/src/reducers/feedback.ts deleted file mode 100644 index 8ea3b2e56c805..0000000000000 --- a/ui/apps/platform/src/reducers/feedback.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { combineReducers } from 'redux'; -import type { Reducer } from 'redux'; - -// Action types - -export type SetFeedbackModalVisibilityAction = { - type: 'feedback/SET_MODAL_VISIBILITY'; - show: boolean; -}; - -// Action creator functions - -export const actions = { - setFeedbackModalVisibility: (show: boolean): SetFeedbackModalVisibilityAction => ({ - type: 'feedback/SET_MODAL_VISIBILITY', - show, - }), -}; - -// Reducers - -const showFeedbackModal: Reducer = ( - state = false, - action -) => { - switch (action.type) { - case 'feedback/SET_MODAL_VISIBILITY': - return action.show; - default: - return state; - } -}; - -const reducer = combineReducers({ - showFeedbackModal, -}); - -export default reducer; - -// Selectors - -type State = ReturnType; - -const feedbackSelector = (state: State) => state.showFeedbackModal; - -export const selectors = { - feedbackSelector, -}; diff --git a/ui/apps/platform/src/reducers/index.js b/ui/apps/platform/src/reducers/index.js index 2d5f9a4a00cd2..98a4f0139e04f 100644 --- a/ui/apps/platform/src/reducers/index.js +++ b/ui/apps/platform/src/reducers/index.js @@ -4,7 +4,6 @@ import { connectRouter } from 'connected-react-router'; import bindSelectors from 'utils/bindSelectors'; import auth, { selectors as authSelectors } from './auth'; -import feedback, { selectors as feedbackSelectors } from './feedback'; import invite, { selectors as inviteSelectors } from './invite'; import notifications, { selectors as notificationSelectors } from './notifications'; import roles, { selectors as roleSelectors } from './roles'; @@ -23,7 +22,6 @@ import centralCapabilities, { const appReducer = combineReducers({ auth, - feedback, invite, notifications, roles, @@ -48,7 +46,6 @@ export default createRootReducer; const getApp = (state) => state.app; const getAuth = (state) => getApp(state).auth; -const getFeedback = (state) => getApp(state).feedback; const getInvite = (state) => getApp(state).invite; const getNotifications = (state) => getApp(state).notifications; const getRoles = (state) => getApp(state).roles; @@ -61,7 +58,6 @@ const getCentralCapabilities = (state) => getApp(state).centralCapabilities; const boundSelectors = { ...bindSelectors(getAuth, authSelectors), - ...bindSelectors(getFeedback, feedbackSelectors), ...bindSelectors(getInvite, inviteSelectors), ...bindSelectors(getNotifications, notificationSelectors), ...bindSelectors(getRoles, roleSelectors), From 870254108948f9bcf670a929ff13b666e0258b2e Mon Sep 17 00:00:00 2001 From: Van Wilson Date: Tue, 22 Sep 2026 13:35:32 -0400 Subject: [PATCH 358/638] chore(ci): Run nightly build at 3am (03:00) UTC every weekday (#22845) --- .github/workflows/nightly-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-tag.yml b/.github/workflows/nightly-tag.yml index d030b195015b5..cc87bb5e965ce 100644 --- a/.github/workflows/nightly-tag.yml +++ b/.github/workflows/nightly-tag.yml @@ -2,7 +2,7 @@ name: Create nightly tag on: workflow_dispatch: schedule: - - cron: 0 0 * * 1-5 + - cron: 0 3 * * 1-5 jobs: create-nightly-tag: if: ${{ github.repository_owner == 'stackrox' }} From 3392769051eedd307bee031f44026638ea3de3f6 Mon Sep 17 00:00:00 2001 From: Stephan Hesselmann Date: Tue, 22 Sep 2026 21:34:04 +0200 Subject: [PATCH 359/638] ROX-35974: add retry to external backup count checks (#22932) Co-authored-by: Claude Opus 4.6 (1M context) --- tests/external_backup_test.go | 126 ++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/tests/external_backup_test.go b/tests/external_backup_test.go index 36617867a31f8..f8646549caf38 100644 --- a/tests/external_backup_test.go +++ b/tests/external_backup_test.go @@ -31,11 +31,17 @@ import ( "google.golang.org/grpc/status" ) +const ( + backupRetryTries = 5 + backupRetryInterval = 10 * time.Second + centralReadinessMaxTries = 10 +) + type backupTestCase struct { name string setup func(t *testing.T) ( backup *storage.ExternalBackup, - countBackups func(ctx context.Context, t *testing.T, prefix string) int, + countBackups func(ctx context.Context, t *testing.T, prefix string) (int, error), cleanupBackups func(ctx context.Context, t *testing.T, prefix string), ) } @@ -51,7 +57,7 @@ func newObjectPrefix() string { } func newGCSBucketFuncs(t *testing.T, bucket, serviceAccount string) ( - countFn func(ctx context.Context, t *testing.T, prefix string) int, + countFn func(ctx context.Context, t *testing.T, prefix string) (int, error), cleanupFn func(ctx context.Context, t *testing.T, prefix string), ) { var opts []option.ClientOption @@ -62,15 +68,17 @@ func newGCSBucketFuncs(t *testing.T, bucket, serviceAccount string) ( require.NoError(t, err) bkt := client.Bucket(bucket) - countFn = func(ctx context.Context, t *testing.T, prefix string) int { + countFn = func(ctx context.Context, _ *testing.T, prefix string) (int, error) { it := bkt.Objects(ctx, &googleStorage.Query{Prefix: prefix}) count := 0 var iterErr error for _, iterErr = it.Next(); iterErr == nil; _, iterErr = it.Next() { count++ } - require.Equal(t, iterator.Done, iterErr) - return count + if iterErr != iterator.Done { + return 0, iterErr + } + return count, nil } cleanupFn = func(ctx context.Context, t *testing.T, prefix string) { it := bkt.Objects(ctx, &googleStorage.Query{Prefix: prefix}) @@ -95,8 +103,9 @@ func gcsTestCases() []backupTestCase { return []backupTestCase{ { name: "GCS/service_account_key", - setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) int, func(context.Context, *testing.T, string)) { - env := testutils.EnvOrFail(t, + setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) (int, error), func(context.Context, *testing.T, string)) { + env := testutils.EnvOrFail( + t, "GCP_GCS_BACKUP_TEST_BUCKET_NAME_V2", "GOOGLE_GCS_BACKUP_SERVICE_ACCOUNT_V2", ) @@ -113,7 +122,7 @@ func gcsTestCases() []backupTestCase { }, { name: "GCS/workload_identity", - setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) int, func(context.Context, *testing.T, string)) { + setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) (int, error), func(context.Context, *testing.T, string)) { env := testutils.EnvOrSkip(t, "GCP_GCS_BACKUP_TEST_BUCKET_NAME_V2", "SETUP_WORKLOAD_IDENTITIES") if env["SETUP_WORKLOAD_IDENTITIES"] != "true" { t.Skip("SETUP_WORKLOAD_IDENTITIES not set to true") @@ -132,10 +141,11 @@ func gcsTestCases() []backupTestCase { } func newS3BucketFuncs(t *testing.T, endpoint, region, accessKeyID, secretAccessKey, bucket string, pathStyle bool) ( - countFn func(ctx context.Context, t *testing.T, prefix string) int, + countFn func(ctx context.Context, t *testing.T, prefix string) (int, error), cleanupFn func(ctx context.Context, t *testing.T, prefix string), ) { - cfg, err := awsConfig.LoadDefaultConfig(context.Background(), + cfg, err := awsConfig.LoadDefaultConfig( + context.Background(), awsConfig.WithRegion(region), awsConfig.WithCredentialsProvider( credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""), @@ -159,13 +169,15 @@ func newS3BucketFuncs(t *testing.T, endpoint, region, accessKeyID, secretAccessK } client := s3.NewFromConfig(cfg, clientOpts...) - countFn = func(ctx context.Context, t *testing.T, prefix string) int { + countFn = func(ctx context.Context, _ *testing.T, prefix string) (int, error) { out, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: aws.String(bucket), Prefix: aws.String(prefix), }) - require.NoError(t, err) - return int(aws.ToInt32(out.KeyCount)) + if err != nil { + return 0, err + } + return int(aws.ToInt32(out.KeyCount)), nil } cleanupFn = func(ctx context.Context, t *testing.T, prefix string) { out, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ @@ -192,7 +204,7 @@ func newAWSS3Setup(name string, makeConfig func(t *testing.T) *storage.S3Config) fullName := "AWS_S3/" + name return backupTestCase{ name: fullName, - setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) int, func(context.Context, *testing.T, string)) { + setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) (int, error), func(context.Context, *testing.T, string)) { config := makeConfig(t) config.ObjectPrefix = newObjectPrefix() countFn, cleanupFn := newS3BucketFuncs(t, "", config.GetRegion(), @@ -208,7 +220,8 @@ func newAWSS3Setup(name string, makeConfig func(t *testing.T) *storage.S3Config) func awsS3Config(withEndpoint bool) func(t *testing.T) *storage.S3Config { return func(t *testing.T) *storage.S3Config { - env := testutils.EnvOrFail(t, + env := testutils.EnvOrFail( + t, "AWS_S3_BACKUP_TEST_BUCKET_NAME", "AWS_S3_BACKUP_TEST_BUCKET_REGION", "AWS_ACCESS_KEY_ID", @@ -238,7 +251,7 @@ func newS3CompatibleSetup(name string, makeConfig func(t *testing.T) *storage.S3 fullName := "S3Compatible/" + name return backupTestCase{ name: fullName, - setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) int, func(context.Context, *testing.T, string)) { + setup: func(t *testing.T) (*storage.ExternalBackup, func(context.Context, *testing.T, string) (int, error), func(context.Context, *testing.T, string)) { config := makeConfig(t) config.ObjectPrefix = newObjectPrefix() pathStyle := config.GetUrlStyle() == storage.S3URLStyle_S3_URL_STYLE_PATH @@ -255,7 +268,8 @@ func newS3CompatibleSetup(name string, makeConfig func(t *testing.T) *storage.S3 func r2Config(withScheme bool, urlStyle storage.S3URLStyle) func(t *testing.T) *storage.S3Compatible { return func(t *testing.T) *storage.S3Compatible { - env := testutils.EnvOrFail(t, + env := testutils.EnvOrFail( + t, "CLOUDFLARE_R2_BACKUP_TEST_ACCOUNT_ID", "CLOUDFLARE_R2_BACKUP_TEST_BUCKET_NAME", "CLOUDFLARE_R2_BACKUP_TEST_REGION", @@ -276,7 +290,8 @@ func r2Config(withScheme bool, urlStyle storage.S3URLStyle) func(t *testing.T) * func odfConfig(withScheme bool) func(t *testing.T) *storage.S3Compatible { return func(t *testing.T) *storage.S3Compatible { - env := testutils.EnvOrFail(t, + env := testutils.EnvOrFail( + t, "ODF_S3_BACKUP_TEST_ENDPOINT", "ODF_S3_BACKUP_TEST_BUCKET_NAME", "ODF_S3_BACKUP_TEST_REGION", @@ -310,11 +325,40 @@ func s3CompatibleTestCases() []backupTestCase { } } +func requireBackupCount( + t *testing.T, + countBackups func(ctx context.Context, t *testing.T, prefix string) (int, error), + prefix string, + expected int, +) error { + return retry.WithRetry( + func() error { + ctx, cancel := context.WithTimeout(context.Background(), backupRetryInterval) + defer cancel() + n, err := countBackups(ctx, t, prefix) + if err != nil { + return err + } + if n != expected { + return fmt.Errorf("expected %d backups, got %d", expected, n) + } + return nil + }, + retry.Tries(backupRetryTries), + retry.BetweenAttempts(func(_ int) { + time.Sleep(backupRetryInterval) + }), + retry.OnFailedAttempts(func(err error) { + t.Logf("Error verifying backup count: %v", err) + }), + ) +} + func runBackupLifecycleTest( t *testing.T, service v1.ExternalBackupServiceClient, backup *storage.ExternalBackup, - countBackups func(ctx context.Context, t *testing.T, prefix string) int, + countBackups func(ctx context.Context, t *testing.T, prefix string) (int, error), cleanupBackups func(ctx context.Context, t *testing.T, prefix string), ) { var prefix string @@ -330,15 +374,16 @@ func runBackupLifecycleTest( t.Logf("Using object prefix: %s", prefix) // Retry TestExternalBackup in case Central is not fully ready yet. - err := retry.WithRetry(func() error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - _, err := service.TestExternalBackup(ctx, backup) - return err - }, - retry.Tries(10), + err := retry.WithRetry( + func() error { + ctx, cancel := context.WithTimeout(context.Background(), backupRetryInterval) + defer cancel() + _, err := service.TestExternalBackup(ctx, backup) + return err + }, + retry.Tries(centralReadinessMaxTries), retry.BetweenAttempts(func(_ int) { - time.Sleep(10 * time.Second) + time.Sleep(backupRetryInterval) }), retry.OnFailedAttempts(func(err error) { t.Logf("Error testing external backup: %v", err) @@ -364,9 +409,7 @@ func runBackupLifecycleTest( } }) - countCtx, countCancel := context.WithTimeout(context.Background(), 10*time.Second) - assert.Equal(t, 0, countBackups(countCtx, t, prefix)) - countCancel() + require.NoError(t, requireBackupCount(t, countBackups, prefix, 0)) for i := 1; i <= 3; i++ { triggerCtx, triggerCancel := context.WithTimeout(context.Background(), 2*time.Minute) @@ -375,31 +418,12 @@ func runBackupLifecycleTest( triggerCancel() if i <= 2 { - verifyCtx, verifyCancel := context.WithTimeout(context.Background(), 10*time.Second) - assert.Equal(t, i, countBackups(verifyCtx, t, prefix)) - verifyCancel() + assert.NoError(t, requireBackupCount(t, countBackups, prefix, i)) } } // Third backup should prune the first, keeping only BackupsToKeep=2. - err = retry.WithRetry(func() error { - retryCtx, retryCancel := context.WithTimeout(context.Background(), 10*time.Second) - defer retryCancel() - n := countBackups(retryCtx, t, prefix) - if n != 2 { - return fmt.Errorf("expected 2 backups after pruning, got %d", n) - } - return nil - }, - retry.Tries(10), - retry.BetweenAttempts(func(_ int) { - time.Sleep(1 * time.Second) - }), - retry.OnFailedAttempts(func(err error) { - t.Logf("Error waiting for backup pruning: %v", err) - }), - ) - require.NoError(t, err) + require.NoError(t, requireBackupCount(t, countBackups, prefix, 2)) } func TestExternalBackup(t *testing.T) { From fb34e70ec78b4dade7acfe40b695cbad4e274562 Mon Sep 17 00:00:00 2001 From: Alex Vulaj Date: Tue, 22 Sep 2026 17:04:39 -0400 Subject: [PATCH 360/638] ROX-34114: Whitelist GKE filestore mount policy in DefaultPoliciesTest (#22945) --- qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy b/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy index 5998fb1b6c6a1..7b261936537d0 100644 --- a/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy +++ b/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy @@ -83,6 +83,10 @@ class DefaultPoliciesTest extends BaseSpecification { "tunnelfront - Secure Shell Server (sshd) Execution", "tunnelfront - Docker CIS 4.7: Alert on Update Instruction", "webhookserver - Kubernetes Actions: Port Forward to Pod", + // Newer EKS ebs-csi-node DaemonSets use bidirectional mount propagation. + "ebs-csi-node - Docker CIS 5.19: Ensure mount propagation mode is not enabled", + // GKE filestore-node DaemonSets use bidirectional mount propagation. + "filestore-node - Docker CIS 5.19: Ensure mount propagation mode is not enabled", ] static final private Deployment STRUTS_DEPLOYMENT = new Deployment() From 63f1561240389adc486e0a2aa31ef43a72c73329 Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:33:04 +0200 Subject: [PATCH 361/638] chore(release): Add 4.11.5 to scanner updater configuration (#22983) Co-authored-by: ksurabhi91 --- scanner/updater/version/RELEASE_VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index a9d9537b16f92..b86f49993d763 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -20,6 +20,7 @@ 4.11.2 4.11.3 4.11.4 +4.11.5 4.4.0 4.4.1 4.4.2 From 4f75bf81d768a614b260176b1f4a87ffdb7dfe23 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 03:48:17 +0000 Subject: [PATCH 362/638] chore(deps): refresh rpm lockfiles [SECURITY] (#22954) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 208 ++++++++++++++++++++++++------------------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 8a3e56e79c3ea..458135bbf6806 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -137,20 +137,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1819455 - checksum: sha256:9a533624ca7110176ccfedbd4ec54d339fadeeb985f7016eed7e4e738dd44ed2 + size: 1828909 + checksum: sha256:06bde44430fc94e09d7dac2c748f19b41161fdc2fa4b4b6cd861ca9a7a1532c0 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-15.18-1.module+el9.8.0+24358+32c5830e.aarch64.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 149345 - checksum: sha256:7168e8dc1147af4fe2033ddaa86418321f850c746c1a92f7aa0e5416b2b37ced + size: 150016 + checksum: sha256:869f22709281040978c3130c972685c217b4be0c4f915c69c3036b886d75bcc2 name: postgresql-private-libs - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 32440 @@ -522,13 +522,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.5.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 292322 - checksum: sha256:ea6c3ade86939df7a985ae71e4c5100731962add54295fd40660a2f2c2f85f1d + size: 292483 + checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 name: libcurl - evr: 7.76.1-40.el9_8.5 - sourcerpm: curl-7.76.1-40.el9_8.5.src.rpm + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 727417 @@ -1070,12 +1070,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 45775054 - checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 + size: 53848854 + checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e + evr: 15.19-1.module+el9.8.0+24800+a95bf172 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 20894742 @@ -1172,12 +1172,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.5.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 2576796 - checksum: sha256:5a455f6c35ae1069b8c54ade4c9d0d3d260134f76bee10f22efef9683c31b03f + size: 2579601 + checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d name: curl - evr: 7.76.1-40.el9_8.5 + evr: 7.76.1-40.el9_8.7 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 4025677 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/0de17e64808b2850d6f04f496214d0ca0a494ecdf6068ad1faf92d4fcbf7ed87-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/6ec982e105d54c866591c0a07c68f06bb302a8cc9e880f9c874bdf4d4c7fa5ca-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 67332 - checksum: sha256:0de17e64808b2850d6f04f496214d0ca0a494ecdf6068ad1faf92d4fcbf7ed87 + size: 67183 + checksum: sha256:6ec982e105d54c866591c0a07c68f06bb302a8cc9e880f9c874bdf4d4c7fa5ca - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1774,20 +1774,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1881747 - checksum: sha256:6aee4d674c4b3b504a62e263386ebc1bf4f9daed2cd89b3a9578de4fc3e3dcce + size: 1893060 + checksum: sha256:cb037d48821efe06c06c45369e4a4fd9ca9f048022bd62bf7373028b9c8c74d9 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-15.18-1.module+el9.8.0+24358+32c5830e.ppc64le.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 170196 - checksum: sha256:388ed8fa7cddbdcf4c20bfd13986e2c1d25090daadcb585e442fe7ca99a76955 + size: 170264 + checksum: sha256:64a378b9cb22b6228f0b1a5a1b2b3b50b959e7d8d093953f5c09058c8e2167f3 name: postgresql-private-libs - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 32494 @@ -2159,13 +2159,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.5.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 329146 - checksum: sha256:1101e1698ca9b9d1b67332cf81c29ffbfbcd4342b053a3e3d4304d8d86e73bc2 + size: 329526 + checksum: sha256:4bf40a5958683446b5757d3f8be92961ffa568ec64a794500f959533bce1bb1b name: libcurl - evr: 7.76.1-40.el9_8.5 - sourcerpm: curl-7.76.1-40.el9_8.5.src.rpm + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 837087 @@ -2714,12 +2714,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 45775054 - checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 + size: 53848854 + checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e + evr: 15.19-1.module+el9.8.0+24800+a95bf172 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 20894742 @@ -2816,12 +2816,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.5.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 2576796 - checksum: sha256:5a455f6c35ae1069b8c54ade4c9d0d3d260134f76bee10f22efef9683c31b03f + size: 2579601 + checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d name: curl - evr: 7.76.1-40.el9_8.5 + evr: 7.76.1-40.el9_8.7 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 4025677 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/3a29f011ee20c5bfcbb7efb866b9e30f8017a8d094597f131a0265c33bc51f76-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/ec878a0f354462d95a8c6b76e74fc1a92f7381fb425bbae516e44b7f0c54ba8b-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 67082 - checksum: sha256:3a29f011ee20c5bfcbb7efb866b9e30f8017a8d094597f131a0265c33bc51f76 + size: 68368 + checksum: sha256:ec878a0f354462d95a8c6b76e74fc1a92f7381fb425bbae516e44b7f0c54ba8b - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3424,20 +3424,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1851802 - checksum: sha256:dec726e071d7a3d2fd601eaa3d0dc88797022ebf3145c5d3facfdc5ff6827602 + size: 1861033 + checksum: sha256:a3aa3b5a24a81f1cb8a36a0eecba4f0eb00a54cbb5840d101533cf4793d9c67e name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-15.18-1.module+el9.8.0+24358+32c5830e.s390x.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 147697 - checksum: sha256:504ab6379c9051e1b745d0ab259f6957e0a2c0eee3042c9989302229af7dd3cd + size: 147716 + checksum: sha256:738dfc0971a4ec61b9092d9598809c5cb082c6aff8db768507d9c0aaa5ae35ea name: postgresql-private-libs - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 32298 @@ -3809,13 +3809,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.5.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 291750 - checksum: sha256:27ec5fc1d1420007c387595e76ce70f944ac51b19f5afe7b867f4f528da43ee4 + size: 291971 + checksum: sha256:18d6734aa46eb35076d0dc6a1d0cc1d8df42be99d2c964fe7125890df4144056 name: libcurl - evr: 7.76.1-40.el9_8.5 - sourcerpm: curl-7.76.1-40.el9_8.5.src.rpm + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 721041 @@ -4351,12 +4351,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 45775054 - checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 + size: 53848854 + checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e + evr: 15.19-1.module+el9.8.0+24800+a95bf172 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 20894742 @@ -4453,12 +4453,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.5.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 2576796 - checksum: sha256:5a455f6c35ae1069b8c54ade4c9d0d3d260134f76bee10f22efef9683c31b03f + size: 2579601 + checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d name: curl - evr: 7.76.1-40.el9_8.5 + evr: 7.76.1-40.el9_8.7 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 4025677 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/8dc68f9815aa6ef73486c7b070e44eba6a8da50917a36efde067c16407838e85-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/ca31328d7c6e06797a1779175be3da1217c64a74e6a50188219aad4651b50d9a-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 65039 - checksum: sha256:8dc68f9815aa6ef73486c7b070e44eba6a8da50917a36efde067c16407838e85 + size: 64964 + checksum: sha256:ca31328d7c6e06797a1779175be3da1217c64a74e6a50188219aad4651b50d9a - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -5048,20 +5048,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1832108 - checksum: sha256:1f2f8e5526bac45da979c876fbb9a069459ec5a6476d89398433048005fb786b + size: 1842434 + checksum: sha256:18506bf4217ef40600adcb830fe651840f041423ef3bea67bd73d6f8c78e383c name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-15.18-1.module+el9.8.0+24358+32c5830e.x86_64.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 154248 - checksum: sha256:a8fca7c9e1d9cf9aa6eb95fd39e90068960190b40cac303f546930cab667ae11 + size: 154163 + checksum: sha256:18d4936ef18627f03facdf1b8f54e313d04f67883d0019abee9a599b7e3e621b name: postgresql-private-libs - evr: 15.18-1.module+el9.8.0+24358+32c5830e - sourcerpm: postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + evr: 15.19-1.module+el9.8.0+24800+a95bf172 + sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 32484 @@ -5426,13 +5426,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.5.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 297551 - checksum: sha256:428b87ca3eb808d769f6f0f68019a97d3c487c86268ccff2ab7ad7223beab01e + size: 296930 + checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 name: libcurl - evr: 7.76.1-40.el9_8.5 - sourcerpm: curl-7.76.1-40.el9_8.5.src.rpm + evr: 7.76.1-40.el9_8.7 + sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 755192 @@ -5974,12 +5974,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-15.18-1.module+el9.8.0+24358+32c5830e.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 45775054 - checksum: sha256:5955f781730fe817b08b7ddc3d2117a7c0a0c95ed2fd2044cbc1ff7d1eba1b28 + size: 53848854 + checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 name: postgresql - evr: 15.18-1.module+el9.8.0+24358+32c5830e + evr: 15.19-1.module+el9.8.0+24800+a95bf172 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 20894742 @@ -6076,12 +6076,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.5.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 2576796 - checksum: sha256:5a455f6c35ae1069b8c54ade4c9d0d3d260134f76bee10f22efef9683c31b03f + size: 2579601 + checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d name: curl - evr: 7.76.1-40.el9_8.5 + evr: 7.76.1-40.el9_8.7 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 4025677 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/cb34c3275498153d41fc1dcbb93d8922eed2524744d40f147010b365e6d48c90-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/cda6cd8154f7c7b97f9797580de964f58c6ebac82457afb91832fe5f8c399da6-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 70375 - checksum: sha256:cb34c3275498153d41fc1dcbb93d8922eed2524744d40f147010b365e6d48c90 + size: 70416 + checksum: sha256:cda6cd8154f7c7b97f9797580de964f58c6ebac82457afb91832fe5f8c399da6 From 1678637fcf8a0d39087ea6af2960326207b53929 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 03:52:11 +0000 Subject: [PATCH 363/638] chore(deps): update all dependencies (#22993) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 2 +- image/rhel/konflux.Dockerfile | 2 +- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index edd6c0e281978..d28c37768e656 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:984df0a2b8d9011d419b0ad260b25f6b74b7ce69ab0595b75522e30f8849f27f + image: registry.redhat.io/ubi9-minimal:latest@sha256:8ebe2ad8fdf3cab3e5a53c1edc69194c98209cfadab24b884f4ad9ebcf7bbbfc imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 727394f45ef3e..94dc047ab378a 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:984df0a2b8d9011d419b0ad260b25f6b74b7ce69ab0595b75522e30f8849f27f + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:8ebe2ad8fdf3cab3e5a53c1edc69194c98209cfadab24b884f4ad9ebcf7bbbfc imagePullPolicy: IfNotPresent script: | #!/bin/bash diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 4dbb7c2e15831..aa3797bec1801 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -68,7 +68,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:9295c5c688f487fa5cf27a734fa55ecd57aeb7dc0904ba537da4f42dfa1d0acb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 1536f1ab1025e..38f16e0d511cc 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:9295c5c688f487fa5cf27a734fa55ecd57aeb7dc0904ba537da4f42dfa1d0acb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index 7a5cf12ea51ad..205f6b635414e 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:9295c5c688f487fa5cf27a734fa55ecd57aeb7dc0904ba537da4f42dfa1d0acb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index 73e2ace25323e..d09e61ea83aa6 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:9295c5c688f487fa5cf27a734fa55ecd57aeb7dc0904ba537da4f42dfa1d0acb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer COPY --from=ubi-micro-base / /out/ From d098e9a2d99616ab753862ef6e64c09907e1301c Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 04:05:41 +0000 Subject: [PATCH 364/638] chore(deps): refresh rpm lockfiles (#22837) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> From 35fb683416093b12b836f6a02f16e15cc59793ee Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 23 Sep 2026 06:36:08 +0200 Subject: [PATCH 365/638] tests(unit): retry `go list` (#22773) --- Makefile | 8 ++++---- scripts/go-files-to-packages.sh | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100755 scripts/go-files-to-packages.sh diff --git a/Makefile b/Makefile index 387480c0b6c8b..11bc6d500c9d6 100644 --- a/Makefile +++ b/Makefile @@ -575,7 +575,7 @@ test-prep: go-unit-tests: build-prep test-prep set -o pipefail ; \ CGO_ENABLED=1 GOEXPERIMENT=cgocheck2 MUTEX_WATCHDOG_TIMEOUT_SECS=30 GOTAGS=$(GOTAGS),test scripts/go-test.sh -timeout 25m -race -cover -coverprofile test-output/coverage.out -v \ - $(shell git ls-files -- '*_test.go' | grep -v -E '^($(NESTED_GO_MODULE_REGEX))(/|$$)' | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list| grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ + $(shell git ls-files -- '*_test.go' | grep -v -E '^($(NESTED_GO_MODULE_REGEX))(/|$$)' | scripts/go-files-to-packages.sh | grep -Ev $(UNIT_TEST_IGNORE)) \ | tee $(GO_TEST_OUTPUT_PATH) # Nested modules have independent coverage profiles, so keep their coverage # out of the root profile while including their verbose output in JUnit. @@ -604,19 +604,19 @@ sensor-pipeline-benchmark: build-prep test-prep go-postgres-unit-tests: build-prep test-prep set -o pipefail ; \ CGO_ENABLED=1 GOEXPERIMENT=cgocheck2 MUTEX_WATCHDOG_TIMEOUT_SECS=30 GOTAGS=$(GOTAGS),test,sql_integration scripts/go-test.sh -timeout 15m -race -cover -coverprofile test-output/coverage.out -v \ - $(shell git grep -rl "//go:build sql_integration" central pkg tools | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list -tags sql_integration | grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ + $(shell git grep -rl "//go:build sql_integration" central pkg tools | scripts/go-files-to-packages.sh -tags sql_integration | grep -Ev $(UNIT_TEST_IGNORE)) \ | tee $(GO_TEST_OUTPUT_PATH) @# The -p 1 passed to go test is required to ensure that tests of different packages are not run in parallel, so as to avoid conflicts when interacting with the DB. set -o pipefail ; \ CGO_ENABLED=1 GOEXPERIMENT=cgocheck2 MUTEX_WATCHDOG_TIMEOUT_SECS=30 GOTAGS=$(GOTAGS),test,sql_integration scripts/go-test.sh -p 1 -race -cover -coverprofile test-output/migrator-coverage.out -v \ - $(shell git grep -rl "//go:build sql_integration" migrator | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list -tags sql_integration | grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ + $(shell git grep -rl "//go:build sql_integration" migrator | scripts/go-files-to-packages.sh -tags sql_integration | grep -Ev $(UNIT_TEST_IGNORE)) \ | tee -a $(GO_TEST_OUTPUT_PATH) .PHONY: go-postgres-bench-tests go-postgres-bench-tests: build-prep test-prep set -o pipefail ; \ CGO_ENABLED=1 GOEXPERIMENT=cgocheck2 MUTEX_WATCHDOG_TIMEOUT_SECS=30 GOTAGS=$(GOTAGS),test,sql_integration scripts/go-test.sh -run=nonthing -bench=. -benchtime=$(BENCHTIME) -benchmem -timeout $(BENCHTIMEOUT) -count $(BENCHCOUNT) -v \ - $(shell git grep -rl "testing.B" central pkg migrator tools | sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq | xargs go list -tags sql_integration | grep -v '^github.com/stackrox/rox/tests$$' | grep -Ev $(UNIT_TEST_IGNORE)) \ + $(shell git grep -rl "testing.B" central pkg migrator tools | scripts/go-files-to-packages.sh -tags sql_integration | grep -Ev $(UNIT_TEST_IGNORE)) \ | tee $(GO_TEST_OUTPUT_PATH) .PHONY: shell-unit-tests diff --git a/scripts/go-files-to-packages.sh b/scripts/go-files-to-packages.sh new file mode 100755 index 0000000000000..f87926393c81c --- /dev/null +++ b/scripts/go-files-to-packages.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +# Reads a list of files (one per line) from stdin and prints on stdout a list of go packages. +# The arguments are passed to `go list` that is used to perform the transformation. +# That go list command is retried, to help with go proxy unavailability. + +num_retries=4 + +dir_list=$(mktemp) +sed -e 's@^@./@g' | xargs -n 1 dirname | sort | uniq > "${dir_list}" +output=$(mktemp) +for attempt in $(seq 0 $num_retries) +do + sleep_sec=$((attempt * 5)) + if [[ $sleep_sec -gt 0 ]]; then + echo >&2 "Sleeping ${sleep_sec} before retrying..." + sleep "${sleep_sec}" + fi + if xargs go list "$@" < "${dir_list}" > "${output}"; then + grep -v '^github.com/stackrox/rox/tests$' "${output}" + exit 0 + fi +done +echo >&2 "Running 'go list $*' failed despite $num_retries retries." +exit 1 From 0b0cb7b8ad6f03b6057d02a14c67c9a8ea0bbeb7 Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:46:46 -0500 Subject: [PATCH 366/638] ROX-19954: quay-auto test allow pull if image not present (#22947) --- qa-tests-backend/src/main/groovy/objects/Deployment.groovy | 7 +++++++ .../src/main/groovy/orchestratormanager/Kubernetes.groovy | 4 +++- qa-tests-backend/src/test/groovy/ImageScanningTest.groovy | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/qa-tests-backend/src/main/groovy/objects/Deployment.groovy b/qa-tests-backend/src/main/groovy/objects/Deployment.groovy index 7645193566c1a..151da20ae5735 100644 --- a/qa-tests-backend/src/main/groovy/objects/Deployment.groovy +++ b/qa-tests-backend/src/main/groovy/objects/Deployment.groovy @@ -14,6 +14,8 @@ class Deployment { String name String namespace = Constants.ORCHESTRATOR_NAMESPACE String image + // Optional policy for the main container; takes precedence over the CI Quay policy. + String imagePullPolicyOverride Map labels = [:] Map ports = [:] Integer targetport @@ -104,6 +106,11 @@ class Deployment { return this } + Deployment setImagePullPolicyOverride(String policy) { + this.imagePullPolicyOverride = policy + return this + } + Deployment addLabel(String k, String v) { this.labels[k] = v return this diff --git a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy index 3ff93c2e6c171..10d4cd84fa38e 100644 --- a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy +++ b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy @@ -2450,7 +2450,9 @@ class Kubernetes { // Allow override of imagePullPolicy for quay.io images. Typically used // to set to Never to help keep the list of quay.io prebuilt images up // to date for image-prefetcher. Why not all images? See ROX-25258. - if (Env.IMAGE_PULL_POLICY_FOR_QUAY_IO && deployment.image =~ /^quay.io/) { + if (deployment.imagePullPolicyOverride) { + container.setImagePullPolicy(deployment.imagePullPolicyOverride) + } else if (Env.IMAGE_PULL_POLICY_FOR_QUAY_IO && deployment.image =~ /^quay.io/) { container.setImagePullPolicy(Env.IMAGE_PULL_POLICY_FOR_QUAY_IO) } if (deployment.livenessProbeDefined) { diff --git a/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy b/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy index c709e80c1498b..a56d13e2c4e47 100644 --- a/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy +++ b/qa-tests-backend/src/test/groovy/ImageScanningTest.groovy @@ -80,6 +80,8 @@ class ImageScanningTest extends BaseSpecification { // Alternatively can use quay.io/rhacs-eng/qa:struts-app but that doesn't have as many // dockerfile violations .setImage("quay.io/rhacs-eng/qa:registry-image-0-3") + // TODO(ROX-37155): Revert this override. + .setImagePullPolicyOverride("IfNotPresent") .addLabel("app", "quay-image-scanning-test") .addImagePullSecret("quay-image-scanning-test"), "gcr": new Deployment() From f6fe1fd5b94e049eb8c4cf0e9545232c12332525 Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Tue, 22 Sep 2026 21:50:52 -0700 Subject: [PATCH 367/638] fix(scanner): exclude kernel packages from rhel-vex exports (#22988) --- .github/workflows/scanner-updater-export.yaml | 1 + scanner/updater/export.go | 35 +++++--- scanner/updater/export_test.go | 86 +++++++++++++++++++ 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/.github/workflows/scanner-updater-export.yaml b/.github/workflows/scanner-updater-export.yaml index e986e6b4a0afa..91e3862ab8ba4 100644 --- a/.github/workflows/scanner-updater-export.yaml +++ b/.github/workflows/scanner-updater-export.yaml @@ -104,6 +104,7 @@ jobs: ROX_GIT_REF: ${{ inputs.ref }} GCS_OBJECT: ${{ inputs.object }} STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT: "10m" + STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES: "true" steps: - name: Checkout repository (master for local actions) uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/scanner/updater/export.go b/scanner/updater/export.go index faf2d5158bda7..ca0ce59f53503 100644 --- a/scanner/updater/export.go +++ b/scanner/updater/export.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "time" "github.com/klauspost/compress/zstd" @@ -201,26 +202,40 @@ func rhelVexOpts() []updates.ManagerOption { updates.WithEnabled([]string{rhelVexUpdaterName}), updates.WithConfigs(map[string]driver.ConfigUnmarshaler{ rhelVexUpdaterName: func(i any) error { - ctx := context.Background() - ctx = log.With(ctx, "updater", rhelVexUpdaterName) + ctx := log.With(context.Background(), "updater", rhelVexUpdaterName) + // Validate during factory configuration so invalid input fails manager + // initialization; updater configuration errors only skip the updater. + ignoreKernel := false + if value := os.Getenv("STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES"); value != "" { + var err error + ignoreKernel, err = strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES: %w", err) + } + } - // This function gets called for both the Factory and the Updater. - // We only need to configure the Factory (which has the CompressedFileTimeout field). + // The factory passes the download timeout to the updater; UpdaterConfig + // has no timeout field. Kernel exclusion is set on UpdaterConfig because + // the manager configures the updater after construction, overwriting any + // kernel-exclusion value inherited from the factory. switch cfg := i.(type) { case *vex.FactoryConfig: - // Configure the factory with custom timeout. - timeout := os.Getenv("STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT") - if timeout != "" { - parsedTimeout, err := time.ParseDuration(timeout) + var timeout *claircore.Duration + if value := os.Getenv("STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT"); value != "" { + parsedTimeout, err := time.ParseDuration(value) if err != nil { slog.WarnContext(ctx, "using default STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT due to invalid duration", "reason", err) } else { - cfg.CompressedFileTimeout = claircore.Duration(parsedTimeout) + duration := claircore.Duration(parsedTimeout) + timeout = &duration slog.InfoContext(ctx, "using compressed file timeout", "timeout", parsedTimeout.String()) } } + if timeout != nil { + cfg.CompressedFileTimeout = *timeout + } case *vex.UpdaterConfig: - // Updater config - nothing to configure here. + cfg.IgnoreKernelPackages = ignoreKernel default: return fmt.Errorf("rhel-vex: unexpected config type: %T", i) } diff --git a/scanner/updater/export_test.go b/scanner/updater/export_test.go index 23c33dc90a945..caca347d2be5c 100644 --- a/scanner/updater/export_test.go +++ b/scanner/updater/export_test.go @@ -1,15 +1,101 @@ package updater import ( + "context" "maps" + "net/http" "slices" "testing" + "time" + "github.com/quay/claircore" + "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/libvuln/updates" + "github.com/quay/claircore/rhel/vex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// configCaptureFactory captures the callback registered with the updates manager. +type configCaptureFactory struct { + driver.UpdaterSetFactory + configure driver.ConfigUnmarshaler +} + +func (f *configCaptureFactory) Configure(_ context.Context, configure driver.ConfigUnmarshaler, _ *http.Client) error { + f.configure = configure + return nil +} + +func rhelVexConfigForTest(t *testing.T) driver.ConfigUnmarshaler { + t.Helper() + factory := &configCaptureFactory{} + opts := append(rhelVexOpts(), updates.WithFactories(map[string]driver.UpdaterSetFactory{ + rhelVexUpdaterName: factory, + })) + _, err := updates.NewManager(context.Background(), nil, nil, http.DefaultClient, opts...) + require.NoError(t, err) + require.NotNil(t, factory.configure) + return factory.configure +} + +func TestRHELVexOpts(t *testing.T) { + configure := rhelVexConfigForTest(t) + for name, tc := range map[string]struct { + value string + want bool + err bool + }{ + "unset": {}, + "enabled": {value: "true", want: true}, + "disabled": {value: "false"}, + "invalid": {value: "invalid", err: true}, + } { + t.Run(name, func(t *testing.T) { + t.Setenv("STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES", tc.value) + t.Setenv("STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT", "10m") + factory := &vex.FactoryConfig{} + updater := &vex.UpdaterConfig{} + for _, cfg := range []any{factory, updater} { + err := configure(cfg) + if tc.err { + require.ErrorContains(t, err, "STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES") + } else { + require.NoError(t, err) + } + } + if !tc.err { + assert.False(t, factory.IgnoreKernelPackages) + assert.Equal(t, tc.want, updater.IgnoreKernelPackages) + assert.Equal(t, claircore.Duration(10*time.Minute), factory.CompressedFileTimeout) + } + }) + } +} + +func TestRHELVexOptsTimeout(t *testing.T) { + configure := rhelVexConfigForTest(t) + for name, tc := range map[string]struct { + value string + want time.Duration + }{ + "empty preserves default": {want: time.Minute}, + "invalid preserves default": {value: "invalid", want: time.Minute}, + "valid overrides default": {value: "10m", want: 10 * time.Minute}, + } { + t.Run(name, func(t *testing.T) { + t.Setenv("STACKROX_RHEL_VEX_COMPRESSED_FILE_TIMEOUT", tc.value) + t.Setenv("STACKROX_RHEL_VEX_IGNORE_KERNEL_PACKAGES", "true") + factory := &vex.FactoryConfig{CompressedFileTimeout: claircore.Duration(time.Minute)} + require.NoError(t, configure(factory)) + assert.Equal(t, claircore.Duration(tc.want), factory.CompressedFileTimeout) + updater := &vex.UpdaterConfig{} + require.NoError(t, configure(updater)) + assert.True(t, updater.IgnoreKernelPackages) + }) + } +} + func TestFilterSources(t *testing.T) { bundles := map[string][]updates.ManagerOption{ "alpha": nil, From 2ceb40c18eda6b901b98e24eb02809490942a6d8 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 23 Sep 2026 08:15:10 +0200 Subject: [PATCH 368/638] fix(gha): fix check-crd-compatibility check - revert (#22974) --- .github/workflows/check-crd-compatibility.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/check-crd-compatibility.yaml b/.github/workflows/check-crd-compatibility.yaml index a11f817d36c30..95ba18adc4a75 100644 --- a/.github/workflows/check-crd-compatibility.yaml +++ b/.github/workflows/check-crd-compatibility.yaml @@ -55,11 +55,6 @@ jobs: run: | make -C operator bundle-post-process released_version="$(make --no-print-directory -C operator replaced-version)" - if ! git rev-parse "${released_version}" >/dev/null 2>&1; then - echo "Tag ${released_version} does not exist yet, falling back to latest release tag" - released_version="$(git tag --list --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.0$' | head -1)" - echo "Using ${released_version}" - fi echo "released_version=$released_version" >> "$GITHUB_OUTPUT" - name: Checkout previous released version From e61caa52efec1cb6852865776bae2ef1c4d4a2d8 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 23 Sep 2026 08:19:02 +0200 Subject: [PATCH 369/638] chore(ci): bump create-demo-clusters.yml to v1.0.45 (#22815) Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/create-clusters.yml | 2 +- .github/workflows/cut-rc.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-clusters.yml b/.github/workflows/create-clusters.yml index 40d939145dfcc..5249b320bacbf 100644 --- a/.github/workflows/create-clusters.yml +++ b/.github/workflows/create-clusters.yml @@ -64,7 +64,7 @@ jobs: clusters: name: Setup demo clusters needs: trim-cluster-names - uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 + uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 secrets: inherit with: version: ${{github.event.inputs.version}} diff --git a/.github/workflows/cut-rc.yml b/.github/workflows/cut-rc.yml index 55870ab183efc..402dda5b0e6a2 100644 --- a/.github/workflows/cut-rc.yml +++ b/.github/workflows/cut-rc.yml @@ -331,7 +331,7 @@ jobs: name: Setup demo clusters secrets: inherit needs: [variables, cut-rc, trim-cluster-names] - uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@4efb76aa74694b9ee55e95c4ead57fdf50d79ddf # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 + uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 if: >- # Skip if no clusters are going to be created. Checking unequal with "false" because closed milestones have unset input values. github.event.inputs.create-k8s-cluster != 'false' || github.event.inputs.create-os4-cluster != 'false' || From dce74d58c7366d4a64169c96141a703dde6f98a1 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Wed, 23 Sep 2026 09:06:04 +0200 Subject: [PATCH 370/638] ROX-32329: Delete `STABLE_SCANNER_VERSION` (#22829) --- central/clusters/deployer.go | 13 -- central/clusters/deployer_test.go | 149 +++++++----------- central/scanner/handler.go | 2 - image/templates/common/setup.sh | 19 --- pkg/helm/charts/meta.go | 54 +++---- pkg/helm/charts/meta_test.go | 4 - .../centralservices/flavor/flavor_test.go | 21 +-- pkg/images/defaults/flavor.go | 40 ----- pkg/images/defaults/flavor_test.go | 3 - pkg/images/defaults/testutils/flavor.go | 34 ++-- pkg/renderer/images.go | 4 - pkg/renderer/images_test.go | 70 -------- pkg/renderer/kubernetes.go | 16 -- pkg/renderer/kubernetes_helm_test.go | 4 +- pkg/renderer/kubernetes_test.go | 5 +- pkg/renderer/render_test.go | 12 -- pkg/renderer/templater.go | 6 - pkg/renderer/templates/public_values.yaml.tpl | 3 - pkg/version/internal/version_data.go | 2 - pkg/version/testutils/set_version.go | 2 - pkg/version/version.go | 8 +- .../bundle_resources_completeness_test.go | 21 ++- status.sh | 1 - tests/versions_test.go | 1 - 24 files changed, 118 insertions(+), 376 deletions(-) diff --git a/central/clusters/deployer.go b/central/clusters/deployer.go index 8e01487cf2385..c7a8f1f38b449 100644 --- a/central/clusters/deployer.go +++ b/central/clusters/deployer.go @@ -32,7 +32,6 @@ func FieldsFromClusterAndRenderOpts(c *storage.Cluster, imageFlavor *defaults.Im baseValues := getBaseMetaValues(c, imageFlavor, imageFlavor.ChartRepo, &opts) setMainOverride(mainImage, baseValues) - deriveScannerRemoteFromMain(mainImage, baseValues) baseValues.EnablePodSecurityPolicies = !opts.DisablePodSecurityPolicies collector := determineCollectorImage(mainImage, collectorImage, imageFlavor) @@ -61,15 +60,6 @@ func MakeClusterImageNames(flavor *defaults.ImageFlavor, c *storage.Cluster) (*s return mainImageName, collectorImageName, nil } -// deriveScannerRemoteFromMain sets scanner-slim image remote, so that it comes from the same location as the main image -func deriveScannerRemoteFromMain(mainImage *storage.ImageName, metaValues *charts.MetaValues) { - scannerRemoteSlice := strings.Split(mainImage.GetRemote(), "/") - if len(scannerRemoteSlice) > 0 { - scannerRemoteSlice[len(scannerRemoteSlice)-1] = metaValues.ScannerSlimImageRemote - metaValues.ScannerSlimImageRemote = strings.Join(scannerRemoteSlice, "/") - } -} - // setMainOverride adds main image values to meta values as defined in secured cluster object. func setMainOverride(mainImage *storage.ImageName, metaValues *charts.MetaValues) { metaValues.MainRegistry = mainImage.GetRegistry() @@ -151,9 +141,6 @@ func getBaseMetaValues(c *storage.Cluster, imageFlavor *defaults.ImageFlavor, ch FactImageTag: versions.FactVersion, FactImageRemote: imageFlavor.FactImageName, - ScannerImageTag: versions.ScannerVersion, - ScannerSlimImageRemote: imageFlavor.ScannerSlimImageName, - KubectlOutput: true, Versions: versions, diff --git a/central/clusters/deployer_test.go b/central/clusters/deployer_test.go index c0b7ed70d8ef2..08e800b6dc56d 100644 --- a/central/clusters/deployer_test.go +++ b/central/clusters/deployer_test.go @@ -18,10 +18,6 @@ func getCollectorImage(fields *charts.MetaValues) string { return fmt.Sprintf("%s/%s:%s", fields.CollectorRegistry, fields.CollectorImageRemote, fields.CollectorImageTag) } -func getScannerSlim(fields *charts.MetaValues) string { - return fmt.Sprintf("%s/%s:%s", fields.MainRegistry, fields.ScannerSlimImageRemote, fields.ScannerImageTag) -} - func getMain(fields *charts.MetaValues) string { return fmt.Sprintf("%s/%s:%s", fields.MainRegistry, fields.ImageRemote, fields.ImageTag) } @@ -114,121 +110,102 @@ func testMetaValueGenerationWithImageFlavor(s *deployerTestSuite, flavor default defaultMainImage := flavor.MainImage() defaultCollectorImageNoTag := flavor.CollectorImageNoTag() defaultCollectorImage := flavor.CollectorImage() - defaultScannerSlimImage := flavor.ScannerSlimImage() var cases = map[string]struct { - cluster *storage.Cluster - expectedErrorMessage string - expectedMain string - expectedCollectorRef string - expectedScannerSlimRef string + cluster *storage.Cluster + expectedErrorMessage string + expectedMain string + expectedCollectorRef string }{ // we're testing possible main & collector combinations, grouped by main image setting // default main image "default main / no collector": { - cluster: makeTestCluster(defaultMainImageNoTag, ""), - expectedMain: defaultMainImage, - expectedCollectorRef: defaultCollectorImage, - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImageNoTag, ""), + expectedMain: defaultMainImage, + expectedCollectorRef: defaultCollectorImage, }, "default main / default collector": { - cluster: makeTestCluster(defaultMainImageNoTag, defaultCollectorImageNoTag), - expectedMain: defaultMainImage, - expectedCollectorRef: defaultCollectorImage, - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImageNoTag, defaultCollectorImageNoTag), + expectedMain: defaultMainImage, + expectedCollectorRef: defaultCollectorImage, }, "default main / default collector: custom tag": { - cluster: makeTestCluster(defaultMainImageNoTag, fmt.Sprintf("%s:custom", defaultCollectorImageNoTag)), - expectedMain: defaultMainImage, - expectedCollectorRef: flavor.CollectorImage(), - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImageNoTag, fmt.Sprintf("%s:custom", defaultCollectorImageNoTag)), + expectedMain: defaultMainImage, + expectedCollectorRef: flavor.CollectorImage(), }, "default main / custom collector: with namespace": { - cluster: makeTestCluster(defaultMainImage, "quay.io/rhacs/collector"), - expectedMain: defaultMainImage, - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImage, "quay.io/rhacs/collector"), + expectedMain: defaultMainImage, + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), }, "default main / custom collector: with namespace & custom tag": { - cluster: makeTestCluster(defaultMainImage, "quay.io/rhacs/collector:custom"), - expectedMain: defaultMainImage, - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImage, "quay.io/rhacs/collector:custom"), + expectedMain: defaultMainImage, + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), }, "default main / custom collector: without namespace": { - cluster: makeTestCluster(defaultMainImage, "example.io/collector"), - expectedMain: defaultMainImage, - expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImage, "example.io/collector"), + expectedMain: defaultMainImage, + expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), }, "default main / custom collector: without namespace & custom tag": { - cluster: makeTestCluster(defaultMainImage, "example.io/collector:custom"), - expectedMain: defaultMainImage, - expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(defaultMainImage, "example.io/collector:custom"), + expectedMain: defaultMainImage, + expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), }, "default main: custom tag / no collector": { - cluster: makeTestCluster(fmt.Sprintf("%s:custom", defaultMainImageNoTag), ""), - expectedMain: fmt.Sprintf("%s:custom", defaultMainImageNoTag), - expectedCollectorRef: defaultCollectorImage, - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(fmt.Sprintf("%s:custom", defaultMainImageNoTag), ""), + expectedMain: fmt.Sprintf("%s:custom", defaultMainImageNoTag), + expectedCollectorRef: defaultCollectorImage, }, "default main: custom tag / default collector": { - cluster: makeTestCluster(fmt.Sprintf("%s:custom", defaultMainImageNoTag), ""), - expectedMain: fmt.Sprintf("%s:custom", defaultMainImageNoTag), - expectedCollectorRef: defaultCollectorImage, - expectedScannerSlimRef: defaultScannerSlimImage, + cluster: makeTestCluster(fmt.Sprintf("%s:custom", defaultMainImageNoTag), ""), + expectedMain: fmt.Sprintf("%s:custom", defaultMainImageNoTag), + expectedCollectorRef: defaultCollectorImage, }, "default main: custom registry / no collector": { - cluster: makeTestCluster("quay.io/rhacs/"+flavor.MainImageName, ""), - expectedMain: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.MainImageName, flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/"+flavor.MainImageName, ""), + expectedMain: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.MainImageName, flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), }, // custom main image "custom main: with namespace / no collector": { - cluster: makeTestCluster("quay.io/rhacs/main", ""), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", ""), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), }, "custom main: without namespace / no collector": { - cluster: makeTestCluster("example.io/main", ""), - expectedMain: fmt.Sprintf("example.io/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("example.io/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("example.io/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("example.io/main", ""), + expectedMain: fmt.Sprintf("example.io/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("example.io/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), }, "custom main / default collector": { - cluster: makeTestCluster("quay.io/rhacs/main", defaultCollectorImageNoTag), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: defaultCollectorImage, - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", defaultCollectorImageNoTag), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: defaultCollectorImage, }, "custom main / custom collector: with namespace": { - cluster: makeTestCluster("quay.io/rhacs/main", "quay.io/rhacs/collector"), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", "quay.io/rhacs/collector"), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), }, "custom main / custom collector: with namespace & custom tag": { - cluster: makeTestCluster("quay.io/rhacs/main", "quay.io/rhacs/collector:custom"), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", "quay.io/rhacs/collector:custom"), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/collector:%s", flavor.CollectorImageTag), }, "custom main / custom collector: without namespace": { - cluster: makeTestCluster("quay.io/rhacs/main", "example.io/collector"), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", "example.io/collector"), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), }, "custom main / custom collector: without namespace & custom tag": { - cluster: makeTestCluster("quay.io/rhacs/main", "example.io/collector:custom"), - expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/main", "example.io/collector:custom"), + expectedMain: fmt.Sprintf("quay.io/rhacs/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("example.io/collector:%s", flavor.CollectorImageTag), }, /* // These tests are failing right now but should succeed after ROX-15691 has been implemented @@ -244,16 +221,14 @@ func testMetaValueGenerationWithImageFlavor(s *deployerTestSuite, flavor default }, */ "custom main / custom collector: same registry, different namespaces": { - cluster: makeTestCluster("quay.io/namespace-a/main", "quay.io/namespace-b/collector"), - expectedMain: fmt.Sprintf("quay.io/namespace-a/main:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/namespace-b/collector:%s", flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/namespace-a/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/namespace-a/main", "quay.io/namespace-b/collector"), + expectedMain: fmt.Sprintf("quay.io/namespace-a/main:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/namespace-b/collector:%s", flavor.CollectorImageTag), }, "custom main: non-default name / no collector": { - cluster: makeTestCluster("quay.io/rhacs/customname", ""), - expectedMain: fmt.Sprintf("quay.io/rhacs/customname:%s", flavor.MainImageTag), - expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), - expectedScannerSlimRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.ScannerSlimImageName, flavor.ScannerImageTag), + cluster: makeTestCluster("quay.io/rhacs/customname", ""), + expectedMain: fmt.Sprintf("quay.io/rhacs/customname:%s", flavor.MainImageTag), + expectedCollectorRef: fmt.Sprintf("quay.io/rhacs/%s:%s", flavor.CollectorImageName, flavor.CollectorImageTag), }, // Expected fail cases "expectedError: empty main image": { @@ -280,7 +255,6 @@ func testMetaValueGenerationWithImageFlavor(s *deployerTestSuite, flavor default s.NoError(err) s.Equal(c.expectedMain, getMain(fields), "Main image does not match") s.Equal(c.expectedCollectorRef, getCollectorImage(fields), "Collector image does not match") - s.Equal(c.expectedScannerSlimRef, getScannerSlim(fields), "Scanner slim image does not match") } }) } @@ -338,7 +312,6 @@ func TestRequiredFieldsArePresent(t *testing.T) { assert.NotEmpty(t, fields.Versions.ChartVersion) assert.NotEmpty(t, fields.Versions.MainVersion) assert.NotEmpty(t, fields.Versions.CollectorVersion) - assert.NotEmpty(t, fields.Versions.ScannerVersion) assert.NotEmpty(t, fields.ChartRepo.URL) } diff --git a/central/scanner/handler.go b/central/scanner/handler.go index 9890f5209aeed..9728df49a6a08 100644 --- a/central/scanner/handler.go +++ b/central/scanner/handler.go @@ -93,8 +93,6 @@ func generateFilesForScannerV1(params *apiparams.Scanner, clusterType storage.Cl ClusterType: clusterType, K8sConfig: &renderer.K8sConfig{ CommonConfig: renderer.CommonConfig{ - ScannerImage: stringutils.OrDefault(params.ScannerImage, flavor.ScannerImage()), - ScannerDBImage: stringutils.OrDefault(params.ScannerDBImage, flavor.ScannerDBImage()), ScannerV4Image: stringutils.OrDefault(params.ScannerV4Image, flavor.ScannerV4Image()), ScannerV4DBImage: stringutils.OrDefault(params.ScannerV4DBImage, flavor.ScannerV4DBImage()), }, diff --git a/image/templates/common/setup.sh b/image/templates/common/setup.sh index 4621967793d60..f0db17b406c7f 100644 --- a/image/templates/common/setup.sh +++ b/image/templates/common/setup.sh @@ -39,22 +39,3 @@ metadata: type: kubernetes.io/dockerconfigjson EOF fi - -{{ if ne .K8sConfig.ScannerSecretName "stackrox" }} -if ! ${KUBE_COMMAND} get secret/{{.K8sConfig.ScannerSecretName}} -n "$NAMESPACE" > /dev/null; then - registry_auth="$("${DIR}/docker-auth.sh" -m k8s "{{.K8sConfig.ScannerRegistry}}")" - [[ -n "$registry_auth" ]] || { echo >&2 "Unable to get registry auth info." ; exit 1 ; } - ${KUBE_COMMAND} create --namespace "$NAMESPACE" -f - < Date: Wed, 23 Sep 2026 09:54:17 +0200 Subject: [PATCH 371/638] chore(release): Add 4.10.9 to scanner updater configuration (#22985) Co-authored-by: vladbologa --- scanner/updater/version/RELEASE_VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index b86f49993d763..9461ad2816d53 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -15,6 +15,7 @@ 4.10.6 4.10.7 4.10.8 +4.10.9 4.11.0 4.11.1 4.11.2 From c20b5182016aef59197ad6f924d9ee4e9bcada1b Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:17:54 +0200 Subject: [PATCH 372/638] chore(ci): Move nightly tag one hour earlier (#23002) --- .github/workflows/nightly-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-tag.yml b/.github/workflows/nightly-tag.yml index cc87bb5e965ce..f40edc3724b0e 100644 --- a/.github/workflows/nightly-tag.yml +++ b/.github/workflows/nightly-tag.yml @@ -2,7 +2,7 @@ name: Create nightly tag on: workflow_dispatch: schedule: - - cron: 0 3 * * 1-5 + - cron: 0 2 * * 1-5 jobs: create-nightly-tag: if: ${{ github.repository_owner == 'stackrox' }} From 8654e4903a02637799c55f5055f15e7ba2bf3d75 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 23 Sep 2026 11:31:34 +0200 Subject: [PATCH 373/638] ROX-36480: retry go mod download before controller-gen (#22817) --- operator/Makefile | 10 ++++++++-- operator/hack/generate-and-install-chart.sh | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/operator/Makefile b/operator/Makefile index 8cfb88a07f6a3..496b3271ef53d 100755 --- a/operator/Makefile +++ b/operator/Makefile @@ -237,15 +237,21 @@ parent-proto-generate: ## Make sure ../generated directory has up-to-date conten [[ $${ROX_OPERATOR_SKIP_PROTO_GENERATED_SRCS:-false} = true ]] || $(MAKE) -C .. proto-generated-srcs .PHONY: manifests -manifests: parent-proto-generate controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. +manifests: parent-proto-generate controller-gen go-mod-download ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate -generate: parent-proto-generate controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +generate: parent-proto-generate controller-gen go-mod-download ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." # The generated source files might not comply with the current go formatting, so format them explicitly. go fmt ./api/... +.PHONY: go-mod-download +go-mod-download: ## Download modules we depend on, with retries. +# Warm up go module cache so that transient network failures do not abort controller-gen. +# Once downloaded, this command is very quick (~200 ms). + go mod download || go mod download || go mod download + .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... diff --git a/operator/hack/generate-and-install-chart.sh b/operator/hack/generate-and-install-chart.sh index f2fca504e9180..0399249b1be1b 100755 --- a/operator/hack/generate-and-install-chart.sh +++ b/operator/hack/generate-and-install-chart.sh @@ -19,4 +19,7 @@ echo >&2 "Deploying operator version ${version} from a temporary checkout at ${d git worktree add "${dir}" "${version}" trap 'git worktree remove --force "${dir}"' EXIT +# Warm up go module cache so that transient network failures do not abort controller-gen. +# TODO: this command can be removed once the version we upgrade from runs go-mod-download internally. +(cd "${dir}"; go mod download || go mod download || go mod download) make -C "${dir}/operator" chart deploy-via-chart VERSION="${version}" From 5bffd9137dd67be8b9063633172a8cf2515da090 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:16:47 +0200 Subject: [PATCH 374/638] chore(deps): bump the aws-sdk-go-v2 group across 1 directory with 9 updates (#22302) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 42 ++++++++++++++--------------- go.sum | 84 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/go.mod b/go.mod index 21a871dfc3ae8..900e204c83fc8 100644 --- a/go.mod +++ b/go.mod @@ -25,16 +25,16 @@ require ( github.com/VividCortex/ewma v1.2.0 github.com/adhocore/gronx v1.20.3 github.com/andygrunwald/go-jira v1.17.0 - github.com/aws/aws-sdk-go-v2 v1.43.4 - github.com/aws/aws-sdk-go-v2/config v1.32.35 - github.com/aws/aws-sdk-go-v2/credentials v1.19.34 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.41 - github.com/aws/aws-sdk-go-v2/service/ecr v1.60.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.107.0 - github.com/aws/aws-sdk-go-v2/service/securityhub v1.76.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 - github.com/aws/smithy-go v1.27.8 + github.com/aws/aws-sdk-go-v2 v1.47.0 + github.com/aws/aws-sdk-go-v2/config v1.33.5 + github.com/aws/aws-sdk-go-v2/credentials v1.20.5 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7 + github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1 + github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 + github.com/aws/smithy-go v1.28.1 github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 @@ -216,17 +216,17 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beevik/etree v1.7.0 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 753f8b9ee3f20..27a4b25850c01 100644 --- a/go.sum +++ b/go.sum @@ -213,50 +213,50 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.43.4 h1:b9FTvbRwy+JCsfp2Wp6wV/KbOx3Aj7nkoFb2cRX0IhE= -github.com/aws/aws-sdk-go-v2 v1.43.4/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 h1:aiuaKlDweRC5qExJondpWjOgyzMHpofpwspGXUtwn4c= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16/go.mod h1:nG/LOlmox9BDe9HvQnXWzgcK8uKbgBMZ/Hp5pVt/21I= -github.com/aws/aws-sdk-go-v2/config v1.32.35 h1:UEzXuET8E42lxBPijuACu/tEK7v5lFPlk0Q+GT5WD9E= -github.com/aws/aws-sdk-go-v2/config v1.32.35/go.mod h1:KaMtJpFa2JlL2BStjjHQVwQpzZEmw+ND/EgVrfFoo2g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.34 h1:y6GkSmcv5myd1ngrYbGmiLlwQqB6TQhOuN/tbSSuWDY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.34/go.mod h1:w3dTcnDVoQIewjo7JG45hduAToikiIFLC4FIO7fndvw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 h1:+S7kbJoLDDQ5tE+lHrUBgMkzC8NLgsaioS2F3dVoFAE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35/go.mod h1:Ak7xXviIARfFdNUJ9Etb0bdVDt/KAvKjMGJVLWXDzik= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.41 h1:RgRlA38QGJPnsG6TNEnMTQq8COa6Ye7AGwdnFlzd3Ys= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.41/go.mod h1:14QhagNwgiQqYUcAAJjQwgBXCcpxUau+8S/0YRLt5Uo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 h1:kzVuGlatQtYinwBJEEyLAbggepCoavosiaHHX9+fD+c= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35/go.mod h1:0yLx0yEI+SfqeJMPvOtIEFoZbiQYXMGszBueiutQyaI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 h1:WK6CjihTuLisCjSKKbildJ79sGZZgbBz3iNa7VsKIhU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35/go.mod h1:KYleN57luLoe97R7vTnx8PMcVrr9gAcRECtOjl91DNg= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 h1:jbGY4CXLzZElOXgGsexlC3Hi+3YM0rSmk4opFXKqg/k= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36/go.mod h1:uBu/9aKsS/UQGc72RAt3y54kjgYQxmhut8ZD2dXCDNE= -github.com/aws/aws-sdk-go-v2/service/ecr v1.60.4 h1:qIgueZwgw+rANqXuL1Cjk2Lm/Pihbw2nx3C1D7eQD+k= -github.com/aws/aws-sdk-go-v2/service/ecr v1.60.4/go.mod h1:Jr9Mh7l72YtNr8nmEwcJiU4xwCerZB8/krBWW4RckmY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 h1:JJLBQxwY+AFwuPAi5ivGc1ChnTdUt4cXMv7e76m2c/Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15/go.mod h1:lQknBIe78MVL0cQOQDlag8KGflMbMEVFx9mB6O8ENvk= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 h1:Q1TF1J9jVD+vFo0LzNnmNdQ9EAt52TS+MQlq9Ir+Yxo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28/go.mod h1:4KqXXC/p1hrotmouDFbrRoWaLy962b9PMUReCG6+uWo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 h1:BBEElKh4a+rKshvjrfpajTe9CbpZvrbb4Jkg2PB7RzA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35/go.mod h1:zaZk983w//8beSruBVec/mr4CmDwgZitW/qzGhAAX0g= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 h1:EUIwBoN+q7UmhAejxgD27APiRjh1vwCFo53gSqdT0BM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36/go.mod h1:6u00gmlTGR6W0b2k9NBrld7MnOEmf1Spqx0VVt6AqyE= +github.com/aws/aws-sdk-go-v2 v1.47.0 h1:0jsHallhJCeaU0Ko48c/3FK1ctOQ7NpzggxriJOQ8MQ= +github.com/aws/aws-sdk-go-v2 v1.47.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8= +github.com/aws/aws-sdk-go-v2/config v1.33.5 h1:UA1dmokBFOLFoOyVBhO6HjM6edy0MIk5AZSkJVcksQw= +github.com/aws/aws-sdk-go-v2/config v1.33.5/go.mod h1:Dop8axzz0xx38GExIYWXdeyc8QQ7Cr+nPsxpD/LYy4U= +github.com/aws/aws-sdk-go-v2/credentials v1.20.5 h1:wklUVvHMc9xTQ3rcp49/ISpiMnhbCicJcA6n6S8m7J8= +github.com/aws/aws-sdk-go-v2/credentials v1.20.5/go.mod h1:fyEdrn6ccLFOkoK84j5bQyGTxp9zPt5l2XMhxf4DVZs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 h1:AM4hHjww+PSFtt6E+UrBrPlZkWsePCLEt9AjkfQX+yM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0/go.mod h1:3x/yXezeQjpOvBb4jEMxrS8SXvpdvJ5abv6l5c1gWM8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7 h1:omtMzSxsMhRDJzG/1bzT4dLA+jybY/nQLwEuoxt52yI= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7/go.mod h1:oTPLfqQ08mCCN1qIkuZUsu0x6Iv/N7YEDax5qhCwN+o= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 h1:Hp/VgjP0BysR3OgLlR057Vz2LcbbVnoWeJ+3qWiS/fY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3/go.mod h1:nwGV5qw7F1IZPgxCvA/ph8N2TAuz+BkRG/bXn808qMA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 h1:MUaM4f+kj1ZIBPZfUS8cxP1GKXXZtHJjAthy93AN7SM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3/go.mod h1:6YmVmEVRI5ZZzRjCSsb9SryKH0hAlMRdgA7kG9aDvBU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 h1:fuSCw4Z2qfRCztMPO3GXJNSiEp6Wee+WOLwrHHUMy9c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3/go.mod h1:6SxcHheD1pPR5+kWm1wGvjlL/YqUsh267sAfEmN4K7A= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0 h1:9i19IigAYxnAxTUQcsxkiIXeeytHllTOetWBhr1DTQs= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0/go.mod h1:iSlv4VibruxMhWfiKrNbn97Yjhe2855EFrgUitSESfM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3 h1:BHKCSX4QXERe8So8rbWqaM7owqOmDJxATXgJwGng22A= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3/go.mod h1:GqWeeKfYfezihA2KfFL9l7ohEdZWe1tuFWh3GfyNSnE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 h1:bON1rJf67TSTDCKg816AAIE4xSTtoo9tl0XRkO72R+I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3/go.mod h1:c5BBpjJcQXpfeq9iASyVKA3T6vX6B6LEXY4mL/gklDY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3 h1:L8vIOxylma91TcR96NFTEC07G3JDwSl+CvK2b+IODms= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3/go.mod h1:fmPIZQzTExYuBNWFyi1P7IoDjvskgphXqK1yObMzusM= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0 h1:uB8ymkVosyourmGXCZHyWhJ4wuKA4xq3ii2dVMPtBZY= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0/go.mod h1:rK4RITSY/qJw3qVJ7p19fceOWuvrisqqOChFkX05n5I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.107.0 h1:OkYV+1171za+ab9otU1tGxMXhx6uZvwVEtVddjLuYTg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.107.0/go.mod h1:5FTZoQxhmLEiCAtYVk6V+t0iS/B5yGZVLZ3Wq5FDJZI= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.76.0 h1:J/olEWGa5NIIZiejnuG4u+ahM5liNIKa07sEP5OPbqs= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.76.0/go.mod h1:zHr00dDys+8JbwQALvat8WGyudYfzc2W3dw79e+bSWs= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 h1:cOJELVNrq5Q3Udry2GLuHUM7MhwpeaQRdYaoa6GI/yI= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.4/go.mod h1:f4LxzKBtaTxD7xh3PiVg3CE1tchQemfmghaJr+NbK2c= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 h1:AMW7a7S8iQaHjBYZdU3PCq4GKRPijTPRAc7e6XtEThY= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.4/go.mod h1:QQNsFV1DVXoXcZt18FS8lI8rtUrlDyAuWZLQ5shunv4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 h1:AsbZcJAQPRmHDJG8K1N0pof/1zPWjVT8TFlTWuGLSvo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4/go.mod h1:6imqztH0//t0mKbl6yWl7swSEl7F/w32oAmqB3vP1ag= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 h1:w/AryDYMjSUANSQ2uoZxJovUsMTwWJNTv3IMex30Y+4= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.4/go.mod h1:WeBiAa67azG7Su9Vf+ChGDBLiAozJCXzdjXiPBUwtbc= -github.com/aws/smithy-go v1.27.8 h1:FR0dxZfIlV7Z8eh2iHfIofdunw382XsDV3Mxt9nUvRY= -github.com/aws/smithy-go v1.27.8/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1 h1:cFHmwLxZPvtoAlo79MboURL2+7b0TMHycnrcf5VZNXk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1/go.mod h1:/uA+2Qj4jd5qBWagVC1AyzzDFXVK997E7U04w7Kw0wI= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 h1:sccblHjf5Swf5xjlBTAUT0Jc0m/KZW0PQP511Cto83A= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0/go.mod h1:39oJ1/fl7d10t0e9rtp/a+50tRLyDPSaccCZF3xXrcc= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 h1:ZD5qFpWcaOKdTuhBi431pIDkCgrMkMlMT6jlpSPoIRI= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.0/go.mod h1:8Nuuf+tR346PjJ3MvZPh9pekbLiLQFWJhzMXfwy7alA= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 h1:JGeeBcMlhg1xtOXYpeCaTQBZObtXMPQCUqBcmr65NRA= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.0/go.mod h1:XwteswG9EOMRFm73UT0t+MbTwyLxMrEXkU6e+v92Lzo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 h1:obhahQXDEdVEv8y5bTKXR30LVaxYe1kyYM0L7l2Iq+k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0/go.mod h1:6twZZ/aXHNy1vXUO8koUbp++MYzMASkOgEBdkbJYmO0= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 h1:Zpnqa6XtrNzXZnwbdCqHOXpXhMsa01ql/pcRQ1sb4hk= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM= +github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= +github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA= From 7a372add7813cda85838bb90ab29f67ba3ee9e13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:06:58 +0200 Subject: [PATCH 375/638] chore(deps-dev): bump nanoid from 3.3.4 to 3.3.19 in /tests/performance/load (#22565) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/load/package-lock.json | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 9655f48ba9e60..638304c522bd7 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -1718,10 +1718,17 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3855,9 +3862,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true }, "natural-compare": { From a65bc51bbc0b850511e657f4ae108c8997411bd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:08:02 +0200 Subject: [PATCH 376/638] chore(deps): bump k8s.io/client-go from 0.36.4 to 0.37.0 in /tests/performance/scale (#22572) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/scale/go.mod | 51 ++++++++------ tests/performance/scale/go.sum | 121 ++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 74 deletions(-) diff --git a/tests/performance/scale/go.mod b/tests/performance/scale/go.mod index 67cb702d7e08f..f8200e8e75790 100644 --- a/tests/performance/scale/go.mod +++ b/tests/performance/scale/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/spf13/cobra v1.10.2 go.yaml.in/yaml/v3 v3.0.5 - k8s.io/client-go v0.36.4 + k8s.io/client-go v0.37.0 ) require ( @@ -20,53 +20,62 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect - github.com/spf13/pflag v1.0.9 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.39.0 // indirect - golang.org/x/time v0.14.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.4 // indirect - k8s.io/apimachinery v0.36.4 // indirect + k8s.io/api v0.37.0 // indirect + k8s.io/apimachinery v0.37.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/tests/performance/scale/go.sum b/tests/performance/scale/go.sum index 3ef00b9025f19..62fc40ae1c7c9 100644 --- a/tests/performance/scale/go.sum +++ b/tests/performance/scale/go.sum @@ -14,7 +14,6 @@ github.com/cloud-bulldozer/go-commons v1.0.19/go.mod h1:CKmdWhyxN6p4leLhdwKxQHJ3 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,8 +26,8 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -41,14 +40,40 @@ github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyG github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -66,8 +91,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -75,15 +98,12 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -111,56 +131,52 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= -golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= @@ -176,26 +192,25 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= -k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= -k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= -k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= -k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= -k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= +k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= +k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= +k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw= -sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 7923c71789f813bfd9b1559646c14742330737c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:38:54 +0200 Subject: [PATCH 377/638] chore(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.2 in /operator/tools/operator-sdk (#22576) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/operator-sdk/go.mod | 32 +++++++------- operator/tools/operator-sdk/go.sum | 68 +++++++++++++++--------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index b414d606b1a0c..f235f2ab529e9 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -5,7 +5,7 @@ go 1.26.3 require github.com/operator-framework/operator-sdk v1.42.3 require ( - cel.dev/expr v0.25.1 // indirect + cel.dev/expr v0.25.2 // indirect dario.cat/mergo v1.0.2 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect @@ -194,33 +194,33 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.podman.io/common v0.66.1 // indirect go.podman.io/image/v5 v5.38.0 // indirect go.podman.io/storage v1.61.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect + golang.org/x/crypto v0.55.0 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect - google.golang.org/grpc v1.82.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index b435790cea172..89b1b7ac57e76 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= @@ -527,8 +527,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -553,16 +553,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.podman.io/common v0.66.1 h1:zDyd4HhVgQAN8LupBHCnhtM3FEOJ9DwmThjulXZq2qA= @@ -589,8 +589,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= @@ -604,8 +604,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -622,8 +622,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -638,8 +638,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -656,8 +656,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -667,8 +667,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -678,8 +678,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -694,8 +694,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -713,17 +713,17 @@ 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= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= -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/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 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= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 54b5f5dfce71443be1f2023a0369c3476c551080 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:36:43 +0200 Subject: [PATCH 378/638] ROX-27948: Wait for `central-db` before `central` (#23003) --- deploy/common/k8sbased.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index bbef8350d1bf2..ca845f3558327 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -635,7 +635,11 @@ function launch_central { # On some systems there's a race condition when port-forward connects to central but its pod then gets deleted due # to ongoing modifications to the central deployment. This port-forward dies and the script hangs "Waiting for # Central to respond" until it times out. Waiting for rollout status should help not get into such situation. + # Central-db is waited first because it needs to be online before Central can start. rollout_wait_timeout="10m" + if [[ -z "$EXTERNAL_DB" ]]; then + kubectl -n "${central_namespace}" rollout status deploy/central-db --timeout="${rollout_wait_timeout}" + fi kubectl -n "${central_namespace}" rollout status deploy/central --timeout="${rollout_wait_timeout}" # if we have specified that we want to use a load balancer, then use that endpoint instead of localhost From 46e400781d69a57d1588e99e8ca1924aed8538f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:22:24 +0200 Subject: [PATCH 379/638] chore(deps): bump sigs.k8s.io/controller-runtime/tools/setup-envtest from 0.24.1 to 0.25.1 in /operator/tools/envtest (#23001) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/envtest/go.mod | 6 +++--- operator/tools/envtest/go.sum | 38 +++++++++++++++++------------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/operator/tools/envtest/go.mod b/operator/tools/envtest/go.mod index b09764af31e09..61430b14f0fbd 100644 --- a/operator/tools/envtest/go.mod +++ b/operator/tools/envtest/go.mod @@ -2,7 +2,7 @@ module github.com/stackrox/rox/operator/tools/envtest go 1.26.3 -require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.24.1 +require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.25.1 require ( github.com/go-logr/logr v1.4.3 // indirect @@ -11,7 +11,7 @@ require ( github.com/spf13/pflag v1.0.10 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/text v0.34.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + golang.org/x/text v0.40.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/envtest/go.sum b/operator/tools/envtest/go.sum index 2c1eb021c9a07..83ff5ea829f9d 100644 --- a/operator/tools/envtest/go.sum +++ b/operator/tools/envtest/go.sum @@ -30,31 +30,29 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -sigs.k8s.io/controller-runtime/tools/setup-envtest v0.24.1 h1:l2AjyGE/PWub6EB165ij4/bpCK0TlY5NlhzIHfmGvmg= -sigs.k8s.io/controller-runtime/tools/setup-envtest v0.24.1/go.mod h1:wpkYufRHTSw9ABET21/PkEL7kdGnmiZJ6o72t9p/1I8= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.25.1 h1:OYp0AfSZ1zr338OwcQC4PsyBuaHjkgHEfPuNFTFqw2Y= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.25.1/go.mod h1:q4i4kLTq6J6o29RUAQieHH8LS5knNkpgalnBhvrLp/8= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 66c8a0af6d2b56debfa8b4563d393f5e3182fabe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:43:09 +0200 Subject: [PATCH 380/638] chore(deps): bump github.com/googleapis/gax-go/v2 from 2.23.0 to 2.24.1 (#22800) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 900e204c83fc8..9b8518d4ba47b 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/google/go-containerregistry v0.21.9 github.com/google/go-github/v91 v91.0.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.23.0 + github.com/googleapis/gax-go/v2 v2.24.1 github.com/gorilla/schema v1.4.1 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/pyroscope-go v1.4.2 @@ -156,9 +156,9 @@ require ( golang.org/x/tools v0.49.0 golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.292.0 - google.golang.org/genproto v0.0.0-20260622175928-b703f567277d + google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 - google.golang.org/grpc v1.83.0 + google.golang.org/grpc v1.83.1 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 gopkg.in/mcuadros/go-syslog.v2 v2.3.0 @@ -193,7 +193,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/iam v1.13.0 // indirect cloud.google.com/go/longrunning v1.2.0 // indirect - cloud.google.com/go/monitoring v1.29.0 // indirect + cloud.google.com/go/monitoring v1.30.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v9 v9.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect diff --git a/go.sum b/go.sum index 27a4b25850c01..9b43f6c4bf08d 100644 --- a/go.sum +++ b/go.sum @@ -56,12 +56,12 @@ cloud.google.com/go/iam v1.13.0 h1:ufT3FPT5rFFXu6UtLkNoxaOaV5EuA1dsSkmemCSTo6U= cloud.google.com/go/iam v1.13.0/go.mod h1:gHXdDEiPDvqd1q1KwBDGQlgZY/BwY760zU2LhOZS5w0= cloud.google.com/go/kms v1.33.0 h1:pG0X78m212b2pv9N4fdMoUO69LuZGQ9kSvn8sHBOFAo= cloud.google.com/go/kms v1.33.0/go.mod h1:CSGvW6GnMQbY+1nOHcIzhMtHSbExXlOmCKjWtYVjcpA= -cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k= -cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= +cloud.google.com/go/logging v1.19.0 h1:NCqhdVUg3wQ8Cobdf16FDSuTGi3+6+hdSBHrY5TsR6Q= +cloud.google.com/go/logging v1.19.0/go.mod h1:i40NZCHC9Gqvod4yE+yQfDWwlgwW/SrshkkGibCHxcA= cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM= cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0= -cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY= -cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM= +cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY= +cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -741,8 +741,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE= -github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg= +github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0= +github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= @@ -2034,8 +2034,8 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20260622175928-b703f567277d h1:CP5omUq8AJTiWMrPKM1WRLJ7zZeXd9OPcQD3TbBNAyY= -google.golang.org/genproto v0.0.0-20260622175928-b703f567277d/go.mod h1:DrwuGJgFSEVNpv3S5Q5VxhRTvdnjauw9GtvwVOEARfA= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 h1:4namukbyF7JY83aWHQwi9J5ugNTnDReLJ9ZcpqOpRB4= google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= @@ -2068,8 +2068,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= -google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 h1:ExN12ndbJ608cboPYflpTny6mXSzPrDLh0iTaVrRrds= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= From 19e53ec199b9aee3a66d62cd685a3b807387f4a0 Mon Sep 17 00:00:00 2001 From: Michael Petrov Date: Wed, 23 Sep 2026 19:28:12 +0200 Subject: [PATCH 381/638] ROX-36941: wait for namespace deletion in AdmissionControllerTest (#22802) Co-authored-by: Claude Opus 4.8 --- .../orchestratormanager/Kubernetes.groovy | 7 +++-- .../groovy/AdmissionControllerTest.groovy | 26 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy index 10d4cd84fa38e..14a7f0f7fac89 100644 --- a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy +++ b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy @@ -2680,8 +2680,11 @@ class Kubernetes { withRetry(2, 3) { client.namespaces().withName(ns).delete() } - if (waitForDeletion) { - waitForNamespaceDeletion(ns) + if (waitForDeletion && !waitForNamespaceDeletion(ns)) { + // Fail loudly: a namespace stuck Terminating leaks into later tests + // (e.g. NamespaceTest's ACS/orchestrator comparison) instead of being + // silently ignored. + throw new OrchestratorManagerException("Timed out waiting for namespace ${ns} to be deleted") } } diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy index 07a5f9c9f3308..c031ef3011efd 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy @@ -316,9 +316,16 @@ class AdmissionControllerTest extends BaseSpecification { if (created) { deleteDeploymentWithCaution(deployment) } - orchestrator.deleteNamespace(testNs, false) - if (policyId) { - PolicyService.deletePolicy(policyId) + try { + // Wait for full namespace deletion; a namespace left in Terminating state + // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). + orchestrator.deleteNamespace(testNs) + } finally { + // Delete the per-test policy even if the namespace deletion above times out + // and throws, otherwise the leftover policy affects later tests. + if (policyId) { + PolicyService.deletePolicy(policyId) + } } where: @@ -413,9 +420,16 @@ class AdmissionControllerTest extends BaseSpecification { if (created2) { deleteDeploymentWithCaution(deployment2) } - orchestrator.deleteNamespace(testNs, false) - if (policyId) { - PolicyService.deletePolicy(policyId) + try { + // Wait for full namespace deletion; a namespace left in Terminating state + // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). + orchestrator.deleteNamespace(testNs) + } finally { + // Delete the per-test policy even if the namespace deletion above times out + // and throws, otherwise the leftover policy affects later tests. + if (policyId) { + PolicyService.deletePolicy(policyId) + } } where: From 29658872243aab4bdaf47d857c47edc05ca6bf73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:50:50 +0200 Subject: [PATCH 382/638] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.43.0 to 1.45.0 (#22896) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 9b8518d4ba47b..80078a9f21b70 100644 --- a/go.mod +++ b/go.mod @@ -157,7 +157,7 @@ require ( golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.292.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d - google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d google.golang.org/grpc v1.83.1 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 @@ -447,13 +447,13 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect diff --git a/go.sum b/go.sum index 9b43f6c4bf08d..d9bccc9609c5e 100644 --- a/go.sum +++ b/go.sum @@ -1474,10 +1474,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252ccGzkEqLc2aSXbQIALYQ= @@ -1492,19 +1492,19 @@ go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIE go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.step.sm/crypto v0.87.0 h1:+u0pDz1OV9M/SswA6KtcQ3GSrYMMelbL//0W/ceJVYI= go.step.sm/crypto v0.87.0/go.mod h1:gBr1mpMiKs5804/Yt03uAoOBiw3DFxARZnGUKEUO82M= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -2036,8 +2036,8 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= -google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 h1:4namukbyF7JY83aWHQwi9J5ugNTnDReLJ9ZcpqOpRB4= -google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= From 6c42c01e6b278289dc9fecfb32c76f82e7de061b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 01:14:15 +0200 Subject: [PATCH 383/638] chore(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2 (#22715) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80078a9f21b70..4e9885af6a92c 100644 --- a/go.mod +++ b/go.mod @@ -158,7 +158,7 @@ require ( google.golang.org/api v0.292.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d - google.golang.org/grpc v1.83.1 + google.golang.org/grpc v1.83.2 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 gopkg.in/mcuadros/go-syslog.v2 v2.3.0 diff --git a/go.sum b/go.sum index d9bccc9609c5e..05243936b2c2f 100644 --- a/go.sum +++ b/go.sum @@ -2068,8 +2068,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= -google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 h1:ExN12ndbJ608cboPYflpTny6mXSzPrDLh0iTaVrRrds= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= From 53a3b76f155e45b5be11bcfbd739643301aa36d8 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 23 Sep 2026 19:27:37 -0400 Subject: [PATCH 384/638] ROX-33635: Delete ROX_BASE_IMAGE_DETECTION in ui (#19467) --- .../integration/baseImages/baseImages.test.ts | 7 ------- .../workloadCveOverviewPage.test.ts | 2 -- .../attributes/imageComponent.ts | 1 - .../Image/ImagePageVulnerabilities.tsx | 3 +-- .../ImageComponentVulnerabilitiesTable.cy.jsx | 3 +-- .../ImageComponentVulnerabilitiesTable.tsx | 20 +++++++------------ ui/apps/platform/src/routePaths.test.ts | 16 ++------------- ui/apps/platform/src/routePaths.ts | 1 - ui/apps/platform/src/types/featureFlag.ts | 1 - 9 files changed, 11 insertions(+), 43 deletions(-) diff --git a/ui/apps/platform/cypress/integration/baseImages/baseImages.test.ts b/ui/apps/platform/cypress/integration/baseImages/baseImages.test.ts index fd4cf8da93a38..ac126cc95443a 100644 --- a/ui/apps/platform/cypress/integration/baseImages/baseImages.test.ts +++ b/ui/apps/platform/cypress/integration/baseImages/baseImages.test.ts @@ -1,5 +1,4 @@ import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; import { interactAndWaitForResponses } from '../../helpers/request'; import { getRegExpForTitleWithBranding } from '../../helpers/title'; @@ -8,12 +7,6 @@ import { addBaseImage, visitBaseImages, visitBaseImagesFromLeftNav } from './bas describe('Base Images', () => { withAuth(); - before(function () { - if (!hasFeatureFlag('ROX_BASE_IMAGE_DETECTION')) { - this.skip(); - } - }); - it('should navigate to Base Images page from left nav', () => { visitBaseImagesFromLeftNav(); diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/workloadCveOverviewPage.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/workloadCveOverviewPage.test.ts index 5b685bca9a27e..12bb73e18817b 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/workloadCveOverviewPage.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/workloadCveOverviewPage.test.ts @@ -363,8 +363,6 @@ describe('Workload CVE overview page tests', () => { describe('Layer type filter tests', () => { it('should apply Layer type filter correctly', () => { - interceptAndOverrideFeatureFlags({ ROX_BASE_IMAGE_DETECTION: true }); - visitWorkloadCveOverview(); addCheckboxSelectFilter('Image component', 'Layer type', 'Base image'); diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageComponent.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageComponent.ts index e4556f1ca8ea4..72e9c5fbeb2a2 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageComponent.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageComponent.ts @@ -35,7 +35,6 @@ export const LayerType: CompoundSearchFilterAttribute = { filterChipLabel: 'Image component layer type', searchTerm: 'Component Layer Type', inputType: 'select', - featureFlagDependency: ['ROX_BASE_IMAGE_DETECTION'], inputProps: { options: [ { label: 'Application', value: 'APPLICATION' }, diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx index 8fd7bf3743048..5c797f0017163 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx @@ -153,7 +153,6 @@ function ImagePageVulnerabilities({ setSearchFilter, }: ImagePageVulnerabilitiesProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); - const isBaseImageDetectionEnabled = isFeatureFlagEnabled('ROX_BASE_IMAGE_DETECTION'); const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); const { analyticsTrack } = useAnalytics(); @@ -304,7 +303,7 @@ function ImagePageVulnerabilities({ - {isBaseImageDetectionEnabled && baseImage && ( + {baseImage && ( diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.cy.jsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.cy.jsx index 40539b413a11d..1aabe1ddf4b56 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.cy.jsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.cy.jsx @@ -5,8 +5,7 @@ import ImageComponentVulnerabilitiesTable from './ImageComponentVulnerabilitiesT const MockFeatureFlagsProvider = ({ children }) => ( - flag === 'ROX_BASE_IMAGE_DETECTION' || flag === 'ROX_SCANNER_V4', + isFeatureFlagEnabled: (flag) => flag === 'ROX_SCANNER_V4', isLoadingFeatureFlags: false, error: undefined, }} diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.tsx index 4632ef147f44e..0d73b14112fb4 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageComponentVulnerabilitiesTable.tsx @@ -61,13 +61,9 @@ function ImageComponentVulnerabilitiesTable({ const { isFeatureFlagEnabled } = useFeatureFlags(); const isAdvisoryColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); const isOriginColumnEnabled = isFeatureFlagEnabled('ROX_SCANNER_V4'); - const isLayerTypeColumnEnabled = isFeatureFlagEnabled('ROX_BASE_IMAGE_DETECTION'); const colSpanForDockerfileLayer = - 7 + - (isAdvisoryColumnEnabled ? 1 : 0) + - (isOriginColumnEnabled ? 1 : 0) + - (isLayerTypeColumnEnabled ? 1 : 0); + 8 + (isAdvisoryColumnEnabled ? 1 : 0) + (isOriginColumnEnabled ? 1 : 0); const { sortOption, getSortParams } = useTableSort({ sortFields, defaultSortOption }); const componentVulns = flattenImageComponentVulns( @@ -88,7 +84,7 @@ function ImageComponentVulnerabilitiesTable({ {isAdvisoryColumnEnabled && Advisory} Source {isOriginColumnEnabled && CVE origin} - {isLayerTypeColumnEnabled && Layer type} + Layer type Location @@ -140,13 +136,11 @@ function ImageComponentVulnerabilitiesTable({ {isOriginColumnEnabled && ( {getOriginDisplayName(origin)} )} - {isLayerTypeColumnEnabled && ( - - - - )} + + + diff --git a/ui/apps/platform/src/routePaths.test.ts b/ui/apps/platform/src/routePaths.test.ts index fb28cd02823be..4980660e36e16 100644 --- a/ui/apps/platform/src/routePaths.test.ts +++ b/ui/apps/platform/src/routePaths.test.ts @@ -14,28 +14,16 @@ import { getLinkToDeploymentInNetworkGraph, isRouteEnabled } from './routePaths' */ describe('routePaths', () => { describe('isRouteEnabled for base-images', () => { - it('should enable route when feature flag is enabled and user has ImageAdministration read access', () => { + it('should enable route when user has ImageAdministration read access', () => { const hasReadAccess: HasReadAccess = vi.fn( (resource) => resource === 'ImageAdministration' ); - const isFeatureFlagEnabled: IsFeatureFlagEnabled = vi.fn( - (flag) => flag === 'ROX_BASE_IMAGE_DETECTION' - ); + const isFeatureFlagEnabled: IsFeatureFlagEnabled = vi.fn(() => false); const enabled = isRouteEnabled({ hasReadAccess, isFeatureFlagEnabled }, 'base-images'); expect(enabled).toBe(true); expect(hasReadAccess).toHaveBeenCalledWith('ImageAdministration'); - expect(isFeatureFlagEnabled).toHaveBeenCalledWith('ROX_BASE_IMAGE_DETECTION'); - }); - - it('should disable route when feature flag is disabled', () => { - const hasReadAccess: HasReadAccess = vi.fn(() => true); - const isFeatureFlagEnabled: IsFeatureFlagEnabled = vi.fn(() => false); - - const enabled = isRouteEnabled({ hasReadAccess, isFeatureFlagEnabled }, 'base-images'); - - expect(enabled).toBe(false); }); it('should disable route when user lacks ImageAdministration read access', () => { diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 15a3f0f002533..33b931c842619 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -410,7 +410,6 @@ const routeRequirementsMap: Record = { resourceAccessRequirements: everyResource(['Cluster']), }, 'base-images': { - featureFlagRequirements: allEnabled(['ROX_BASE_IMAGE_DETECTION']), resourceAccessRequirements: everyResource(['ImageAdministration']), }, 'vulnerability-management': { diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index 4251f28f5af30..c427d4e9101dd 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -2,7 +2,6 @@ // However, add strings in alphabetical order to minimize merge conflicts when multiple people add or delete strings. // prettier-ignore export type FeatureFlagEnvVar = - | 'ROX_BASE_IMAGE_DETECTION' | 'ROX_CISA_KEV' | 'ROX_CVE_FIX_TIMESTAMP' | 'ROX_DEPRECATED_COMPLIANCE_DASHBOARD' From cf05ee309c2660d006652d2d1d555eefa1fc0954 Mon Sep 17 00:00:00 2001 From: "J. Victor Martins" Date: Wed, 23 Sep 2026 22:00:56 -0700 Subject: [PATCH 385/638] ROX-36439: align v3 exporter with release-4.11 claircore bump (#22900) --- scanner/updater/version/VULNERABILITY_BUNDLE_VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION b/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION index 8cf08d06206d0..8409e0debc779 100644 --- a/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION +++ b/scanner/updater/version/VULNERABILITY_BUNDLE_VERSION @@ -6,7 +6,7 @@ # This process ensures that each version's vulnerability data is accurately captured and maintained in accordance with its respective version. dev heads/master v2 heads/release-4.9.x/scanner-updater-v2 -# release-4.11 at 2026-08-06, after 4.11.3-rc.0. -v3 heads/2e8d7a35b1200eb29d5a75cad2a443719cf265e1 +# release-4.11 with claircore v1.5.55 +v3 heads/1e51a275092d9421b9acabaf09bca3ae3d59d3fc # master until the release branch is available. v4 heads/master From 2096e5229cb3cc8676e318c01609ce0de1ee8626 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Thu, 24 Sep 2026 08:45:58 +0200 Subject: [PATCH 386/638] fix(ci): Enable SFA for OCP candidate (#22972) An OCP release candidate flavor is called "openshift-4-ocp/candidate", but enable_sfa_for_ocp does not know that, thus SFA is not enabled on nightlies for OCP release candidates. Fix that by introducing a special case and assuming if we run on such a flavor, it's new enough to satisfy SFA requirements. --- .openshift-ci/common.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.openshift-ci/common.py b/.openshift-ci/common.py index 06cebaab6985b..fdf0d0bd212e5 100644 --- a/.openshift-ci/common.py +++ b/.openshift-ci/common.py @@ -39,6 +39,14 @@ def enable_sfa_for_ocp(): """ try: ocp_variant = os.environ.get("CLUSTER_FLAVOR_VARIANT", "") + + # A special case, OCP release candidate version. Most certainly newer + # than we require. + if ocp_variant == "openshift-4-ocp/candidate": + os.environ["SFA_AGENT"] = "true" + log_print("Enabled SFA agent for OCP", ocp_variant) + return + expr = r"openshift-4-ocp/\w+-(?P\d+)\.(?P\d+)" m = re.match(expr, ocp_variant) if m: From ebad3670b316ddfd8e3de6a54b950dea824a51b0 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 07:19:28 +0000 Subject: [PATCH 387/638] chore(deps): update quay.io/rhacs-eng/konflux-tasks:latest docker digest to 06b877f (#23016) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index e794aa07eb2aa..ded1ab7f050ca 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index fd29e0fb2426c..85bdfaa2ce8f1 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index d28c37768e656..1fa37a222d808 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 211d0915ba5fc..a6f8b2dbbb411 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index d3daf2a9cddda..cd0acc1e74e2a 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 94dc047ab378a..f67d85c66a613 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 9a81e73b72a13..10ce4bb760115 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index f32e1196845c3..1285b72837557 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:c0d2afcf938180935f8227f1d564fa94c3807595e8782063030c80575d55f313 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae - name: kind value: task resolver: bundles From ce3ee19305a25d6ac5ffb7eae45d854da7b1cca2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:37:57 +0200 Subject: [PATCH 388/638] chore(deps): bump stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml from f4660e8ce6bd7558e782e8e7cd26bad8cfbb6b9f to 6691d5c300d4af17546145bdbbb9862c53975af4 (#22851) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/konflux-auto-retest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/konflux-auto-retest.yml b/.github/workflows/konflux-auto-retest.yml index 03dcac1d1f6a5..2a76babeb7de6 100644 --- a/.github/workflows/konflux-auto-retest.yml +++ b/.github/workflows/konflux-auto-retest.yml @@ -7,7 +7,7 @@ on: jobs: retest: - uses: stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@f4660e8ce6bd7558e782e8e7cd26bad8cfbb6b9f # ratchet:stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@v1 + uses: stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@6691d5c300d4af17546145bdbbb9862c53975af4 # ratchet:stackrox/actions/.github/workflows/periodic-retest-konflux-builds.yml@v1 permissions: pull-requests: write issues: write From 988e4dbb56b0083ad9ad23008b076cc13ba2b7f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:38:31 +0200 Subject: [PATCH 389/638] chore(deps): bump stackrox/actions/roxie/install-cli from 43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 to 6691d5c300d4af17546145bdbbb9862c53975af4 (#22850) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-qa-tests.yaml | 2 +- .github/workflows/emailsender-central-compatibility.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index 56b47f044edf6..fe63f141908e0 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -189,7 +189,7 @@ jobs: run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" - name: Install roxie - uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + uses: stackrox/actions/roxie/install-cli@6691d5c300d4af17546145bdbbb9862c53975af4 # ratchet:stackrox/actions@v1 with: version: "v${{ steps.roxie-version.outputs.version }}" diff --git a/.github/workflows/emailsender-central-compatibility.yaml b/.github/workflows/emailsender-central-compatibility.yaml index 7dd300125b10e..9600d3cf78c94 100644 --- a/.github/workflows/emailsender-central-compatibility.yaml +++ b/.github/workflows/emailsender-central-compatibility.yaml @@ -68,7 +68,7 @@ jobs: run: echo "version=$(cat stackrox/ROXIE_VERSION)" >> "$GITHUB_OUTPUT" - name: Install roxie - uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + uses: stackrox/actions/roxie/install-cli@6691d5c300d4af17546145bdbbb9862c53975af4 # ratchet:stackrox/actions@v1 with: version: "v${{ steps.roxie-version.outputs.version }}" From bcc6e50021f5c956225f6fdc71ff3323b275dda9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:46:56 +0200 Subject: [PATCH 390/638] chore(deps): bump the golang-org-x group with 5 updates (#23024) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 4e9885af6a92c..a7c34188710c9 100644 --- a/go.mod +++ b/go.mod @@ -144,16 +144,16 @@ require ( go.uber.org/mock v0.6.0 go.uber.org/zap v1.28.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.56.0 + golang.org/x/crypto v0.57.0 golang.org/x/mod v0.41.0 - golang.org/x/net v0.58.0 + golang.org/x/net v0.59.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.23.0 golang.org/x/sys v0.48.0 - golang.org/x/term v0.45.0 - golang.org/x/text v0.41.0 + golang.org/x/term v0.46.0 + golang.org/x/text v0.42.0 golang.org/x/time v0.16.0 - golang.org/x/tools v0.49.0 + golang.org/x/tools v0.50.0 golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.292.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d diff --git a/go.sum b/go.sum index 05243936b2c2f..875d2ef52935c 100644 --- a/go.sum +++ b/go.sum @@ -1550,8 +1550,8 @@ golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqj golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1670,8 +1670,8 @@ golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1812,8 +1812,8 @@ golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1833,8 +1833,8 @@ golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1911,8 +1911,8 @@ golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg golang.org/x/tools v0.34.1-0.20250613162507-3f93fece84c7/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= +golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= From b93bdebd9641784179ec71714bb1ba33f46a3f32 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Thu, 24 Sep 2026 11:19:46 +0200 Subject: [PATCH 391/638] fix(ci): amend manifest list on retry to avoid stale failure (#22856) --- scripts/ci/push-as-multiarch-manifest-list.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/ci/push-as-multiarch-manifest-list.sh b/scripts/ci/push-as-multiarch-manifest-list.sh index 6c55d6f5ed9b4..01c6387506ef2 100755 --- a/scripts/ci/push-as-multiarch-manifest-list.sh +++ b/scripts/ci/push-as-multiarch-manifest-list.sh @@ -22,7 +22,10 @@ do image_list+=("$arch_image") done -docker manifest create "$image" "${image_list[@]}" +# --amend: this script is retried on failure (see push_*_manifest_lists in +# scripts/ci/lib.sh), and "docker manifest create" errors out if a local +# manifest list of the same name already exists, which it would on a retry. +docker manifest create --amend "$image" "${image_list[@]}" # Try pushing manifest a few times for the case when quay.io has issues pushed=0 From 850962ee6bf8006f85c4dd0c6acd160c0414a944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:42:16 +0200 Subject: [PATCH 392/638] chore(deps): bump sigs.k8s.io/kubebuilder/v4 from 4.15.0 to 4.16.0 in /operator/tools/kubebuilder (#22853) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/kubebuilder/go.mod | 23 ++++++----- operator/tools/kubebuilder/go.sum | 66 +++++++++++++------------------ 2 files changed, 39 insertions(+), 50 deletions(-) diff --git a/operator/tools/kubebuilder/go.mod b/operator/tools/kubebuilder/go.mod index 0225addfc86f7..adba09128eb2b 100644 --- a/operator/tools/kubebuilder/go.mod +++ b/operator/tools/kubebuilder/go.mod @@ -2,7 +2,7 @@ module github.com/stackrox/rox/operator/tools/kubebuilder go 1.26.3 -require sigs.k8s.io/kubebuilder/v4 v4.15.0 +require sigs.k8s.io/kubebuilder/v4 v4.16.0 require ( github.com/fxamacker/cbor/v2 v2.9.1 // indirect @@ -10,7 +10,6 @@ require ( github.com/gobuffalo/flect v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/spf13/afero v1.15.0 // indirect @@ -18,19 +17,19 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/text v0.38.0 // indirect - golang.org/x/tools v0.46.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.49.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/apimachinery v0.36.2 // indirect + k8s.io/apimachinery v0.37.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f // indirect - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/kubebuilder/go.sum b/operator/tools/kubebuilder/go.sum index 7577d7cafe457..c18e6f79e30c3 100644 --- a/operator/tools/kubebuilder/go.sum +++ b/operator/tools/kubebuilder/go.sum @@ -1,7 +1,6 @@ github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -27,27 +26,19 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.30.0 h1:zxM/9XneXFIy64j6/wAmBIX4zRC7Hu6U8XFNZvDnCQc= -github.com/onsi/ginkgo/v2 v2.30.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= -github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU= +github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= @@ -69,43 +60,42 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= -golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= -k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f h1:4Qiq0YAoQATdgmHALJWz9rJ4fj20pB3xebpB4CFNhYM= -k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kubebuilder/v4 v4.15.0 h1:LiTS6XCCmhxS0P7OXwpVqcoywlvbF3yK8G+tZjZF62A= -sigs.k8s.io/kubebuilder/v4 v4.15.0/go.mod h1:3Q0bwaT7zJg9V1wSqYOCClA29iAYkR9CVe+iqMuBg1w= +sigs.k8s.io/kubebuilder/v4 v4.16.0 h1:ay3e//yOWy2Jmi3JZnF8esrtCi0+/6jiPXiRtmW0Wu8= +sigs.k8s.io/kubebuilder/v4 v4.16.0/go.mod h1:ghrV2jGJIxjPVnzALWGVZyfpzIMzpMnN/LcYg7pZuZc= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 8da3938b88282a354c29d190a3b2d701ae8ab054 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:41:03 +0200 Subject: [PATCH 393/638] chore(deps): bump oras.land/oras-go/v2 from 2.6.1 to 2.6.2 (#22885) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a7c34188710c9..484cb848bca18 100644 --- a/go.mod +++ b/go.mod @@ -477,7 +477,7 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.12.1 // indirect modernc.org/sqlite v1.58.0 // indirect - oras.land/oras-go/v2 v2.6.1 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect diff --git a/go.sum b/go.sum index 875d2ef52935c..f932612639f81 100644 --- a/go.sum +++ b/go.sum @@ -2213,8 +2213,8 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 3b6dcc3bde552e8c12d6c4bca2169f45274236c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:41:22 +0200 Subject: [PATCH 394/638] chore(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.1 (#22395) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 484cb848bca18..a0edf0391dc83 100644 --- a/go.mod +++ b/go.mod @@ -134,7 +134,7 @@ require ( github.com/stackrox/pkcs7 v0.0.0-20240314170115-841ca6b5f88d github.com/stackrox/scanner v0.0.0-20240830165150-d133ba942d59 github.com/stolostron/cluster-lifecycle-api v0.0.0-20260330032750-43755d6ceb09 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.12.1 github.com/tidwall/gjson v1.19.0 github.com/tkuchiki/go-timezone v0.2.3 github.com/travelaudience/go-promhttp v1.0.1 diff --git a/go.sum b/go.sum index f932612639f81..13a8e545e60dd 100644 --- a/go.sum +++ b/go.sum @@ -1346,8 +1346,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= From 26ca6d5b8ba3aaed0ca86bf6c25ecc3775748ef2 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier <111092021+mclasmeier@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:50:21 +0200 Subject: [PATCH 395/638] ROX-37173: Don't send operator's first log message into void (#22229) --- operator/cmd/main.go | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/operator/cmd/main.go b/operator/cmd/main.go index 55537fdbeb212..fdcb412405a44 100644 --- a/operator/cmd/main.go +++ b/operator/cmd/main.go @@ -20,10 +20,12 @@ import ( "context" "crypto/tls" "flag" + "fmt" "os" "strconv" "time" + "github.com/go-logr/logr" "github.com/go-logr/zapr" configv1 "github.com/openshift/api/config/v1" consolev1 "github.com/openshift/api/console/v1" @@ -73,7 +75,6 @@ const ( ) var ( - setupLog = ctrl.Log.WithName("setup") scheme = runtime.NewScheme() enableProfiling = env.RegisterBooleanSetting("ENABLE_PROFILING", false) profilingThresholdFraction = env.RegisterSetting("PROFILING_THRESHOLD_FRACTION", env.WithDefault("0.8")) @@ -113,25 +114,27 @@ func init() { func main() { if err := run(); err != nil { - setupLog.Error(err, "fatal error") + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } -func run() error { - setupLog.Info("Starting RHACS Operator", "version", version.GetMainVersion()) +type runtimeConfig struct { + metricsAddr string + enableLeaderElection bool + probeAddr string + enableHTTP2 bool +} - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - var enableHTTP2 bool +func run() error { + var config runtimeConfig - flag.StringVar(&metricsAddr, "metrics-bind-address", "0.0.0.0:8443", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, + flag.StringVar(&config.metricsAddr, "metrics-bind-address", "0.0.0.0:8443", "The address the metric endpoint binds to.") + flag.StringVar(&config.probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&config.enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&enableHTTP2, "enable-http2", enableHTTP2, "If HTTP/2 should be enabled for the metrics server.") + flag.BoolVar(&config.enableHTTP2, "enable-http2", false, "If HTTP/2 should be enabled for the metrics server.") opts := zap.Options{ Development: !buildinfo.ReleaseBuild, @@ -141,13 +144,20 @@ func run() error { zapLogger := zap.NewRaw(zap.UseFlagOptions(&opts)) ctrl.SetLogger(zapr.NewLogger(zapLogger)) + setupLog := ctrl.Log.WithName("setup") restore, err := rawZap.RedirectStdLogAt(zapLogger, zapcore.DebugLevel) if err != nil { return errors.Wrap(err, "unable to redirect std log") } defer restore() - clusterTLSProfile, tlsOpts, err := buildMetricsServerTLSOpts(enableHTTP2) + return runWithLogger(setupLog, config) +} + +func runWithLogger(setupLog logr.Logger, config runtimeConfig) error { + setupLog.Info("Starting RHACS Operator", "version", version.GetMainVersion()) + + clusterTLSProfile, tlsOpts, err := buildMetricsServerTLSOpts(setupLog, config.enableHTTP2) if err != nil { return err } @@ -165,7 +175,7 @@ func run() error { mgr, err := ctrl.NewManager(utils.GetRHACSConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{ - BindAddress: metricsAddr, + BindAddress: config.metricsAddr, SecureServing: true, FilterProvider: filters.WithAuthenticationAndAuthorization, TLSOpts: tlsOpts, @@ -183,8 +193,8 @@ func run() error { }, }, }, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, + HealthProbeBindAddress: config.probeAddr, + LeaderElection: config.enableLeaderElection, LeaderElectionID: "bf7ea6a2.stackrox.io", }) if err != nil { @@ -280,7 +290,7 @@ func run() error { // buildMetricsServerTLSOpts reads the cluster TLS profile and returns the // TLS options for the Operator's metrics server along with the cluster profile // for later use by the watcher and operand enricher. -func buildMetricsServerTLSOpts(enableHTTP2 bool) (*tlsprofile.ClusterTLSProfile, []func(c *tls.Config), error) { +func buildMetricsServerTLSOpts(setupLog logr.Logger, enableHTTP2 bool) (*tlsprofile.ClusterTLSProfile, []func(c *tls.Config), error) { bootstrapClient, err := ctrlClient.New(utils.GetRHACSConfigOrDie(), ctrlClient.Options{Scheme: scheme}) if err != nil { return nil, nil, errors.Wrap(err, "unable to create bootstrap client for TLS profile") From bdb0b23f77afa88a43a4258ddb2dfac49bc6a97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:18:14 +0200 Subject: [PATCH 396/638] chore(deps): bump google.golang.org/protobuf from 1.36.11 to 1.36.12 in /tools/proto (#22363) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: StackRox PR Fixxxer --- generated/api/common/extended_rpc_status.pb.go | 2 +- generated/api/integrations/splunk_service.pb.go | 2 +- generated/api/v1/administration_events_service.pb.go | 2 +- generated/api/v1/administration_usage_service.pb.go | 2 +- generated/api/v1/alert_service.pb.go | 2 +- generated/api/v1/api_token_service.pb.go | 2 +- generated/api/v1/audit.pb.go | 2 +- generated/api/v1/auth_service.pb.go | 2 +- generated/api/v1/authprovider_service.pb.go | 2 +- generated/api/v1/backup_service.pb.go | 2 +- generated/api/v1/central_health_service.pb.go | 2 +- generated/api/v1/cloud_source_service.pb.go | 2 +- generated/api/v1/cluster_init_service.pb.go | 2 +- generated/api/v1/cluster_service.pb.go | 2 +- generated/api/v1/common.pb.go | 2 +- generated/api/v1/compliance_management_service.pb.go | 2 +- generated/api/v1/compliance_service.pb.go | 2 +- generated/api/v1/config_service.pb.go | 2 +- generated/api/v1/credential_expiry_service.pb.go | 2 +- generated/api/v1/cve_service.pb.go | 2 +- generated/api/v1/db_service.pb.go | 2 +- generated/api/v1/debug_service.pb.go | 2 +- generated/api/v1/declarative_config_health_service.pb.go | 2 +- generated/api/v1/delegated_registry_config_service.pb.go | 2 +- generated/api/v1/deployment_service.pb.go | 2 +- generated/api/v1/detection_service.pb.go | 2 +- generated/api/v1/discovered_cluster_service.pb.go | 2 +- generated/api/v1/empty.pb.go | 2 +- generated/api/v1/feature_flag_service.pb.go | 2 +- generated/api/v1/group_service.pb.go | 2 +- generated/api/v1/grpc_preference_service.pb.go | 2 +- generated/api/v1/image_integration_service.pb.go | 2 +- generated/api/v1/image_service.pb.go | 2 +- generated/api/v1/integration_health_service.pb.go | 2 +- generated/api/v1/metadata_service.pb.go | 2 +- generated/api/v1/mitre_service.pb.go | 2 +- generated/api/v1/namespace_service.pb.go | 2 +- generated/api/v1/network_baseline_service.pb.go | 2 +- generated/api/v1/network_graph_service.pb.go | 2 +- generated/api/v1/network_policy_service.pb.go | 2 +- generated/api/v1/node_service.pb.go | 2 +- generated/api/v1/notifications.pb.go | 2 +- generated/api/v1/notifier_service.pb.go | 2 +- generated/api/v1/pagination.pb.go | 2 +- generated/api/v1/ping_service.pb.go | 2 +- generated/api/v1/pod_service.pb.go | 2 +- generated/api/v1/policy_category_service.pb.go | 2 +- generated/api/v1/policy_service.pb.go | 2 +- generated/api/v1/probe_upload_service.pb.go | 2 +- generated/api/v1/process_baseline_service.pb.go | 2 +- generated/api/v1/process_listening_on_port_service.pb.go | 2 +- generated/api/v1/process_service.pb.go | 2 +- generated/api/v1/rbac_service.pb.go | 2 +- generated/api/v1/resource_collection_service.pb.go | 2 +- generated/api/v1/role_service.pb.go | 2 +- generated/api/v1/sbom.pb.go | 2 +- generated/api/v1/search_service.pb.go | 2 +- generated/api/v1/secret_service.pb.go | 2 +- generated/api/v1/sensor_upgrade_service.pb.go | 2 +- generated/api/v1/service_account_service.pb.go | 2 +- generated/api/v1/service_identity_service.pb.go | 2 +- generated/api/v1/signal.pb.go | 2 +- generated/api/v1/signature_integration_service.pb.go | 2 +- generated/api/v1/telemetry_service.pb.go | 2 +- generated/api/v1/traits.pb.go | 2 +- generated/api/v1/user_service.pb.go | 2 +- generated/api/v1/vuln_mgmt_service.pb.go | 2 +- generated/api/v2/base_image_service.pb.go | 2 +- generated/api/v2/common.pb.go | 2 +- generated/api/v2/compliance_common.pb.go | 2 +- generated/api/v2/compliance_integration_service.pb.go | 2 +- generated/api/v2/compliance_profile_service.pb.go | 2 +- generated/api/v2/compliance_results_service.pb.go | 2 +- generated/api/v2/compliance_results_stats_service.pb.go | 2 +- generated/api/v2/compliance_rule_service.pb.go | 2 +- generated/api/v2/compliance_scan_configuration_service.pb.go | 2 +- generated/api/v2/node_report_service.pb.go | 2 +- generated/api/v2/pagination.pb.go | 2 +- generated/api/v2/report_service.pb.go | 2 +- generated/api/v2/scan_component.pb.go | 2 +- generated/api/v2/search_query.pb.go | 2 +- generated/api/v2/user.pb.go | 2 +- generated/api/v2/virtual_machine_service.pb.go | 2 +- generated/api/v2/virtual_machine_v2_service.pb.go | 2 +- generated/api/v2/vuln_exception_service.pb.go | 2 +- generated/api/v2/vuln_state.pb.go | 2 +- generated/api/v2/vulnerability.pb.go | 2 +- generated/internalapi/central/auth.pb.go | 2 +- generated/internalapi/central/baseline_sync.pb.go | 2 +- generated/internalapi/central/cluster_config.pb.go | 2 +- generated/internalapi/central/cluster_metrics.pb.go | 2 +- generated/internalapi/central/cluster_status.pb.go | 2 +- generated/internalapi/central/compliance_operator.pb.go | 2 +- generated/internalapi/central/delegated_registry_config.pb.go | 2 +- generated/internalapi/central/deployment_enhancement.pb.go | 2 +- generated/internalapi/central/development_service.pb.go | 2 +- generated/internalapi/central/hello.pb.go | 2 +- generated/internalapi/central/image.pb.go | 2 +- generated/internalapi/central/local_scanner.pb.go | 2 +- generated/internalapi/central/network_baseline_sync.pb.go | 2 +- generated/internalapi/central/network_flow.pb.go | 2 +- generated/internalapi/central/policy_sync.pb.go | 2 +- .../central/process_listening_on_ports_update.pb.go | 2 +- .../internalapi/central/secured_cluster_cert_refresh.pb.go | 2 +- generated/internalapi/central/sensor_events.pb.go | 2 +- generated/internalapi/central/sensor_iservice.pb.go | 2 +- generated/internalapi/central/sensor_upgrade.pb.go | 2 +- .../internalapi/central/sensor_upgrade_ctrl_iservice.pb.go | 2 +- generated/internalapi/central/telemetry.pb.go | 2 +- generated/internalapi/central/v1/token_service.pb.go | 2 +- generated/internalapi/compliance/compliance_data.pb.go | 2 +- generated/internalapi/scanner/v4/common.pb.go | 2 +- generated/internalapi/scanner/v4/index_report.pb.go | 2 +- generated/internalapi/scanner/v4/indexer_service.pb.go | 2 +- generated/internalapi/scanner/v4/matcher_service.pb.go | 2 +- generated/internalapi/scanner/v4/vulnerability_report.pb.go | 2 +- generated/internalapi/sensor/admission_control.pb.go | 2 +- generated/internalapi/sensor/admission_control_iservice.pb.go | 2 +- generated/internalapi/sensor/collector.pb.go | 2 +- generated/internalapi/sensor/collector_iservice.pb.go | 2 +- generated/internalapi/sensor/compliance_iservice.pb.go | 2 +- generated/internalapi/sensor/deployment_iservice.pb.go | 2 +- generated/internalapi/sensor/fact.pb.go | 2 +- generated/internalapi/sensor/image_iservice.pb.go | 2 +- generated/internalapi/sensor/network_connection_info.pb.go | 2 +- .../internalapi/sensor/network_connection_iservice.pb.go | 2 +- generated/internalapi/sensor/network_enums.pb.go | 2 +- generated/internalapi/sensor/sfa.pb.go | 2 +- generated/internalapi/sensor/sfa_iservice.pb.go | 2 +- generated/internalapi/sensor/signal_iservice.pb.go | 2 +- generated/internalapi/virtualmachine/v1/index_report.pb.go | 2 +- generated/internalapi/virtualmachine/v1/virtual_machine.pb.go | 2 +- generated/internalapi/virtualmachine/v1/vm_service.pb.go | 2 +- generated/internalapi/wrapper/splunk_alert.pb.go | 2 +- generated/storage/administration_event.pb.go | 2 +- generated/storage/administration_usage.pb.go | 2 +- generated/storage/alert.pb.go | 2 +- generated/storage/api_token.pb.go | 2 +- generated/storage/auth_machine_to_machine.pb.go | 2 +- generated/storage/auth_provider.pb.go | 2 +- generated/storage/base_image.pb.go | 2 +- generated/storage/base_image_repository.pb.go | 2 +- generated/storage/blob.pb.go | 2 +- generated/storage/cloud_source.pb.go | 2 +- generated/storage/cluster.pb.go | 2 +- generated/storage/cluster_init.pb.go | 2 +- generated/storage/common.pb.go | 2 +- generated/storage/compliance.pb.go | 2 +- generated/storage/compliance_config.pb.go | 2 +- generated/storage/compliance_integration.pb.go | 2 +- generated/storage/compliance_operator.pb.go | 2 +- generated/storage/compliance_operator_v2.pb.go | 2 +- generated/storage/config.pb.go | 2 +- generated/storage/container_runtime.pb.go | 2 +- generated/storage/cve.pb.go | 2 +- generated/storage/declarative_config_health.pb.go | 2 +- generated/storage/delegated_registry_config.pb.go | 2 +- generated/storage/deployment.pb.go | 2 +- generated/storage/discovered_cluster.pb.go | 2 +- generated/storage/external_backup.pb.go | 2 +- generated/storage/file_access.pb.go | 2 +- generated/storage/group.pb.go | 2 +- generated/storage/hash.pb.go | 2 +- generated/storage/helm_cluster.pb.go | 2 +- generated/storage/http_endpoint.pb.go | 2 +- generated/storage/image.pb.go | 2 +- generated/storage/image_component.pb.go | 2 +- generated/storage/image_integration.pb.go | 2 +- generated/storage/image_v2.pb.go | 2 +- generated/storage/installation.pb.go | 2 +- generated/storage/integration_health.pb.go | 2 +- generated/storage/kube_event.pb.go | 2 +- generated/storage/labels.pb.go | 2 +- generated/storage/log.pb.go | 2 +- generated/storage/mitre.pb.go | 2 +- generated/storage/namespace_metadata.pb.go | 2 +- generated/storage/network_baseline.pb.go | 2 +- generated/storage/network_flow.pb.go | 2 +- generated/storage/network_graph_config.pb.go | 2 +- generated/storage/network_policy.pb.go | 2 +- generated/storage/node.pb.go | 2 +- generated/storage/node_component.pb.go | 2 +- generated/storage/node_integration.pb.go | 2 +- generated/storage/notification_schedule.pb.go | 2 +- generated/storage/notifier.pb.go | 2 +- generated/storage/notifier_enc_config.pb.go | 2 +- generated/storage/operation_status.pb.go | 2 +- generated/storage/orchestrator_integration.pb.go | 2 +- generated/storage/policy.pb.go | 2 +- generated/storage/policy_category.pb.go | 2 +- generated/storage/process_baseline.pb.go | 2 +- generated/storage/process_indicator.pb.go | 2 +- generated/storage/process_listening_on_port.pb.go | 2 +- generated/storage/rbac.pb.go | 2 +- generated/storage/relations.pb.go | 2 +- generated/storage/report_configuration.pb.go | 2 +- generated/storage/report_notifier_configuration.pb.go | 2 +- generated/storage/report_snapshot.pb.go | 2 +- generated/storage/resource_collection.pb.go | 2 +- generated/storage/risk.pb.go | 2 +- generated/storage/role.pb.go | 2 +- generated/storage/schedule.pb.go | 2 +- generated/storage/scope.pb.go | 2 +- generated/storage/secret.pb.go | 2 +- generated/storage/sensor_upgrade.pb.go | 2 +- generated/storage/service_account.pb.go | 2 +- generated/storage/service_identity.pb.go | 2 +- generated/storage/signature_integration.pb.go | 2 +- generated/storage/system_info.pb.go | 2 +- generated/storage/taints.pb.go | 2 +- generated/storage/telemetry.pb.go | 2 +- generated/storage/test.pb.go | 2 +- generated/storage/traits.pb.go | 2 +- generated/storage/user.pb.go | 2 +- generated/storage/version.pb.go | 2 +- generated/storage/virtual_machine.pb.go | 2 +- generated/storage/virtual_machine_component_v2.pb.go | 2 +- generated/storage/virtual_machine_cve_v2.pb.go | 2 +- generated/storage/virtual_machine_scan_v2.pb.go | 2 +- generated/storage/virtual_machine_v2.pb.go | 2 +- generated/storage/vuln_requests.pb.go | 2 +- generated/storage/vulnerability.pb.go | 2 +- generated/test/test.pb.go | 2 +- generated/tools/local-sensor/message.pb.go | 2 +- tools/proto/go.mod | 2 +- tools/proto/go.sum | 4 ++-- 226 files changed, 227 insertions(+), 227 deletions(-) diff --git a/generated/api/common/extended_rpc_status.pb.go b/generated/api/common/extended_rpc_status.pb.go index 2e4fd515fed3f..1b98d4df7d216 100644 --- a/generated/api/common/extended_rpc_status.pb.go +++ b/generated/api/common/extended_rpc_status.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/common/extended_rpc_status.proto diff --git a/generated/api/integrations/splunk_service.pb.go b/generated/api/integrations/splunk_service.pb.go index 78c283fc1e2fa..fb8e951bf08be 100644 --- a/generated/api/integrations/splunk_service.pb.go +++ b/generated/api/integrations/splunk_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/integrations/splunk_service.proto diff --git a/generated/api/v1/administration_events_service.pb.go b/generated/api/v1/administration_events_service.pb.go index 43eeec241c05d..d9a59d764f8de 100644 --- a/generated/api/v1/administration_events_service.pb.go +++ b/generated/api/v1/administration_events_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/administration_events_service.proto diff --git a/generated/api/v1/administration_usage_service.pb.go b/generated/api/v1/administration_usage_service.pb.go index 6e92409a7e7e3..8d53d6e5cd51f 100644 --- a/generated/api/v1/administration_usage_service.pb.go +++ b/generated/api/v1/administration_usage_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/administration_usage_service.proto diff --git a/generated/api/v1/alert_service.pb.go b/generated/api/v1/alert_service.pb.go index 3e23235d9c681..7c6cfa1d39954 100644 --- a/generated/api/v1/alert_service.pb.go +++ b/generated/api/v1/alert_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/alert_service.proto diff --git a/generated/api/v1/api_token_service.pb.go b/generated/api/v1/api_token_service.pb.go index bf9a3a7a27e33..fc0a1e08254a5 100644 --- a/generated/api/v1/api_token_service.pb.go +++ b/generated/api/v1/api_token_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/api_token_service.proto diff --git a/generated/api/v1/audit.pb.go b/generated/api/v1/audit.pb.go index d6061d0c60a33..21ac701c05122 100644 --- a/generated/api/v1/audit.pb.go +++ b/generated/api/v1/audit.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/audit.proto diff --git a/generated/api/v1/auth_service.pb.go b/generated/api/v1/auth_service.pb.go index a1a9293bb741a..f73e92a603e26 100644 --- a/generated/api/v1/auth_service.pb.go +++ b/generated/api/v1/auth_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/auth_service.proto diff --git a/generated/api/v1/authprovider_service.pb.go b/generated/api/v1/authprovider_service.pb.go index 6a330cdf80dfb..f8d8f077c2924 100644 --- a/generated/api/v1/authprovider_service.pb.go +++ b/generated/api/v1/authprovider_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/authprovider_service.proto diff --git a/generated/api/v1/backup_service.pb.go b/generated/api/v1/backup_service.pb.go index d34c9aa786c8c..7517816852110 100644 --- a/generated/api/v1/backup_service.pb.go +++ b/generated/api/v1/backup_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/backup_service.proto diff --git a/generated/api/v1/central_health_service.pb.go b/generated/api/v1/central_health_service.pb.go index f4c9e38293be9..bbdcdc6d51ef4 100644 --- a/generated/api/v1/central_health_service.pb.go +++ b/generated/api/v1/central_health_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/central_health_service.proto diff --git a/generated/api/v1/cloud_source_service.pb.go b/generated/api/v1/cloud_source_service.pb.go index e6835b0d88b25..dbbd311948e97 100644 --- a/generated/api/v1/cloud_source_service.pb.go +++ b/generated/api/v1/cloud_source_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/cloud_source_service.proto diff --git a/generated/api/v1/cluster_init_service.pb.go b/generated/api/v1/cluster_init_service.pb.go index 85a88915e3a19..c62740345a676 100644 --- a/generated/api/v1/cluster_init_service.pb.go +++ b/generated/api/v1/cluster_init_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/cluster_init_service.proto diff --git a/generated/api/v1/cluster_service.pb.go b/generated/api/v1/cluster_service.pb.go index 6426337fbb739..aa892aa0471b7 100644 --- a/generated/api/v1/cluster_service.pb.go +++ b/generated/api/v1/cluster_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/cluster_service.proto diff --git a/generated/api/v1/common.pb.go b/generated/api/v1/common.pb.go index 015f336224cb2..a459f0aed74a5 100644 --- a/generated/api/v1/common.pb.go +++ b/generated/api/v1/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/common.proto diff --git a/generated/api/v1/compliance_management_service.pb.go b/generated/api/v1/compliance_management_service.pb.go index 5216c51320609..88b806e2b0d08 100644 --- a/generated/api/v1/compliance_management_service.pb.go +++ b/generated/api/v1/compliance_management_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/compliance_management_service.proto diff --git a/generated/api/v1/compliance_service.pb.go b/generated/api/v1/compliance_service.pb.go index 0e79d14433e1e..312ec3a6e252c 100644 --- a/generated/api/v1/compliance_service.pb.go +++ b/generated/api/v1/compliance_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/compliance_service.proto diff --git a/generated/api/v1/config_service.pb.go b/generated/api/v1/config_service.pb.go index 40837fbde2163..babbb3f1f7cd8 100644 --- a/generated/api/v1/config_service.pb.go +++ b/generated/api/v1/config_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/config_service.proto diff --git a/generated/api/v1/credential_expiry_service.pb.go b/generated/api/v1/credential_expiry_service.pb.go index a5f7347929bf2..caaf77268effa 100644 --- a/generated/api/v1/credential_expiry_service.pb.go +++ b/generated/api/v1/credential_expiry_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/credential_expiry_service.proto diff --git a/generated/api/v1/cve_service.pb.go b/generated/api/v1/cve_service.pb.go index c7793cfc68520..353f0bb81dd31 100644 --- a/generated/api/v1/cve_service.pb.go +++ b/generated/api/v1/cve_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/cve_service.proto diff --git a/generated/api/v1/db_service.pb.go b/generated/api/v1/db_service.pb.go index 30c74ac743f8c..1c38c412fd736 100644 --- a/generated/api/v1/db_service.pb.go +++ b/generated/api/v1/db_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/db_service.proto diff --git a/generated/api/v1/debug_service.pb.go b/generated/api/v1/debug_service.pb.go index aae29f1d51dd2..6e4848efff030 100644 --- a/generated/api/v1/debug_service.pb.go +++ b/generated/api/v1/debug_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/debug_service.proto diff --git a/generated/api/v1/declarative_config_health_service.pb.go b/generated/api/v1/declarative_config_health_service.pb.go index 6059cda2d6cb0..b5d64e94f95cf 100644 --- a/generated/api/v1/declarative_config_health_service.pb.go +++ b/generated/api/v1/declarative_config_health_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/declarative_config_health_service.proto diff --git a/generated/api/v1/delegated_registry_config_service.pb.go b/generated/api/v1/delegated_registry_config_service.pb.go index 86984982c6e58..1c02c02d58982 100644 --- a/generated/api/v1/delegated_registry_config_service.pb.go +++ b/generated/api/v1/delegated_registry_config_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/delegated_registry_config_service.proto diff --git a/generated/api/v1/deployment_service.pb.go b/generated/api/v1/deployment_service.pb.go index 2003993071140..ef061a27ec403 100644 --- a/generated/api/v1/deployment_service.pb.go +++ b/generated/api/v1/deployment_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/deployment_service.proto diff --git a/generated/api/v1/detection_service.pb.go b/generated/api/v1/detection_service.pb.go index c38b91ddd1363..d031244214079 100644 --- a/generated/api/v1/detection_service.pb.go +++ b/generated/api/v1/detection_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/detection_service.proto diff --git a/generated/api/v1/discovered_cluster_service.pb.go b/generated/api/v1/discovered_cluster_service.pb.go index d67e410e47c81..d90390f43f837 100644 --- a/generated/api/v1/discovered_cluster_service.pb.go +++ b/generated/api/v1/discovered_cluster_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/discovered_cluster_service.proto diff --git a/generated/api/v1/empty.pb.go b/generated/api/v1/empty.pb.go index 29b2451dc443b..58f3dd273b55d 100644 --- a/generated/api/v1/empty.pb.go +++ b/generated/api/v1/empty.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/empty.proto diff --git a/generated/api/v1/feature_flag_service.pb.go b/generated/api/v1/feature_flag_service.pb.go index 71964de453878..7054015e5656b 100644 --- a/generated/api/v1/feature_flag_service.pb.go +++ b/generated/api/v1/feature_flag_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/feature_flag_service.proto diff --git a/generated/api/v1/group_service.pb.go b/generated/api/v1/group_service.pb.go index 79eee42cb6a1a..3a10dba133c19 100644 --- a/generated/api/v1/group_service.pb.go +++ b/generated/api/v1/group_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/group_service.proto diff --git a/generated/api/v1/grpc_preference_service.pb.go b/generated/api/v1/grpc_preference_service.pb.go index 8e57e5ea24a42..4fa4723aa6ba1 100644 --- a/generated/api/v1/grpc_preference_service.pb.go +++ b/generated/api/v1/grpc_preference_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/grpc_preference_service.proto diff --git a/generated/api/v1/image_integration_service.pb.go b/generated/api/v1/image_integration_service.pb.go index 9d464ac7f4274..990fcb98237f5 100644 --- a/generated/api/v1/image_integration_service.pb.go +++ b/generated/api/v1/image_integration_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/image_integration_service.proto diff --git a/generated/api/v1/image_service.pb.go b/generated/api/v1/image_service.pb.go index 5175ee8c5578b..debf248ab5af0 100644 --- a/generated/api/v1/image_service.pb.go +++ b/generated/api/v1/image_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/image_service.proto diff --git a/generated/api/v1/integration_health_service.pb.go b/generated/api/v1/integration_health_service.pb.go index d0bcf266f4728..f30cbde79c49f 100644 --- a/generated/api/v1/integration_health_service.pb.go +++ b/generated/api/v1/integration_health_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/integration_health_service.proto diff --git a/generated/api/v1/metadata_service.pb.go b/generated/api/v1/metadata_service.pb.go index a0c8c7b88991c..c472242c142a9 100644 --- a/generated/api/v1/metadata_service.pb.go +++ b/generated/api/v1/metadata_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/metadata_service.proto diff --git a/generated/api/v1/mitre_service.pb.go b/generated/api/v1/mitre_service.pb.go index d16ed7fb3822d..d1b698411d4b4 100644 --- a/generated/api/v1/mitre_service.pb.go +++ b/generated/api/v1/mitre_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/mitre_service.proto diff --git a/generated/api/v1/namespace_service.pb.go b/generated/api/v1/namespace_service.pb.go index 9987f3a8e3b8d..479f1d2b05855 100644 --- a/generated/api/v1/namespace_service.pb.go +++ b/generated/api/v1/namespace_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/namespace_service.proto diff --git a/generated/api/v1/network_baseline_service.pb.go b/generated/api/v1/network_baseline_service.pb.go index f7898440bda13..63cba2bb08abf 100644 --- a/generated/api/v1/network_baseline_service.pb.go +++ b/generated/api/v1/network_baseline_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/network_baseline_service.proto diff --git a/generated/api/v1/network_graph_service.pb.go b/generated/api/v1/network_graph_service.pb.go index 561bb0d436488..14675ce92dad9 100644 --- a/generated/api/v1/network_graph_service.pb.go +++ b/generated/api/v1/network_graph_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/network_graph_service.proto diff --git a/generated/api/v1/network_policy_service.pb.go b/generated/api/v1/network_policy_service.pb.go index 134a03b6f236e..1bb454e9459f5 100644 --- a/generated/api/v1/network_policy_service.pb.go +++ b/generated/api/v1/network_policy_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/network_policy_service.proto diff --git a/generated/api/v1/node_service.pb.go b/generated/api/v1/node_service.pb.go index c4d46819af44a..d4e6d727974aa 100644 --- a/generated/api/v1/node_service.pb.go +++ b/generated/api/v1/node_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/node_service.proto diff --git a/generated/api/v1/notifications.pb.go b/generated/api/v1/notifications.pb.go index d1e211b7ca879..3793361470541 100644 --- a/generated/api/v1/notifications.pb.go +++ b/generated/api/v1/notifications.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/notifications.proto diff --git a/generated/api/v1/notifier_service.pb.go b/generated/api/v1/notifier_service.pb.go index 17f3804e00d72..c49f9e1b2ab43 100644 --- a/generated/api/v1/notifier_service.pb.go +++ b/generated/api/v1/notifier_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/notifier_service.proto diff --git a/generated/api/v1/pagination.pb.go b/generated/api/v1/pagination.pb.go index 67fcced57bd45..673948679eab8 100644 --- a/generated/api/v1/pagination.pb.go +++ b/generated/api/v1/pagination.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/pagination.proto diff --git a/generated/api/v1/ping_service.pb.go b/generated/api/v1/ping_service.pb.go index bff82ac06521c..a82b6d3e58ea7 100644 --- a/generated/api/v1/ping_service.pb.go +++ b/generated/api/v1/ping_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/ping_service.proto diff --git a/generated/api/v1/pod_service.pb.go b/generated/api/v1/pod_service.pb.go index 4908e85d853a3..3e7d8429e9531 100644 --- a/generated/api/v1/pod_service.pb.go +++ b/generated/api/v1/pod_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/pod_service.proto diff --git a/generated/api/v1/policy_category_service.pb.go b/generated/api/v1/policy_category_service.pb.go index 3243cb76f0ce5..a02160b93036c 100644 --- a/generated/api/v1/policy_category_service.pb.go +++ b/generated/api/v1/policy_category_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/policy_category_service.proto diff --git a/generated/api/v1/policy_service.pb.go b/generated/api/v1/policy_service.pb.go index d650c2722cc4f..503c54a5edf39 100644 --- a/generated/api/v1/policy_service.pb.go +++ b/generated/api/v1/policy_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/policy_service.proto diff --git a/generated/api/v1/probe_upload_service.pb.go b/generated/api/v1/probe_upload_service.pb.go index 46a14a15bc233..a38038150ccba 100644 --- a/generated/api/v1/probe_upload_service.pb.go +++ b/generated/api/v1/probe_upload_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/probe_upload_service.proto diff --git a/generated/api/v1/process_baseline_service.pb.go b/generated/api/v1/process_baseline_service.pb.go index e078981337d5a..40c8f1b92aa6c 100644 --- a/generated/api/v1/process_baseline_service.pb.go +++ b/generated/api/v1/process_baseline_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/process_baseline_service.proto diff --git a/generated/api/v1/process_listening_on_port_service.pb.go b/generated/api/v1/process_listening_on_port_service.pb.go index b028c17d133b8..b7773df360138 100644 --- a/generated/api/v1/process_listening_on_port_service.pb.go +++ b/generated/api/v1/process_listening_on_port_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/process_listening_on_port_service.proto diff --git a/generated/api/v1/process_service.pb.go b/generated/api/v1/process_service.pb.go index 031690f9996fd..7dbd8e40ae252 100644 --- a/generated/api/v1/process_service.pb.go +++ b/generated/api/v1/process_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/process_service.proto diff --git a/generated/api/v1/rbac_service.pb.go b/generated/api/v1/rbac_service.pb.go index 742ade005a9fe..09bc326a11fe2 100644 --- a/generated/api/v1/rbac_service.pb.go +++ b/generated/api/v1/rbac_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/rbac_service.proto diff --git a/generated/api/v1/resource_collection_service.pb.go b/generated/api/v1/resource_collection_service.pb.go index 0b71cbc3194f2..eb6720e5d5234 100644 --- a/generated/api/v1/resource_collection_service.pb.go +++ b/generated/api/v1/resource_collection_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/resource_collection_service.proto diff --git a/generated/api/v1/role_service.pb.go b/generated/api/v1/role_service.pb.go index cfe89975452c7..236017eb36dd0 100644 --- a/generated/api/v1/role_service.pb.go +++ b/generated/api/v1/role_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/role_service.proto diff --git a/generated/api/v1/sbom.pb.go b/generated/api/v1/sbom.pb.go index 5857f099a3d19..e5a28eab3710e 100644 --- a/generated/api/v1/sbom.pb.go +++ b/generated/api/v1/sbom.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/sbom.proto diff --git a/generated/api/v1/search_service.pb.go b/generated/api/v1/search_service.pb.go index 1a7e19fa32741..9b50e8b779b00 100644 --- a/generated/api/v1/search_service.pb.go +++ b/generated/api/v1/search_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/search_service.proto diff --git a/generated/api/v1/secret_service.pb.go b/generated/api/v1/secret_service.pb.go index c88e62cb63f80..9c480651181a1 100644 --- a/generated/api/v1/secret_service.pb.go +++ b/generated/api/v1/secret_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/secret_service.proto diff --git a/generated/api/v1/sensor_upgrade_service.pb.go b/generated/api/v1/sensor_upgrade_service.pb.go index 15d26cfdf661f..690ee71561cf4 100644 --- a/generated/api/v1/sensor_upgrade_service.pb.go +++ b/generated/api/v1/sensor_upgrade_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/sensor_upgrade_service.proto diff --git a/generated/api/v1/service_account_service.pb.go b/generated/api/v1/service_account_service.pb.go index b76622395e63a..5e1c0556f42e8 100644 --- a/generated/api/v1/service_account_service.pb.go +++ b/generated/api/v1/service_account_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/service_account_service.proto diff --git a/generated/api/v1/service_identity_service.pb.go b/generated/api/v1/service_identity_service.pb.go index abb0f106b4b7f..a7a8d1a0af870 100644 --- a/generated/api/v1/service_identity_service.pb.go +++ b/generated/api/v1/service_identity_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/service_identity_service.proto diff --git a/generated/api/v1/signal.pb.go b/generated/api/v1/signal.pb.go index c54ddaf697f5a..059076c5abc56 100644 --- a/generated/api/v1/signal.pb.go +++ b/generated/api/v1/signal.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/signal.proto diff --git a/generated/api/v1/signature_integration_service.pb.go b/generated/api/v1/signature_integration_service.pb.go index fbed8847a24d2..ccff377da6b66 100644 --- a/generated/api/v1/signature_integration_service.pb.go +++ b/generated/api/v1/signature_integration_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/signature_integration_service.proto diff --git a/generated/api/v1/telemetry_service.pb.go b/generated/api/v1/telemetry_service.pb.go index 4923cccc45643..b7961504d70ce 100644 --- a/generated/api/v1/telemetry_service.pb.go +++ b/generated/api/v1/telemetry_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/telemetry_service.proto diff --git a/generated/api/v1/traits.pb.go b/generated/api/v1/traits.pb.go index 413295858e8b4..2fbf9683cedbf 100644 --- a/generated/api/v1/traits.pb.go +++ b/generated/api/v1/traits.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/traits.proto diff --git a/generated/api/v1/user_service.pb.go b/generated/api/v1/user_service.pb.go index 5db9fbf6de584..2ab345487c4d1 100644 --- a/generated/api/v1/user_service.pb.go +++ b/generated/api/v1/user_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/user_service.proto diff --git a/generated/api/v1/vuln_mgmt_service.pb.go b/generated/api/v1/vuln_mgmt_service.pb.go index 09a84733debfc..7b48309d83bc4 100644 --- a/generated/api/v1/vuln_mgmt_service.pb.go +++ b/generated/api/v1/vuln_mgmt_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v1/vuln_mgmt_service.proto diff --git a/generated/api/v2/base_image_service.pb.go b/generated/api/v2/base_image_service.pb.go index e14ce1c99dfb9..0ee3a84f97fe0 100644 --- a/generated/api/v2/base_image_service.pb.go +++ b/generated/api/v2/base_image_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/base_image_service.proto diff --git a/generated/api/v2/common.pb.go b/generated/api/v2/common.pb.go index 46c6af89b3828..05518776c4403 100644 --- a/generated/api/v2/common.pb.go +++ b/generated/api/v2/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/common.proto diff --git a/generated/api/v2/compliance_common.pb.go b/generated/api/v2/compliance_common.pb.go index ba7c41a32adee..d92f5c5cbc5d6 100644 --- a/generated/api/v2/compliance_common.pb.go +++ b/generated/api/v2/compliance_common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_common.proto diff --git a/generated/api/v2/compliance_integration_service.pb.go b/generated/api/v2/compliance_integration_service.pb.go index 769817cd4bcae..7a9ea6cae2642 100644 --- a/generated/api/v2/compliance_integration_service.pb.go +++ b/generated/api/v2/compliance_integration_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_integration_service.proto diff --git a/generated/api/v2/compliance_profile_service.pb.go b/generated/api/v2/compliance_profile_service.pb.go index f16414001848c..7336af6368561 100644 --- a/generated/api/v2/compliance_profile_service.pb.go +++ b/generated/api/v2/compliance_profile_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_profile_service.proto diff --git a/generated/api/v2/compliance_results_service.pb.go b/generated/api/v2/compliance_results_service.pb.go index d3960013e7c7e..65cd85ab10ffe 100644 --- a/generated/api/v2/compliance_results_service.pb.go +++ b/generated/api/v2/compliance_results_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_results_service.proto diff --git a/generated/api/v2/compliance_results_stats_service.pb.go b/generated/api/v2/compliance_results_stats_service.pb.go index b2858c0c473c0..aad9bbc9b9a98 100644 --- a/generated/api/v2/compliance_results_stats_service.pb.go +++ b/generated/api/v2/compliance_results_stats_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_results_stats_service.proto diff --git a/generated/api/v2/compliance_rule_service.pb.go b/generated/api/v2/compliance_rule_service.pb.go index 018f0515d91f3..f962e1500e898 100644 --- a/generated/api/v2/compliance_rule_service.pb.go +++ b/generated/api/v2/compliance_rule_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_rule_service.proto diff --git a/generated/api/v2/compliance_scan_configuration_service.pb.go b/generated/api/v2/compliance_scan_configuration_service.pb.go index c5e288dec6d33..bdb65f6518c61 100644 --- a/generated/api/v2/compliance_scan_configuration_service.pb.go +++ b/generated/api/v2/compliance_scan_configuration_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/compliance_scan_configuration_service.proto diff --git a/generated/api/v2/node_report_service.pb.go b/generated/api/v2/node_report_service.pb.go index 47da5df35034e..2c30b83c3ad23 100644 --- a/generated/api/v2/node_report_service.pb.go +++ b/generated/api/v2/node_report_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/node_report_service.proto diff --git a/generated/api/v2/pagination.pb.go b/generated/api/v2/pagination.pb.go index 872cc707cd73a..4caf365cac11a 100644 --- a/generated/api/v2/pagination.pb.go +++ b/generated/api/v2/pagination.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/pagination.proto diff --git a/generated/api/v2/report_service.pb.go b/generated/api/v2/report_service.pb.go index f33e6d1df9466..a0e3d59f5b211 100644 --- a/generated/api/v2/report_service.pb.go +++ b/generated/api/v2/report_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/report_service.proto diff --git a/generated/api/v2/scan_component.pb.go b/generated/api/v2/scan_component.pb.go index a28631c792fe7..1c34fc2650d30 100644 --- a/generated/api/v2/scan_component.pb.go +++ b/generated/api/v2/scan_component.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/scan_component.proto diff --git a/generated/api/v2/search_query.pb.go b/generated/api/v2/search_query.pb.go index bf921ff60f374..af455db782c5b 100644 --- a/generated/api/v2/search_query.pb.go +++ b/generated/api/v2/search_query.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/search_query.proto diff --git a/generated/api/v2/user.pb.go b/generated/api/v2/user.pb.go index 00506829287a8..b594a8bec8d52 100644 --- a/generated/api/v2/user.pb.go +++ b/generated/api/v2/user.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/user.proto diff --git a/generated/api/v2/virtual_machine_service.pb.go b/generated/api/v2/virtual_machine_service.pb.go index 3fb3ad8e65eac..2b63475d9a674 100644 --- a/generated/api/v2/virtual_machine_service.pb.go +++ b/generated/api/v2/virtual_machine_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/virtual_machine_service.proto diff --git a/generated/api/v2/virtual_machine_v2_service.pb.go b/generated/api/v2/virtual_machine_v2_service.pb.go index 61bd7b26f1f40..f57b4bf3b44e3 100644 --- a/generated/api/v2/virtual_machine_v2_service.pb.go +++ b/generated/api/v2/virtual_machine_v2_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/virtual_machine_v2_service.proto diff --git a/generated/api/v2/vuln_exception_service.pb.go b/generated/api/v2/vuln_exception_service.pb.go index 4fc43b0fba1fa..e7bc42d269992 100644 --- a/generated/api/v2/vuln_exception_service.pb.go +++ b/generated/api/v2/vuln_exception_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/vuln_exception_service.proto diff --git a/generated/api/v2/vuln_state.pb.go b/generated/api/v2/vuln_state.pb.go index c37d616e94f1b..6edbc792a07a8 100644 --- a/generated/api/v2/vuln_state.pb.go +++ b/generated/api/v2/vuln_state.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/vuln_state.proto diff --git a/generated/api/v2/vulnerability.pb.go b/generated/api/v2/vulnerability.pb.go index 18099224a6193..0da91357ddea9 100644 --- a/generated/api/v2/vulnerability.pb.go +++ b/generated/api/v2/vulnerability.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: api/v2/vulnerability.proto diff --git a/generated/internalapi/central/auth.pb.go b/generated/internalapi/central/auth.pb.go index 98310971c11fa..0dbfada5ec090 100644 --- a/generated/internalapi/central/auth.pb.go +++ b/generated/internalapi/central/auth.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/auth.proto diff --git a/generated/internalapi/central/baseline_sync.pb.go b/generated/internalapi/central/baseline_sync.pb.go index f4d7faf9c78a6..03f6b169863ae 100644 --- a/generated/internalapi/central/baseline_sync.pb.go +++ b/generated/internalapi/central/baseline_sync.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/baseline_sync.proto diff --git a/generated/internalapi/central/cluster_config.pb.go b/generated/internalapi/central/cluster_config.pb.go index d9340b258415d..a0edcdf059287 100644 --- a/generated/internalapi/central/cluster_config.pb.go +++ b/generated/internalapi/central/cluster_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/cluster_config.proto diff --git a/generated/internalapi/central/cluster_metrics.pb.go b/generated/internalapi/central/cluster_metrics.pb.go index db9f16eb351f9..cdf3289ee21d9 100644 --- a/generated/internalapi/central/cluster_metrics.pb.go +++ b/generated/internalapi/central/cluster_metrics.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/cluster_metrics.proto diff --git a/generated/internalapi/central/cluster_status.pb.go b/generated/internalapi/central/cluster_status.pb.go index 469bf3766797d..b33a449c6d802 100644 --- a/generated/internalapi/central/cluster_status.pb.go +++ b/generated/internalapi/central/cluster_status.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/cluster_status.proto diff --git a/generated/internalapi/central/compliance_operator.pb.go b/generated/internalapi/central/compliance_operator.pb.go index ded411391134d..49c13bbf8686c 100644 --- a/generated/internalapi/central/compliance_operator.pb.go +++ b/generated/internalapi/central/compliance_operator.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/compliance_operator.proto diff --git a/generated/internalapi/central/delegated_registry_config.pb.go b/generated/internalapi/central/delegated_registry_config.pb.go index 2b52fcd7c6deb..6fcdf4cb2f17a 100644 --- a/generated/internalapi/central/delegated_registry_config.pb.go +++ b/generated/internalapi/central/delegated_registry_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/delegated_registry_config.proto diff --git a/generated/internalapi/central/deployment_enhancement.pb.go b/generated/internalapi/central/deployment_enhancement.pb.go index e8efe5675e331..82700235ecd76 100644 --- a/generated/internalapi/central/deployment_enhancement.pb.go +++ b/generated/internalapi/central/deployment_enhancement.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/deployment_enhancement.proto diff --git a/generated/internalapi/central/development_service.pb.go b/generated/internalapi/central/development_service.pb.go index d5beefb4c85e6..80058f8093a05 100644 --- a/generated/internalapi/central/development_service.pb.go +++ b/generated/internalapi/central/development_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/development_service.proto diff --git a/generated/internalapi/central/hello.pb.go b/generated/internalapi/central/hello.pb.go index 874368738bfee..5720311cf4bdd 100644 --- a/generated/internalapi/central/hello.pb.go +++ b/generated/internalapi/central/hello.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/hello.proto diff --git a/generated/internalapi/central/image.pb.go b/generated/internalapi/central/image.pb.go index 25305a6e5585c..85e1a447b3475 100644 --- a/generated/internalapi/central/image.pb.go +++ b/generated/internalapi/central/image.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/image.proto diff --git a/generated/internalapi/central/local_scanner.pb.go b/generated/internalapi/central/local_scanner.pb.go index 803964ec1fbbd..2209659e9768b 100644 --- a/generated/internalapi/central/local_scanner.pb.go +++ b/generated/internalapi/central/local_scanner.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/local_scanner.proto diff --git a/generated/internalapi/central/network_baseline_sync.pb.go b/generated/internalapi/central/network_baseline_sync.pb.go index aec1571dd98d8..d2a1090fdcbc8 100644 --- a/generated/internalapi/central/network_baseline_sync.pb.go +++ b/generated/internalapi/central/network_baseline_sync.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/network_baseline_sync.proto diff --git a/generated/internalapi/central/network_flow.pb.go b/generated/internalapi/central/network_flow.pb.go index fb9f918627c6f..ea6da89962369 100644 --- a/generated/internalapi/central/network_flow.pb.go +++ b/generated/internalapi/central/network_flow.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/network_flow.proto diff --git a/generated/internalapi/central/policy_sync.pb.go b/generated/internalapi/central/policy_sync.pb.go index 532b9c8020cff..8477cde92b692 100644 --- a/generated/internalapi/central/policy_sync.pb.go +++ b/generated/internalapi/central/policy_sync.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/policy_sync.proto diff --git a/generated/internalapi/central/process_listening_on_ports_update.pb.go b/generated/internalapi/central/process_listening_on_ports_update.pb.go index 87aabe9a70a6e..0adb573d5fc14 100644 --- a/generated/internalapi/central/process_listening_on_ports_update.pb.go +++ b/generated/internalapi/central/process_listening_on_ports_update.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/process_listening_on_ports_update.proto diff --git a/generated/internalapi/central/secured_cluster_cert_refresh.pb.go b/generated/internalapi/central/secured_cluster_cert_refresh.pb.go index d29951663abb5..2a8aa24d86315 100644 --- a/generated/internalapi/central/secured_cluster_cert_refresh.pb.go +++ b/generated/internalapi/central/secured_cluster_cert_refresh.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/secured_cluster_cert_refresh.proto diff --git a/generated/internalapi/central/sensor_events.pb.go b/generated/internalapi/central/sensor_events.pb.go index 3a567cb1fb43e..32c8efbcc3d04 100644 --- a/generated/internalapi/central/sensor_events.pb.go +++ b/generated/internalapi/central/sensor_events.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/sensor_events.proto diff --git a/generated/internalapi/central/sensor_iservice.pb.go b/generated/internalapi/central/sensor_iservice.pb.go index 61ca5245e77b4..835caba41697b 100644 --- a/generated/internalapi/central/sensor_iservice.pb.go +++ b/generated/internalapi/central/sensor_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/sensor_iservice.proto diff --git a/generated/internalapi/central/sensor_upgrade.pb.go b/generated/internalapi/central/sensor_upgrade.pb.go index 0217c7996b8ce..03e18f908e9c5 100644 --- a/generated/internalapi/central/sensor_upgrade.pb.go +++ b/generated/internalapi/central/sensor_upgrade.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/sensor_upgrade.proto diff --git a/generated/internalapi/central/sensor_upgrade_ctrl_iservice.pb.go b/generated/internalapi/central/sensor_upgrade_ctrl_iservice.pb.go index 11ed075adf498..cf8e6b1027d39 100644 --- a/generated/internalapi/central/sensor_upgrade_ctrl_iservice.pb.go +++ b/generated/internalapi/central/sensor_upgrade_ctrl_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/sensor_upgrade_ctrl_iservice.proto diff --git a/generated/internalapi/central/telemetry.pb.go b/generated/internalapi/central/telemetry.pb.go index fff1ae98a6a00..a21c999c5b8c1 100644 --- a/generated/internalapi/central/telemetry.pb.go +++ b/generated/internalapi/central/telemetry.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/telemetry.proto diff --git a/generated/internalapi/central/v1/token_service.pb.go b/generated/internalapi/central/v1/token_service.pb.go index 44d13cacd94c5..e8045d252b33b 100644 --- a/generated/internalapi/central/v1/token_service.pb.go +++ b/generated/internalapi/central/v1/token_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/central/v1/token_service.proto diff --git a/generated/internalapi/compliance/compliance_data.pb.go b/generated/internalapi/compliance/compliance_data.pb.go index f83af5017b2c2..e9eef707766e8 100644 --- a/generated/internalapi/compliance/compliance_data.pb.go +++ b/generated/internalapi/compliance/compliance_data.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/compliance/compliance_data.proto diff --git a/generated/internalapi/scanner/v4/common.pb.go b/generated/internalapi/scanner/v4/common.pb.go index 0e880bdfa519d..67299462678e8 100644 --- a/generated/internalapi/scanner/v4/common.pb.go +++ b/generated/internalapi/scanner/v4/common.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/scanner/v4/common.proto diff --git a/generated/internalapi/scanner/v4/index_report.pb.go b/generated/internalapi/scanner/v4/index_report.pb.go index 8ef70bab7f8ec..ef57844231a47 100644 --- a/generated/internalapi/scanner/v4/index_report.pb.go +++ b/generated/internalapi/scanner/v4/index_report.pb.go @@ -3,7 +3,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/scanner/v4/index_report.proto diff --git a/generated/internalapi/scanner/v4/indexer_service.pb.go b/generated/internalapi/scanner/v4/indexer_service.pb.go index 5febdad9a7344..aea3b37eed023 100644 --- a/generated/internalapi/scanner/v4/indexer_service.pb.go +++ b/generated/internalapi/scanner/v4/indexer_service.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/scanner/v4/indexer_service.proto diff --git a/generated/internalapi/scanner/v4/matcher_service.pb.go b/generated/internalapi/scanner/v4/matcher_service.pb.go index 93368d7f63fa2..c3b614afc7077 100644 --- a/generated/internalapi/scanner/v4/matcher_service.pb.go +++ b/generated/internalapi/scanner/v4/matcher_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/scanner/v4/matcher_service.proto diff --git a/generated/internalapi/scanner/v4/vulnerability_report.pb.go b/generated/internalapi/scanner/v4/vulnerability_report.pb.go index 3bd16abc49155..ffab5a0dbef8a 100644 --- a/generated/internalapi/scanner/v4/vulnerability_report.pb.go +++ b/generated/internalapi/scanner/v4/vulnerability_report.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/scanner/v4/vulnerability_report.proto diff --git a/generated/internalapi/sensor/admission_control.pb.go b/generated/internalapi/sensor/admission_control.pb.go index c43f3c244cb34..c1adff451079a 100644 --- a/generated/internalapi/sensor/admission_control.pb.go +++ b/generated/internalapi/sensor/admission_control.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/admission_control.proto diff --git a/generated/internalapi/sensor/admission_control_iservice.pb.go b/generated/internalapi/sensor/admission_control_iservice.pb.go index e01e909a96d3e..e9cb91ee552f6 100644 --- a/generated/internalapi/sensor/admission_control_iservice.pb.go +++ b/generated/internalapi/sensor/admission_control_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/admission_control_iservice.proto diff --git a/generated/internalapi/sensor/collector.pb.go b/generated/internalapi/sensor/collector.pb.go index 2a95c1d7facd3..f9ef7f7c936e3 100644 --- a/generated/internalapi/sensor/collector.pb.go +++ b/generated/internalapi/sensor/collector.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/collector.proto diff --git a/generated/internalapi/sensor/collector_iservice.pb.go b/generated/internalapi/sensor/collector_iservice.pb.go index 96efa3e0321b9..9d48eda3a6234 100644 --- a/generated/internalapi/sensor/collector_iservice.pb.go +++ b/generated/internalapi/sensor/collector_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/collector_iservice.proto diff --git a/generated/internalapi/sensor/compliance_iservice.pb.go b/generated/internalapi/sensor/compliance_iservice.pb.go index 041c11c982e9e..fb1533932bca2 100644 --- a/generated/internalapi/sensor/compliance_iservice.pb.go +++ b/generated/internalapi/sensor/compliance_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/compliance_iservice.proto diff --git a/generated/internalapi/sensor/deployment_iservice.pb.go b/generated/internalapi/sensor/deployment_iservice.pb.go index 82e9efadd2409..23963030dfb0e 100644 --- a/generated/internalapi/sensor/deployment_iservice.pb.go +++ b/generated/internalapi/sensor/deployment_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/deployment_iservice.proto diff --git a/generated/internalapi/sensor/fact.pb.go b/generated/internalapi/sensor/fact.pb.go index 1c5fac5e74654..11d3811fbace7 100644 --- a/generated/internalapi/sensor/fact.pb.go +++ b/generated/internalapi/sensor/fact.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/fact.proto diff --git a/generated/internalapi/sensor/image_iservice.pb.go b/generated/internalapi/sensor/image_iservice.pb.go index 21c13768dfd81..1827aa49de2f9 100644 --- a/generated/internalapi/sensor/image_iservice.pb.go +++ b/generated/internalapi/sensor/image_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/image_iservice.proto diff --git a/generated/internalapi/sensor/network_connection_info.pb.go b/generated/internalapi/sensor/network_connection_info.pb.go index 1e4dea02732ec..80cb31835ed4b 100644 --- a/generated/internalapi/sensor/network_connection_info.pb.go +++ b/generated/internalapi/sensor/network_connection_info.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/network_connection_info.proto diff --git a/generated/internalapi/sensor/network_connection_iservice.pb.go b/generated/internalapi/sensor/network_connection_iservice.pb.go index 832a4e7718f1a..205af81b2f734 100644 --- a/generated/internalapi/sensor/network_connection_iservice.pb.go +++ b/generated/internalapi/sensor/network_connection_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/network_connection_iservice.proto diff --git a/generated/internalapi/sensor/network_enums.pb.go b/generated/internalapi/sensor/network_enums.pb.go index 745fdb6eca418..3e5af29aa0212 100644 --- a/generated/internalapi/sensor/network_enums.pb.go +++ b/generated/internalapi/sensor/network_enums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/network_enums.proto diff --git a/generated/internalapi/sensor/sfa.pb.go b/generated/internalapi/sensor/sfa.pb.go index 7e484946ff09a..5b692eb8ede3a 100644 --- a/generated/internalapi/sensor/sfa.pb.go +++ b/generated/internalapi/sensor/sfa.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/sfa.proto diff --git a/generated/internalapi/sensor/sfa_iservice.pb.go b/generated/internalapi/sensor/sfa_iservice.pb.go index acbc88d078122..a0fe9781bdf8d 100644 --- a/generated/internalapi/sensor/sfa_iservice.pb.go +++ b/generated/internalapi/sensor/sfa_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/sfa_iservice.proto diff --git a/generated/internalapi/sensor/signal_iservice.pb.go b/generated/internalapi/sensor/signal_iservice.pb.go index f719986c05bdf..a60cecc1da7e1 100644 --- a/generated/internalapi/sensor/signal_iservice.pb.go +++ b/generated/internalapi/sensor/signal_iservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/sensor/signal_iservice.proto diff --git a/generated/internalapi/virtualmachine/v1/index_report.pb.go b/generated/internalapi/virtualmachine/v1/index_report.pb.go index 61269d35286d4..f82fea0df92c2 100644 --- a/generated/internalapi/virtualmachine/v1/index_report.pb.go +++ b/generated/internalapi/virtualmachine/v1/index_report.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/virtualmachine/v1/index_report.proto diff --git a/generated/internalapi/virtualmachine/v1/virtual_machine.pb.go b/generated/internalapi/virtualmachine/v1/virtual_machine.pb.go index 5921d62ac5a53..a68ca4d0d3d13 100644 --- a/generated/internalapi/virtualmachine/v1/virtual_machine.pb.go +++ b/generated/internalapi/virtualmachine/v1/virtual_machine.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/virtualmachine/v1/virtual_machine.proto diff --git a/generated/internalapi/virtualmachine/v1/vm_service.pb.go b/generated/internalapi/virtualmachine/v1/vm_service.pb.go index 0944d64b27b20..e209febc395c0 100644 --- a/generated/internalapi/virtualmachine/v1/vm_service.pb.go +++ b/generated/internalapi/virtualmachine/v1/vm_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/virtualmachine/v1/vm_service.proto diff --git a/generated/internalapi/wrapper/splunk_alert.pb.go b/generated/internalapi/wrapper/splunk_alert.pb.go index 5f337f671b73b..e9059584398c6 100644 --- a/generated/internalapi/wrapper/splunk_alert.pb.go +++ b/generated/internalapi/wrapper/splunk_alert.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: internalapi/wrapper/splunk_alert.proto diff --git a/generated/storage/administration_event.pb.go b/generated/storage/administration_event.pb.go index df9d45e84de39..e9be271076741 100644 --- a/generated/storage/administration_event.pb.go +++ b/generated/storage/administration_event.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/administration_event.proto diff --git a/generated/storage/administration_usage.pb.go b/generated/storage/administration_usage.pb.go index 6dd7fe42016e1..5a47c7315b122 100644 --- a/generated/storage/administration_usage.pb.go +++ b/generated/storage/administration_usage.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/administration_usage.proto diff --git a/generated/storage/alert.pb.go b/generated/storage/alert.pb.go index 5efdec723b73f..0cb19c88a4b7b 100644 --- a/generated/storage/alert.pb.go +++ b/generated/storage/alert.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/alert.proto diff --git a/generated/storage/api_token.pb.go b/generated/storage/api_token.pb.go index 553d80929e232..ccf6a95ad4ece 100644 --- a/generated/storage/api_token.pb.go +++ b/generated/storage/api_token.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/api_token.proto diff --git a/generated/storage/auth_machine_to_machine.pb.go b/generated/storage/auth_machine_to_machine.pb.go index 75595ab528119..8dee2c425a520 100644 --- a/generated/storage/auth_machine_to_machine.pb.go +++ b/generated/storage/auth_machine_to_machine.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/auth_machine_to_machine.proto diff --git a/generated/storage/auth_provider.pb.go b/generated/storage/auth_provider.pb.go index 116d32ff70de4..6894934e299ac 100644 --- a/generated/storage/auth_provider.pb.go +++ b/generated/storage/auth_provider.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/auth_provider.proto diff --git a/generated/storage/base_image.pb.go b/generated/storage/base_image.pb.go index 2302244fc1d57..91e6f9bdd80b6 100644 --- a/generated/storage/base_image.pb.go +++ b/generated/storage/base_image.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/base_image.proto diff --git a/generated/storage/base_image_repository.pb.go b/generated/storage/base_image_repository.pb.go index 98275adf0ee64..71c3665241dbe 100644 --- a/generated/storage/base_image_repository.pb.go +++ b/generated/storage/base_image_repository.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/base_image_repository.proto diff --git a/generated/storage/blob.pb.go b/generated/storage/blob.pb.go index de4cb680fa39d..71f5d355b89d2 100644 --- a/generated/storage/blob.pb.go +++ b/generated/storage/blob.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/blob.proto diff --git a/generated/storage/cloud_source.pb.go b/generated/storage/cloud_source.pb.go index 645bc74b61d5a..512ac32e377cb 100644 --- a/generated/storage/cloud_source.pb.go +++ b/generated/storage/cloud_source.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/cloud_source.proto diff --git a/generated/storage/cluster.pb.go b/generated/storage/cluster.pb.go index b032df461b173..6b327ad571102 100644 --- a/generated/storage/cluster.pb.go +++ b/generated/storage/cluster.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/cluster.proto diff --git a/generated/storage/cluster_init.pb.go b/generated/storage/cluster_init.pb.go index e145a4a0f8d52..76809c3446204 100644 --- a/generated/storage/cluster_init.pb.go +++ b/generated/storage/cluster_init.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/cluster_init.proto diff --git a/generated/storage/common.pb.go b/generated/storage/common.pb.go index 0019e4dfbfa26..5de76e47c3144 100644 --- a/generated/storage/common.pb.go +++ b/generated/storage/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/common.proto diff --git a/generated/storage/compliance.pb.go b/generated/storage/compliance.pb.go index a50a75801c3f2..c1747d82f5353 100644 --- a/generated/storage/compliance.pb.go +++ b/generated/storage/compliance.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/compliance.proto diff --git a/generated/storage/compliance_config.pb.go b/generated/storage/compliance_config.pb.go index 2c2541f8c00bb..39f7b1941bf93 100644 --- a/generated/storage/compliance_config.pb.go +++ b/generated/storage/compliance_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/compliance_config.proto diff --git a/generated/storage/compliance_integration.pb.go b/generated/storage/compliance_integration.pb.go index 43ba8b37010b3..2ea6bb1a2d1cb 100644 --- a/generated/storage/compliance_integration.pb.go +++ b/generated/storage/compliance_integration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/compliance_integration.proto diff --git a/generated/storage/compliance_operator.pb.go b/generated/storage/compliance_operator.pb.go index 22e7bc33f22b9..09a4962f8d3c6 100644 --- a/generated/storage/compliance_operator.pb.go +++ b/generated/storage/compliance_operator.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/compliance_operator.proto diff --git a/generated/storage/compliance_operator_v2.pb.go b/generated/storage/compliance_operator_v2.pb.go index 5ad5bbeb070b2..a6d253464298f 100644 --- a/generated/storage/compliance_operator_v2.pb.go +++ b/generated/storage/compliance_operator_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/compliance_operator_v2.proto diff --git a/generated/storage/config.pb.go b/generated/storage/config.pb.go index d0e5a336d1bec..96c75b18bff25 100644 --- a/generated/storage/config.pb.go +++ b/generated/storage/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/config.proto diff --git a/generated/storage/container_runtime.pb.go b/generated/storage/container_runtime.pb.go index 5b0cbfc4c857b..2aff805dfac12 100644 --- a/generated/storage/container_runtime.pb.go +++ b/generated/storage/container_runtime.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/container_runtime.proto diff --git a/generated/storage/cve.pb.go b/generated/storage/cve.pb.go index a3d5625cef0b2..62dd566ae5313 100644 --- a/generated/storage/cve.pb.go +++ b/generated/storage/cve.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/cve.proto diff --git a/generated/storage/declarative_config_health.pb.go b/generated/storage/declarative_config_health.pb.go index 2774a543154a8..ab8ae734357da 100644 --- a/generated/storage/declarative_config_health.pb.go +++ b/generated/storage/declarative_config_health.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/declarative_config_health.proto diff --git a/generated/storage/delegated_registry_config.pb.go b/generated/storage/delegated_registry_config.pb.go index e95aa9ab08de4..0c37b53103134 100644 --- a/generated/storage/delegated_registry_config.pb.go +++ b/generated/storage/delegated_registry_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/delegated_registry_config.proto diff --git a/generated/storage/deployment.pb.go b/generated/storage/deployment.pb.go index 652446d8996e5..e239a6b485255 100644 --- a/generated/storage/deployment.pb.go +++ b/generated/storage/deployment.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/deployment.proto diff --git a/generated/storage/discovered_cluster.pb.go b/generated/storage/discovered_cluster.pb.go index 305e3e5415b3d..44af51e85fd49 100644 --- a/generated/storage/discovered_cluster.pb.go +++ b/generated/storage/discovered_cluster.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/discovered_cluster.proto diff --git a/generated/storage/external_backup.pb.go b/generated/storage/external_backup.pb.go index 139ce582c3ace..a3abba232dbe3 100644 --- a/generated/storage/external_backup.pb.go +++ b/generated/storage/external_backup.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/external_backup.proto diff --git a/generated/storage/file_access.pb.go b/generated/storage/file_access.pb.go index 4c1a916c401b9..af066b531e822 100644 --- a/generated/storage/file_access.pb.go +++ b/generated/storage/file_access.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/file_access.proto diff --git a/generated/storage/group.pb.go b/generated/storage/group.pb.go index 0a7256ef0ca46..6e8dfd698d8d5 100644 --- a/generated/storage/group.pb.go +++ b/generated/storage/group.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/group.proto diff --git a/generated/storage/hash.pb.go b/generated/storage/hash.pb.go index fafb3d2e6e183..af6d811abd811 100644 --- a/generated/storage/hash.pb.go +++ b/generated/storage/hash.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/hash.proto diff --git a/generated/storage/helm_cluster.pb.go b/generated/storage/helm_cluster.pb.go index 6a4925aa0b167..7097f25b31a47 100644 --- a/generated/storage/helm_cluster.pb.go +++ b/generated/storage/helm_cluster.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/helm_cluster.proto diff --git a/generated/storage/http_endpoint.pb.go b/generated/storage/http_endpoint.pb.go index 40c5fe6f9220b..a67789f8acf25 100644 --- a/generated/storage/http_endpoint.pb.go +++ b/generated/storage/http_endpoint.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/http_endpoint.proto diff --git a/generated/storage/image.pb.go b/generated/storage/image.pb.go index 9e7c6399be2c8..514f86d078138 100644 --- a/generated/storage/image.pb.go +++ b/generated/storage/image.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/image.proto diff --git a/generated/storage/image_component.pb.go b/generated/storage/image_component.pb.go index 6f19b3ae98933..f3de77c18fa76 100644 --- a/generated/storage/image_component.pb.go +++ b/generated/storage/image_component.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/image_component.proto diff --git a/generated/storage/image_integration.pb.go b/generated/storage/image_integration.pb.go index a07822bbcd53f..5bd60d6f5a3de 100644 --- a/generated/storage/image_integration.pb.go +++ b/generated/storage/image_integration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/image_integration.proto diff --git a/generated/storage/image_v2.pb.go b/generated/storage/image_v2.pb.go index 91c8fec274ec4..5ad060b592d96 100644 --- a/generated/storage/image_v2.pb.go +++ b/generated/storage/image_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/image_v2.proto diff --git a/generated/storage/installation.pb.go b/generated/storage/installation.pb.go index 9116f619063db..d4377c57509ca 100644 --- a/generated/storage/installation.pb.go +++ b/generated/storage/installation.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/installation.proto diff --git a/generated/storage/integration_health.pb.go b/generated/storage/integration_health.pb.go index 1db17d2ebdc65..19730f4b31677 100644 --- a/generated/storage/integration_health.pb.go +++ b/generated/storage/integration_health.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/integration_health.proto diff --git a/generated/storage/kube_event.pb.go b/generated/storage/kube_event.pb.go index f16d6361dd25a..dfb2371d0fefb 100644 --- a/generated/storage/kube_event.pb.go +++ b/generated/storage/kube_event.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/kube_event.proto diff --git a/generated/storage/labels.pb.go b/generated/storage/labels.pb.go index efef57b7c92a3..564be4ea5bea5 100644 --- a/generated/storage/labels.pb.go +++ b/generated/storage/labels.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/labels.proto diff --git a/generated/storage/log.pb.go b/generated/storage/log.pb.go index 8d97ea23b666a..ccc10571fb333 100644 --- a/generated/storage/log.pb.go +++ b/generated/storage/log.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/log.proto diff --git a/generated/storage/mitre.pb.go b/generated/storage/mitre.pb.go index ce55aad910033..eae305559454e 100644 --- a/generated/storage/mitre.pb.go +++ b/generated/storage/mitre.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/mitre.proto diff --git a/generated/storage/namespace_metadata.pb.go b/generated/storage/namespace_metadata.pb.go index d1f325690d21a..416992ff47e30 100644 --- a/generated/storage/namespace_metadata.pb.go +++ b/generated/storage/namespace_metadata.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/namespace_metadata.proto diff --git a/generated/storage/network_baseline.pb.go b/generated/storage/network_baseline.pb.go index 5eb542d00dac6..9aeff948794ed 100644 --- a/generated/storage/network_baseline.pb.go +++ b/generated/storage/network_baseline.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/network_baseline.proto diff --git a/generated/storage/network_flow.pb.go b/generated/storage/network_flow.pb.go index f414d43499582..8c1037154ceca 100644 --- a/generated/storage/network_flow.pb.go +++ b/generated/storage/network_flow.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/network_flow.proto diff --git a/generated/storage/network_graph_config.pb.go b/generated/storage/network_graph_config.pb.go index d4d827735d388..f997b69b2bec9 100644 --- a/generated/storage/network_graph_config.pb.go +++ b/generated/storage/network_graph_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/network_graph_config.proto diff --git a/generated/storage/network_policy.pb.go b/generated/storage/network_policy.pb.go index 7c54d9d2c5c07..5a024cf1fbcfc 100644 --- a/generated/storage/network_policy.pb.go +++ b/generated/storage/network_policy.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/network_policy.proto diff --git a/generated/storage/node.pb.go b/generated/storage/node.pb.go index cc9988e90a4f6..26f5199b75a28 100644 --- a/generated/storage/node.pb.go +++ b/generated/storage/node.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/node.proto diff --git a/generated/storage/node_component.pb.go b/generated/storage/node_component.pb.go index 11103bf02cf2e..e64871d9945e0 100644 --- a/generated/storage/node_component.pb.go +++ b/generated/storage/node_component.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/node_component.proto diff --git a/generated/storage/node_integration.pb.go b/generated/storage/node_integration.pb.go index 62f2462fc3288..705aba38b1421 100644 --- a/generated/storage/node_integration.pb.go +++ b/generated/storage/node_integration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/node_integration.proto diff --git a/generated/storage/notification_schedule.pb.go b/generated/storage/notification_schedule.pb.go index b2bbe7ce7762d..5fc49b03dcac7 100644 --- a/generated/storage/notification_schedule.pb.go +++ b/generated/storage/notification_schedule.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/notification_schedule.proto diff --git a/generated/storage/notifier.pb.go b/generated/storage/notifier.pb.go index 251d60ffc26b2..b18b3e0af803d 100644 --- a/generated/storage/notifier.pb.go +++ b/generated/storage/notifier.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/notifier.proto diff --git a/generated/storage/notifier_enc_config.pb.go b/generated/storage/notifier_enc_config.pb.go index 81a3a16a931f3..be1f06c80499c 100644 --- a/generated/storage/notifier_enc_config.pb.go +++ b/generated/storage/notifier_enc_config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/notifier_enc_config.proto diff --git a/generated/storage/operation_status.pb.go b/generated/storage/operation_status.pb.go index 52d2abff8fa90..3fd6f13c5ba2a 100644 --- a/generated/storage/operation_status.pb.go +++ b/generated/storage/operation_status.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/operation_status.proto diff --git a/generated/storage/orchestrator_integration.pb.go b/generated/storage/orchestrator_integration.pb.go index 11e713af2a7df..76d6ce66b1d1d 100644 --- a/generated/storage/orchestrator_integration.pb.go +++ b/generated/storage/orchestrator_integration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/orchestrator_integration.proto diff --git a/generated/storage/policy.pb.go b/generated/storage/policy.pb.go index c99de9332733b..3f2a48f2c108c 100644 --- a/generated/storage/policy.pb.go +++ b/generated/storage/policy.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/policy.proto diff --git a/generated/storage/policy_category.pb.go b/generated/storage/policy_category.pb.go index 1d9f7c240caee..9f251b985de73 100644 --- a/generated/storage/policy_category.pb.go +++ b/generated/storage/policy_category.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/policy_category.proto diff --git a/generated/storage/process_baseline.pb.go b/generated/storage/process_baseline.pb.go index 60b8012737c28..5514b838e8e50 100644 --- a/generated/storage/process_baseline.pb.go +++ b/generated/storage/process_baseline.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/process_baseline.proto diff --git a/generated/storage/process_indicator.pb.go b/generated/storage/process_indicator.pb.go index 8bb409310fb2b..42af97489fbef 100644 --- a/generated/storage/process_indicator.pb.go +++ b/generated/storage/process_indicator.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/process_indicator.proto diff --git a/generated/storage/process_listening_on_port.pb.go b/generated/storage/process_listening_on_port.pb.go index aed79161edf94..d6f7d2bf73396 100644 --- a/generated/storage/process_listening_on_port.pb.go +++ b/generated/storage/process_listening_on_port.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/process_listening_on_port.proto diff --git a/generated/storage/rbac.pb.go b/generated/storage/rbac.pb.go index c792bfb7cd9a2..dbde734a53ab8 100644 --- a/generated/storage/rbac.pb.go +++ b/generated/storage/rbac.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/rbac.proto diff --git a/generated/storage/relations.pb.go b/generated/storage/relations.pb.go index 9fa78603c26f5..1e51ad5cb2adc 100644 --- a/generated/storage/relations.pb.go +++ b/generated/storage/relations.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/relations.proto diff --git a/generated/storage/report_configuration.pb.go b/generated/storage/report_configuration.pb.go index 72ad19441b0a3..3f969710bdc18 100644 --- a/generated/storage/report_configuration.pb.go +++ b/generated/storage/report_configuration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/report_configuration.proto diff --git a/generated/storage/report_notifier_configuration.pb.go b/generated/storage/report_notifier_configuration.pb.go index 19dfa872e83af..7f687465c3762 100644 --- a/generated/storage/report_notifier_configuration.pb.go +++ b/generated/storage/report_notifier_configuration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/report_notifier_configuration.proto diff --git a/generated/storage/report_snapshot.pb.go b/generated/storage/report_snapshot.pb.go index 54acf8958dd53..5d0e879a64623 100644 --- a/generated/storage/report_snapshot.pb.go +++ b/generated/storage/report_snapshot.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/report_snapshot.proto diff --git a/generated/storage/resource_collection.pb.go b/generated/storage/resource_collection.pb.go index 70e708df4febe..9b42e2f8865ad 100644 --- a/generated/storage/resource_collection.pb.go +++ b/generated/storage/resource_collection.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/resource_collection.proto diff --git a/generated/storage/risk.pb.go b/generated/storage/risk.pb.go index 146a00fa04b64..27ad23016ba5a 100644 --- a/generated/storage/risk.pb.go +++ b/generated/storage/risk.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/risk.proto diff --git a/generated/storage/role.pb.go b/generated/storage/role.pb.go index 095b43889468b..b140ce12e3969 100644 --- a/generated/storage/role.pb.go +++ b/generated/storage/role.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/role.proto diff --git a/generated/storage/schedule.pb.go b/generated/storage/schedule.pb.go index c39adb9ca6945..696be25aea17e 100644 --- a/generated/storage/schedule.pb.go +++ b/generated/storage/schedule.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/schedule.proto diff --git a/generated/storage/scope.pb.go b/generated/storage/scope.pb.go index 1b16ac4a200e5..4c74ece92a4cb 100644 --- a/generated/storage/scope.pb.go +++ b/generated/storage/scope.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/scope.proto diff --git a/generated/storage/secret.pb.go b/generated/storage/secret.pb.go index 62340b587afc8..d793f8b30e3f9 100644 --- a/generated/storage/secret.pb.go +++ b/generated/storage/secret.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/secret.proto diff --git a/generated/storage/sensor_upgrade.pb.go b/generated/storage/sensor_upgrade.pb.go index ea91a650104d0..a755cd2a7b3da 100644 --- a/generated/storage/sensor_upgrade.pb.go +++ b/generated/storage/sensor_upgrade.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/sensor_upgrade.proto diff --git a/generated/storage/service_account.pb.go b/generated/storage/service_account.pb.go index 090ab6d6bc01c..9e1dffac42593 100644 --- a/generated/storage/service_account.pb.go +++ b/generated/storage/service_account.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/service_account.proto diff --git a/generated/storage/service_identity.pb.go b/generated/storage/service_identity.pb.go index 977969b3c14c1..c5b86d165673d 100644 --- a/generated/storage/service_identity.pb.go +++ b/generated/storage/service_identity.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/service_identity.proto diff --git a/generated/storage/signature_integration.pb.go b/generated/storage/signature_integration.pb.go index fb77716dd328c..ba5e6066215fc 100644 --- a/generated/storage/signature_integration.pb.go +++ b/generated/storage/signature_integration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/signature_integration.proto diff --git a/generated/storage/system_info.pb.go b/generated/storage/system_info.pb.go index 7610f92952411..9a785ad00ca21 100644 --- a/generated/storage/system_info.pb.go +++ b/generated/storage/system_info.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/system_info.proto diff --git a/generated/storage/taints.pb.go b/generated/storage/taints.pb.go index 70442763e613b..0d65db63da121 100644 --- a/generated/storage/taints.pb.go +++ b/generated/storage/taints.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/taints.proto diff --git a/generated/storage/telemetry.pb.go b/generated/storage/telemetry.pb.go index 92ee31589a4c4..f668c57b255ae 100644 --- a/generated/storage/telemetry.pb.go +++ b/generated/storage/telemetry.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/telemetry.proto diff --git a/generated/storage/test.pb.go b/generated/storage/test.pb.go index f7a42207b4636..4eb02802084ca 100644 --- a/generated/storage/test.pb.go +++ b/generated/storage/test.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/test.proto diff --git a/generated/storage/traits.pb.go b/generated/storage/traits.pb.go index 2b52cd5e6ca8c..95d2420db81f3 100644 --- a/generated/storage/traits.pb.go +++ b/generated/storage/traits.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/traits.proto diff --git a/generated/storage/user.pb.go b/generated/storage/user.pb.go index 8cf1a7c6ffe81..f014ea07d7778 100644 --- a/generated/storage/user.pb.go +++ b/generated/storage/user.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/user.proto diff --git a/generated/storage/version.pb.go b/generated/storage/version.pb.go index 07cbb4044198f..010bae61d2933 100644 --- a/generated/storage/version.pb.go +++ b/generated/storage/version.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/version.proto diff --git a/generated/storage/virtual_machine.pb.go b/generated/storage/virtual_machine.pb.go index 3e6238c18d3cc..5b11e46a5573b 100644 --- a/generated/storage/virtual_machine.pb.go +++ b/generated/storage/virtual_machine.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/virtual_machine.proto diff --git a/generated/storage/virtual_machine_component_v2.pb.go b/generated/storage/virtual_machine_component_v2.pb.go index 5c49311e552dc..cbbccf82b8dec 100644 --- a/generated/storage/virtual_machine_component_v2.pb.go +++ b/generated/storage/virtual_machine_component_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/virtual_machine_component_v2.proto diff --git a/generated/storage/virtual_machine_cve_v2.pb.go b/generated/storage/virtual_machine_cve_v2.pb.go index 97caffbf5325b..130097ac3e344 100644 --- a/generated/storage/virtual_machine_cve_v2.pb.go +++ b/generated/storage/virtual_machine_cve_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/virtual_machine_cve_v2.proto diff --git a/generated/storage/virtual_machine_scan_v2.pb.go b/generated/storage/virtual_machine_scan_v2.pb.go index 3f0adad8f9afb..cf2317cc60247 100644 --- a/generated/storage/virtual_machine_scan_v2.pb.go +++ b/generated/storage/virtual_machine_scan_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/virtual_machine_scan_v2.proto diff --git a/generated/storage/virtual_machine_v2.pb.go b/generated/storage/virtual_machine_v2.pb.go index c9e7e603482a2..c228772e3559d 100644 --- a/generated/storage/virtual_machine_v2.pb.go +++ b/generated/storage/virtual_machine_v2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/virtual_machine_v2.proto diff --git a/generated/storage/vuln_requests.pb.go b/generated/storage/vuln_requests.pb.go index 4c9821d23dd56..b17ccd4bdb986 100644 --- a/generated/storage/vuln_requests.pb.go +++ b/generated/storage/vuln_requests.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/vuln_requests.proto diff --git a/generated/storage/vulnerability.pb.go b/generated/storage/vulnerability.pb.go index 4d5bcd07dc214..95e619dea61a9 100644 --- a/generated/storage/vulnerability.pb.go +++ b/generated/storage/vulnerability.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: storage/vulnerability.proto diff --git a/generated/test/test.pb.go b/generated/test/test.pb.go index 5bc6afe472a3d..f17e92627291e 100644 --- a/generated/test/test.pb.go +++ b/generated/test/test.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: test/test.proto diff --git a/generated/tools/local-sensor/message.pb.go b/generated/tools/local-sensor/message.pb.go index fdd50698a2b55..a0b724624c413 100644 --- a/generated/tools/local-sensor/message.pb.go +++ b/generated/tools/local-sensor/message.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v6.32.1 // source: tools/local-sensor/message.proto diff --git a/tools/proto/go.mod b/tools/proto/go.mod index b86088a333a04..756534936b74e 100644 --- a/tools/proto/go.mod +++ b/tools/proto/go.mod @@ -6,7 +6,7 @@ require ( github.com/bufbuild/buf v1.72.0 github.com/favadi/protoc-go-inject-tag v1.4.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 - google.golang.org/protobuf v1.36.11 + google.golang.org/protobuf v1.36.12 ) require ( diff --git a/tools/proto/go.sum b/tools/proto/go.sum index d51ed71ab7c1d..bb43d3e61bf8b 100644 --- a/tools/proto/go.sum +++ b/tools/proto/go.sum @@ -217,8 +217,8 @@ 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= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 9c040721455443072470a8fbaaa6402a0e20a751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:39:37 +0200 Subject: [PATCH 397/638] chore(deps): bump github.com/mikefarah/yq/v4 from 4.53.3 to 4.53.6 in /operator/tools/yq (#22469) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/yq/go.mod | 23 +++++++++--------- operator/tools/yq/go.sum | 50 +++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/operator/tools/yq/go.mod b/operator/tools/yq/go.mod index 2e37eb540e772..73b47acec8e29 100644 --- a/operator/tools/yq/go.mod +++ b/operator/tools/yq/go.mod @@ -2,13 +2,14 @@ module github.com/stackrox/rox/operator/tools/yq go 1.26.3 -require github.com/mikefarah/yq/v4 v4.53.3 +require github.com/mikefarah/yq/v4 v4.53.6 require ( github.com/a8m/envsubst v1.4.3 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/alecthomas/participle/v2 v2.1.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/elliotchance/orderedmap v1.8.0 // indirect github.com/fatih/color v1.19.0 // indirect @@ -19,20 +20,20 @@ require ( github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect - github.com/magiconair/properties v1.8.10 // indirect + github.com/magiconair/properties v1.18.11 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/pelletier/go-toml/v2 v2.3.1 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/yuin/gopher-lua v1.1.2 // indirect - github.com/zclconf/go-cty v1.18.1 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/tools v0.44.0 // indirect + github.com/zclconf/go-cty v1.19.0 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect + golang.org/x/mod v0.40.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.49.0 // indirect ) diff --git a/operator/tools/yq/go.sum b/operator/tools/yq/go.sum index 1597a5ce1c948..6b0189e7efba9 100644 --- a/operator/tools/yq/go.sum +++ b/operator/tools/yq/go.sum @@ -6,10 +6,12 @@ github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8v github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U= github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI= -github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= -github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/repr v0.5.4 h1:OVP7JEcuzU9CCDsT6STCr3rg17oQfWILtPWd2EG0uN4= +github.com/alecthomas/repr v0.5.4/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/apparentlymart/go-textseg/v17 v17.0.1 h1:bpMXRgQ5cEoRNuQke1a80/Nl6w3G5eoIbWo9f3gXkAs= +github.com/apparentlymart/go-textseg/v17 v17.0.1/go.mod h1:fa8X4jgGeevslICIY6LcdjkSecWnXmYd9Lk34z/VxZs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -38,18 +40,18 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0= +github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mikefarah/yq/v4 v4.53.3 h1:S0kwPkINdOjgPskW5CfYTGo/Vt7c3gUnUkKSp7CNqD0= -github.com/mikefarah/yq/v4 v4.53.3/go.mod h1:4mvgujMtSAB3lA27+lwg8eIud/4QUQw2/1rLbxgiauw= +github.com/mikefarah/yq/v4 v4.53.6 h1:5vB15LTZRvrF6z/metQF31bmF63SjDzCKKX8o8UemQQ= +github.com/mikefarah/yq/v4 v4.53.6/go.mod h1:3tmDcCSTOTUFa5Z1X8R4j0H1kauIwNo2wjKq9sLyAh4= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= -github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -65,26 +67,26 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA= github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8= -github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= -github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= +github.com/zclconf/go-cty v1.19.0 h1:IV8WdqYZc2c5rLX9bEoLNXKojBAp0MZPBHMIrCoa/s4= +github.com/zclconf/go-cty v1.19.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= -go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3b211d8e61f0e0572c661908ac81413c756dbc91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:49:46 +0200 Subject: [PATCH 398/638] chore(deps): bump google.golang.org/api from 0.292.0 to 0.298.0 (#22772) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index a0edf0391dc83..8d1cc4b6f294d 100644 --- a/go.mod +++ b/go.mod @@ -155,7 +155,7 @@ require ( golang.org/x/time v0.16.0 golang.org/x/tools v0.50.0 golang.stackrox.io/grpc-http1 v0.5.1 - google.golang.org/api v0.292.0 + google.golang.org/api v0.298.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d google.golang.org/grpc v1.83.2 @@ -189,7 +189,7 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect cel.dev/expr v0.25.2 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.22.0 // indirect + cloud.google.com/go/auth v0.23.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/iam v1.13.0 // indirect cloud.google.com/go/longrunning v1.2.0 // indirect @@ -318,7 +318,7 @@ require ( github.com/google/go-querystring v1.2.0 // indirect github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect @@ -458,7 +458,7 @@ require ( go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 13a8e545e60dd..510ca0b198da8 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/artifactregistry v1.26.0 h1:iq5kkdY2FJY8RkkNghUelb3EOcqyy7dVhfDGVk/Xw9g= cloud.google.com/go/artifactregistry v1.26.0/go.mod h1:c5FPi5GtDBP+OAr5kKhCBNQDT9ZgAyobXQjekx93VWs= -cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ= -cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s= +cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo= +cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -735,8 +735,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/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.19 h1:mMOE7DN2+p76/EdIrmAy9B9bH+yC4563vmnJ34QR8i4= -github.com/googleapis/enterprise-certificate-proxy v0.3.19/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k= +github.com/googleapis/enterprise-certificate-proxy v0.3.20 h1:t/xL64VUoN69MuMRQuJETqYGOw4Z9mSRJK9epIEtwFk= +github.com/googleapis/enterprise-certificate-proxy v0.3.20/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1961,8 +1961,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.292.0 h1:Ewiwo/GTtiaPZSNAZQUcWLh8AYDEoPmIXyJfeoTSMHU= -google.golang.org/api v0.292.0/go.mod h1:07kjmMnFGm2RQuCza2EZM/5N68G/fVvFb1xKjWqoFA0= +google.golang.org/api v0.298.0 h1:YW18RkHBMZBA1ergX0m4biagzgbiPTb2uTsRsDPWNRY= +google.golang.org/api v0.298.0/go.mod h1:02qB8+Ox1ZFzcaKFMguy1nQLJmSIyvV6Ff4txJEXtl4= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2038,8 +2038,8 @@ google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAf google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= From 5f44dc4e54f6f87777a5c6b5138a2c76e756aa4c Mon Sep 17 00:00:00 2001 From: David Vail Date: Thu, 24 Sep 2026 09:30:24 -0400 Subject: [PATCH 399/638] ROX-37121: Loosen selectors for OCP plugin version selection (#23015) --- ui/apps/platform/cypress/integration-ocp/smoke.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/apps/platform/cypress/integration-ocp/smoke.test.ts b/ui/apps/platform/cypress/integration-ocp/smoke.test.ts index 13cfd9b1a9cbb..ff6b5fa915c08 100644 --- a/ui/apps/platform/cypress/integration-ocp/smoke.test.ts +++ b/ui/apps/platform/cypress/integration-ocp/smoke.test.ts @@ -32,12 +32,10 @@ describe('Basic tests of the OCP plugin', () => { it('should display plugin information in cluster settings', () => { visitConsoleDynamicPluginsStatusPage(); - cy.get('td[data-label="name"]:contains("advanced-cluster-security")') + cy.get('td:has(a:contains("advanced-cluster-security"))') .parent() .within(() => { - cy.get('td[data-label="version"]') - .invoke('text') - .should('match', /^[1-9]\d*\.\d+\.\d+/); + cy.contains('td', /^[1-9]\d*\.\d+\.\d+/); }); }); }); From 88a74e8f8e3b85ad96d01916fe9c5cd2c8cf365b Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Thu, 24 Sep 2026 15:41:31 +0200 Subject: [PATCH 400/638] ROX-35277: add MERGE_WINDOW file (#22786) --- scripts/release-tools/MERGE_WINDOW | 1 + scripts/release-tools/README.md | 33 +++++++++++++----------------- 2 files changed, 15 insertions(+), 19 deletions(-) create mode 100644 scripts/release-tools/MERGE_WINDOW diff --git a/scripts/release-tools/MERGE_WINDOW b/scripts/release-tools/MERGE_WINDOW new file mode 100644 index 0000000000000..10e31f59d7a41 --- /dev/null +++ b/scripts/release-tools/MERGE_WINDOW @@ -0,0 +1 @@ +OPEN diff --git a/scripts/release-tools/README.md b/scripts/release-tools/README.md index 615f6821a4c5e..6ef54f10a1a85 100644 --- a/scripts/release-tools/README.md +++ b/scripts/release-tools/README.md @@ -1,28 +1,23 @@ -# Upstream Release Tools +# Release Helpers and Configuration -This directory contains a selection of scripts that automate selected mundane manual steps (a.k.a toil) in the upstream release process. +This directory contains a selection of helper scripts and configuration for the StackRox release process. -:warning: The scripts are bleeding-edge - there is no guarantee that they would work for you the way they work for me. Test coverage is currently not provided.:warning: +## Contents -## Meta +### Long-running cluster -This document describes the intention behind the scripts in this folder. -We acknowledge that different developers may run some parts of the release differently, use different tools, and prefer different programming languages. -Some parts could have been definitely done better than they currently are, however the balance of time, effort, and usability at the time of the release resulted exactly in this state. -Thus, it is fully okay to: +For the purposes of configuring workloads on the long-running cluster (created as part of the release process), there are directories containing configuration: -- use these scripts partially or not at all, -- replace them with something better, -- add functionalities, bugfixes, or tests. +- `kube-burner-configs` +- `long-running-cluster` -## Prerequisites +### Cluster access -The scripts described here require the following env variables to exist and have correct values: +To access long-running and upgrade clusters created during the release process, there are two scripts and a library of shared functions. -```shell -export RELEASE=3.69 -export RC_NUMBER=1 +### Merge Window -export INFRA_TOKEN="xxx" # generate under https://infra.stackrox.com/ -export JIRA_TOKEN="yyy" # generate under https://issues.redhat.com/secure/ViewProfile.jspa?selectedTab=com.atlassian.pats.pats-plugin:jira-user-personal-access-tokens -``` +When on a release branch, the `MERGE_WINDOW` file describes whether or not changes are allowed to be merged to the respective release branch. +It contains either "OPEN" or "CLOSED". +These states are set through release automation. +Follow the documented process in case you need to bypass a closed merge window. From 4010c82856f9a8ff9e97717af5afea8eabd71028 Mon Sep 17 00:00:00 2001 From: David Shrewsberry <99685630+dashrews78@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:50:33 -0400 Subject: [PATCH 401/638] ROX-36978: remove potentially aggressive node settings (#22936) --- deploy/common/k8sbased.sh | 18 ++++++++++-------- .../src/test/groovy/NodeIndexTest.groovy | 8 +------- tests/e2e/lib-compat.sh | 4 ++++ tests/e2e/lib.sh | 14 ++++++++------ 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index ca845f3558327..c8bd48861efb1 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -944,20 +944,18 @@ function launch_sensor { extra_helm_config+=(--set "virtualMachines.enabled=false") fi - # Shorten node-scan cadence for e2e (production: 5m initial, 4h interval). - # Matcher-not-ready drops the first index as unretryable; a short interval - # covers the next scan without restarting collector. - helm_args+=( - --set customize.envVars.ROX_NODE_SCANNING_MAX_INITIAL_WAIT=1s - --set customize.envVars.ROX_NODE_SCANNING_INTERVAL=30s - ) - if [[ -n "$LOGLEVEL" ]]; then helm_args+=( --set customize.envVars.LOGLEVEL="${LOGLEVEL}" ) fi + # Scan every 9-11 minutes with Helm deployments. + helm_args+=( + --set customize.envVars.ROX_NODE_SCANNING_INTERVAL=10m + --set customize.envVars.ROX_NODE_SCANNING_INTERVAL_DEVIATION=60s + ) + if [[ -n "${ROX_NETFLOW_BATCHING:-}" ]]; then helm_args+=( --set customize.envVars.ROX_NETFLOW_BATCHING="${ROX_NETFLOW_BATCHING}" @@ -1103,6 +1101,10 @@ function launch_sensor { if [[ "${#sensor_env[@]}" -gt 0 ]]; then kubectl -n "${sensor_namespace}" set env deploy/sensor "${sensor_env[@]}" fi + + # Scan every 9-11 minutes with manifest deployments. + kubectl -n "${sensor_namespace}" set env ds/collector --containers=compliance \ + ROX_NODE_SCANNING_INTERVAL=10m ROX_NODE_SCANNING_INTERVAL_DEVIATION=60s fi collector_env=() diff --git a/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy b/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy index 6c101aa005ca7..68963fb8e9be0 100644 --- a/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy +++ b/qa-tests-backend/src/test/groovy/NodeIndexTest.groovy @@ -31,8 +31,6 @@ class NodeIndexTest extends BaseSpecification { given: "Scanner V4 is enabled and the cluster has nodes" Assume.assumeTrue("Scanner V4 node indexing is required", scannerV4Enabled) - List nodes = NodeService.getNodes() - assert nodes.size() > 0 when: "scanner-v4-matcher is ready so index reports can be enriched" @@ -40,14 +38,10 @@ class NodeIndexTest extends BaseSpecification { waitForTrue(20, 6) { orchestrator.deploymentReady(Constants.STACKROX_NAMESPACE, "scanner-v4-matcher") } - // Matcher-not-ready drops the first index report as unretryable. CI deploy - // sets ROX_NODE_SCANNING_MAX_INITIAL_WAIT=1s and ROX_NODE_SCANNING_INTERVAL=30s - // so a later scan lands after matcher is up without restarting collector. - then: "each OpenShift node scan has RPM packages, not only kubelet/kernel/runtime" withRetry(12, 30) { - nodes = NodeService.getNodes() + List nodes = NodeService.getNodes() assert nodes.size() > 0, "Expected to find at least one node" nodes.each { node -> assert node.getScan(), "Expected to find a nodeScan on the node" diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index 2ff61f96818ec..f48b7e4dfecc8 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -153,6 +153,10 @@ roxie_config_from_environment_compat() { env_with_default ROX_NETFLOW_BATCHING "true" # pkg/env/sensor.go. env_with_default ROX_NETFLOW_CACHE_LIMITING "true" # pkg/env/sensor.go. + # Scan every 9-11 minutes in roxie-deployed QA tests, including GHA. + env_with_default ROX_NODE_SCANNING_INTERVAL "10m" + env_with_default ROX_NODE_SCANNING_INTERVAL_DEVIATION "60s" + collect_feature_flags ) diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index dc0a4c2a6e377..545859fefc55b 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -297,6 +297,10 @@ securedCluster: clusterName: remote EOF + # Scan every 9-11 minutes during compatibility tests. + set_custom_env "$config_file" "securedCluster" "ROX_NODE_SCANNING_INTERVAL" "10m" + set_custom_env "$config_file" "securedCluster" "ROX_NODE_SCANNING_INTERVAL_DEVIATION" "60s" + # Expose plaintext endpoints required by endpoints_test.go. set_custom_env "$config_file" "central" "ROX_PLAINTEXT_ENDPOINTS" "8080,grpc@8081" @@ -749,13 +753,11 @@ deploy_sensor_via_operator() { fi customize_envVars="" - # Shorten node-scan cadence for e2e (production: 5m initial, 4h interval). - # Matcher-not-ready drops the first index as unretryable; a short interval - # covers the next scan without restarting collector. - customize_envVars+=$'\n - name: ROX_NODE_SCANNING_MAX_INITIAL_WAIT' - customize_envVars+=$'\n value: "1s"' + # Scan every 9-11 minutes during operator-deployed e2e tests. customize_envVars+=$'\n - name: ROX_NODE_SCANNING_INTERVAL' - customize_envVars+=$'\n value: "30s"' + customize_envVars+=$'\n value: "10m"' + customize_envVars+=$'\n - name: ROX_NODE_SCANNING_INTERVAL_DEVIATION' + customize_envVars+=$'\n value: "60s"' if [[ -n "${ROX_NETFLOW_BATCHING:-}" ]]; then customize_envVars+=$'\n - name: ROX_NETFLOW_BATCHING' customize_envVars+=$'\n value: "'"${ROX_NETFLOW_BATCHING}"'"' From f49ce99de990f3e425177609b731a10f6a2cba44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:21:23 +0200 Subject: [PATCH 402/638] chore(deps): bump github.com/aws/smithy-go from 1.28.1 to 1.28.2 (#22526) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8d1cc4b6f294d..aacefbf59faf9 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1 github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 - github.com/aws/smithy-go v1.28.1 + github.com/aws/smithy-go v1.28.2 github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 diff --git a/go.sum b/go.sum index 510ca0b198da8..245ebecfd2d3f 100644 --- a/go.sum +++ b/go.sum @@ -255,8 +255,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 h1:obhahQXDEdVEv8y5bTKXR30L github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0/go.mod h1:6twZZ/aXHNy1vXUO8koUbp++MYzMASkOgEBdkbJYmO0= github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 h1:Zpnqa6XtrNzXZnwbdCqHOXpXhMsa01ql/pcRQ1sb4hk= github.com/aws/aws-sdk-go-v2/service/sts v1.51.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM= -github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= -github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.28.2 h1:myhcykQcatTul2B/zITjDk203G7t0awUAs1hVry5Bvg= +github.com/aws/smithy-go v1.28.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA= From 7c49bc57558c43723bae34223c6c79b21d44e962 Mon Sep 17 00:00:00 2001 From: Brad Rogers <61400697+bradr5@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:18:02 -0500 Subject: [PATCH 403/638] ROX-37188: Remove Node Reports feature flag in UI (#23012) --- .../reports/reportsOverviewNav.test.ts | 6 +--- .../Overview/NodeCvesOverviewPage.tsx | 36 +++++++++---------- ui/apps/platform/src/routePaths.ts | 1 - ui/apps/platform/src/types/featureFlag.ts | 1 - 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts index 68e360a6b7555..49ef7cc346922 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/reports/reportsOverviewNav.test.ts @@ -1,8 +1,5 @@ import withAuth from '../../../helpers/basicAuth'; -import { - interceptAndOverrideFeatureFlags, - interceptAndOverridePermissions, -} from '../../../helpers/request'; +import { interceptAndOverridePermissions } from '../../../helpers/request'; import { visit } from '../../../helpers/visit'; import pf6 from '../../../selectors/pf6'; @@ -24,7 +21,6 @@ describe('Vulnerability Reports Overview Navigation', () => { it('navigates to the node vulnerability reports page', () => { interceptAndOverridePermissions({ Cluster: 'READ_ACCESS', Node: 'READ_ACCESS' }); - interceptAndOverrideFeatureFlags({ ROX_NODE_VULNERABILITY_REPORTS: true }); visit(vulnerabilityReportsOverviewPath); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx index 7a58cf3a96787..4aae855f98bcc 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/NodeCves/Overview/NodeCvesOverviewPage.tsx @@ -159,16 +159,14 @@ function NodeCvesOverviewPage() { trackAppliedFilter(NODE_CVE_FILTER_APPLIED, searchPayload); }} > - {isFeatureFlagEnabled('ROX_NODE_VULNERABILITY_REPORTS') && ( - { - setIsCreateViewBasedReportModalOpen(true); - }} - onSelectCreateScheduledReport={() => { - navigate(createScheduledReportForNodeVulnerabilitiesURL(querySearchFilter)); - }} - /> - )} + { + setIsCreateViewBasedReportModalOpen(true); + }} + onSelectCreateScheduledReport={() => { + navigate(createScheduledReportForNodeVulnerabilitiesURL(querySearchFilter)); + }} + /> ); @@ -301,16 +299,14 @@ function NodeCvesOverviewPage() { /> )} - {isFeatureFlagEnabled('ROX_NODE_VULNERABILITY_REPORTS') && ( - - )} + ); } diff --git a/ui/apps/platform/src/routePaths.ts b/ui/apps/platform/src/routePaths.ts index 33b931c842619..af58e7a11e86f 100644 --- a/ui/apps/platform/src/routePaths.ts +++ b/ui/apps/platform/src/routePaths.ts @@ -387,7 +387,6 @@ const routeRequirementsMap: Record = { resourceAccessRequirements: everyResource(['Deployment', 'Image']), }, 'vulnerabilities/reports/nodes': { - featureFlagRequirements: allEnabled(['ROX_NODE_VULNERABILITY_REPORTS']), resourceAccessRequirements: everyResource(['Cluster', 'Node']), }, 'vulnerabilities/user-workloads': { diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index c427d4e9101dd..4e9e1d3845522 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -12,7 +12,6 @@ export type FeatureFlagEnvVar = | 'ROX_LEGACY_SCANNER' | 'ROX_LIGHTSPEED_RISK_SUMMARY' | 'ROX_NODE_INDEX_ENABLED' - | 'ROX_NODE_VULNERABILITY_REPORTS' | 'ROX_POLICY_CRITERIA_MODAL' | 'ROX_SCANNER_V4' | 'ROX_SENSITIVE_FILE_ACTIVITY' From 92b9d3903d6bf71479241b4f93df7befcfad0b2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 21:37:08 +0200 Subject: [PATCH 404/638] chore(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.2 in /tools/test (#22577) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/test/go.mod | 46 ++++++------ tools/test/go.sum | 187 +++++++++++++--------------------------------- 2 files changed, 73 insertions(+), 160 deletions(-) diff --git a/tools/test/go.mod b/tools/test/go.mod index 507306861087c..4eeed19adcd11 100644 --- a/tools/test/go.mod +++ b/tools/test/go.mod @@ -9,19 +9,19 @@ require ( ) require ( - cloud.google.com/go/auth v0.5.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/auth v0.18.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/monitoring v1.19.0 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -31,24 +31,24 @@ require ( github.com/prometheus/prom2json v1.3.3 // indirect github.com/spf13/cobra v1.10.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - golang.org/x/crypto v0.52.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/crypto v0.55.0 // indirect golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.183.0 // indirect - google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/grpc v1.82.1 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/api v0.264.0 // indirect + google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/tools/test/go.sum b/tools/test/go.sum index f7ff091e5b80e..b84af8b3dd2ab 100644 --- a/tools/test/go.sum +++ b/tools/test/go.sum @@ -1,73 +1,44 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= -cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= +cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/monitoring v1.19.0 h1:NCXf8hfQi+Kmr56QJezXRZ6GPb80ZI7El1XztyUuLQI= -cloud.google.com/go/monitoring v1.19.0/go.mod h1:25IeMR5cQ5BoZ8j1eogHE5VPJLlReQ7zFp5OiLgiGZw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc= @@ -78,10 +49,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= @@ -98,111 +67,55 @@ github.com/stackrox/image-prefetcher/deploy v0.5.2 h1:b8VP/i6xD46aAxMHq9nLgyq4vM github.com/stackrox/image-prefetcher/deploy v0.5.2/go.mod h1:bAhl/GuKJnAchWpAYkEaoqfaJHqCkbBKhLDWHm1GXLs= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5 h1:gAg8XyBlWRq8Ia4uTPmhNUnVYtiDN4eiqviJ+tD87xw= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5/go.mod h1:y4d4bwvOygvByIxM/g0XCzBKMryqXilCk4Lj+mMBsVM= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= -google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= -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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3 h1:8RTI1cmuvdY9J7q/jpJWEj5UfgWjhV5MCoXaYmwLBYQ= -google.golang.org/genproto v0.0.0-20240610135401-a8a62080eff3/go.mod h1:qb66gsewNb7Ghv1enkhJiRfYGWUklv3n6G8UvprOhzA= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -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= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/api v0.264.0 h1:+Fo3DQXBK8gLdf8rFZ3uLu39JpOnhvzJrLMQSoSYZJM= +google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +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.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 6aeeb020bc24be504791734b856a5b9c99fc845a Mon Sep 17 00:00:00 2001 From: AJ Heflin <77823405+ajheflin@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:51:59 -0400 Subject: [PATCH 405/638] fix(ui): clarify KEV policy criterion labels (#22864) Co-authored-by: Van Wilson --- .../Wizard/Step3/policyCriteriaDescriptors.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx index 1cc6724e8141c..3443e3bb474eb 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx +++ b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx @@ -684,19 +684,19 @@ export const policyCriteriaDescriptors: Descriptor[] = [ lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'], }, { - label: 'CISA KEV', + label: 'Known Exploited CVE', name: 'CISA KEV', - shortName: 'CISA KEV', - longName: 'CISA Known Exploited Vulnerabilities catalog', + shortName: 'Known exploited CVE', + longName: 'Known exploited vulnerabilities from the CISA KEV catalog', category: policyCriteriaCategories.IMAGE_SCANNING, type: 'radioGroup', radioButtons: [ { - text: 'CVE is in CISA KEV catalog (known exploited)', + text: 'Yes', value: true, }, { - text: 'CVE is not in CISA KEV catalog', + text: 'No', value: false, }, ], From 9d8c61a3e1f448b7fc745439b5b02c18f0dc5e4b Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:15:11 -0700 Subject: [PATCH 406/638] ROX-37158: add workload type exclusion flag and capability (#22960) --- pkg/centralsensor/caps_list.go | 5 +++++ pkg/features/list.go | 3 +++ sensor/common/detector/detector.go | 6 +++++- tests/e2e/lib-compat.sh | 1 + tests/e2e/lib.sh | 7 +++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/centralsensor/caps_list.go b/pkg/centralsensor/caps_list.go index 6a90ecbaa50b8..2cbfbdf727d5a 100644 --- a/pkg/centralsensor/caps_list.go +++ b/pkg/centralsensor/caps_list.go @@ -109,4 +109,9 @@ const ( // VirtualMachineTelemetryCap lets Central distinguish "VM scanning off on // a new Sensor" from "old Sensor that predates VM telemetry fields." VirtualMachineTelemetryCap SensorCapability = "VirtualMachineTelemetry" + + // WorkloadTypeExclusionCap identifies the capability of Sensor to honor + // Exclusion.exclude_by_type rules. Central strips those rules when syncing + // policies to Sensors that lack this capability. + WorkloadTypeExclusionCap SensorCapability = "WorkloadTypeExclusion" ) diff --git a/pkg/features/list.go b/pkg/features/list.go index dc632d82f305f..f650124fef654 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -146,6 +146,9 @@ var ( // EvaluationFilter enables evaluation filtering in policy evaluation. EvaluationFilter = registerFeature("Enable evaluation filtering in policy evaluation", "ROX_EVALUATION_FILTER", enabled) + // PolicyWorkloadTypeExclusion enables excluding all workloads of selected Kubernetes kinds (Job, CronJob) from a policy. + PolicyWorkloadTypeExclusion = registerFeature("Enable excluding all workloads of a given type from a policy", "ROX_POLICY_WORKLOAD_TYPE_EXCLUSION") + // UISecretsPageMigration enables the secrets list page under the Risk section UISecretsPageMigration = registerFeature("Display secrets list page under Risk section", "ROX_UI_SECRETS_PAGE_MIGRATION") diff --git a/sensor/common/detector/detector.go b/sensor/common/detector/detector.go index ef9b94103e2ef..91a329abf3c20 100644 --- a/sensor/common/detector/detector.go +++ b/sensor/common/detector/detector.go @@ -427,7 +427,11 @@ func (d *detectorImpl) Notify(e common.SensorComponentEvent) { } func (d *detectorImpl) Capabilities() []centralsensor.SensorCapability { - return []centralsensor.SensorCapability{centralsensor.SensorDetectionCap} + caps := []centralsensor.SensorCapability{centralsensor.SensorDetectionCap} + if features.PolicyWorkloadTypeExclusion.Enabled() { + caps = append(caps, centralsensor.WorkloadTypeExclusionCap) + } + return caps } // ProcessPolicySync reconciles policies and flush all deployments through the detector diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index f48b7e4dfecc8..85b686fcbfec2 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -205,6 +205,7 @@ collect_feature_flags() { env_with_default ROX_NODE_VULNERABILITY_REPORTS "true" env_with_default ROX_TAILORED_PROFILES "true" env_with_default ROX_INIT_CONTAINER_SUPPORT "true" + env_with_default ROX_POLICY_WORKLOAD_TYPE_EXCLUSION "true" env_with_default ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL "true" env_with_default ROX_LABEL_BASED_POLICY_SCOPING "true" env_with_default ROX_POLICY_CRITERIA_MODAL "true" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 545859fefc55b..3f074bec50f45 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -444,6 +444,7 @@ export_test_environment() { ci_export ROX_NETFLOW_BATCHING "${ROX_NETFLOW_BATCHING:-true}" ci_export ROX_NETFLOW_CACHE_LIMITING "${ROX_NETFLOW_CACHE_LIMITING:-true}" ci_export ROX_INIT_CONTAINER_SUPPORT "${ROX_INIT_CONTAINER_SUPPORT:-true}" + ci_export ROX_POLICY_WORKLOAD_TYPE_EXCLUSION "${ROX_POLICY_WORKLOAD_TYPE_EXCLUSION:-true}" ci_export ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL "${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}" ci_export ROX_UI_SECRETS_PAGE_MIGRATION "${ROX_UI_SECRETS_PAGE_MIGRATION:-true}" ci_export ROX_AI_INTEGRATIONS "${ROX_AI_INTEGRATIONS:-true}" @@ -624,6 +625,8 @@ deploy_central_via_operator() { customize_envVars+=$'\n value: "true"' customize_envVars+=$'\n - name: ROX_INIT_CONTAINER_SUPPORT' customize_envVars+=$'\n value: "true"' + customize_envVars+=$'\n - name: ROX_POLICY_WORKLOAD_TYPE_EXCLUSION' + customize_envVars+=$'\n value: "true"' customize_envVars+=$'\n - name: ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL' customize_envVars+=$'\n value: "'"${ROX_VIRTUAL_MACHINES_ENHANCED_DATA_MODEL:-true}"'"' customize_envVars+=$'\n - name: ROX_UI_SECRETS_PAGE_MIGRATION' @@ -775,6 +778,10 @@ deploy_sensor_via_operator() { customize_envVars+=$'\n - name: ROX_INIT_CONTAINER_SUPPORT' customize_envVars+=$'\n value: "'"${ROX_INIT_CONTAINER_SUPPORT}"'"' fi + if [[ -n "${ROX_POLICY_WORKLOAD_TYPE_EXCLUSION:-}" ]]; then + customize_envVars+=$'\n - name: ROX_POLICY_WORKLOAD_TYPE_EXCLUSION' + customize_envVars+=$'\n value: "'"${ROX_POLICY_WORKLOAD_TYPE_EXCLUSION}"'"' + fi local scannerV4DbPersistenceYaml scannerV4DbPersistenceYaml="$(_scanner_v4_db_persistence_yaml)" From 8330205f3acfcf513fddb72638795ea7bfa7254f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Valero=20Mart=C3=ADn?= Date: Fri, 25 Sep 2026 08:53:59 +0200 Subject: [PATCH 407/638] feat: add Central version header for roxctl skew detection (#22692) Co-authored-by: Claude --- CHANGELOG.md | 2 + central/main.go | 3 + pkg/clientconn/roxctl_command_header.go | 5 + pkg/grpc/versionheader/interceptor.go | 27 +++ .../centralgrpc/connect_to_central.go | 2 +- roxctl/common/connection.go | 40 ++- roxctl/common/environment/environment-impl.go | 1 + roxctl/common/versioncheck/check.go | 87 +++++++ roxctl/common/versioncheck/check_test.go | 227 ++++++++++++++++++ tests/metadata_test.go | 40 ++- 10 files changed, 420 insertions(+), 14 deletions(-) create mode 100644 pkg/grpc/versionheader/interceptor.go create mode 100644 roxctl/common/versioncheck/check.go create mode 100644 roxctl/common/versioncheck/check_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 698d94710d7da..8ec9b661ee677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Technical Changes +- ROX-37048: Central writes its version in `Rh-Central-Version` gRPC response header to authenticated clients. `roxctl` compares this version with its own version and reports a warning to stderr if the version skew is outside of a supported range. + ## [5.0.0] diff --git a/central/main.go b/central/main.go index c45ba2561361c..1a2d2a2c1d37a 100755 --- a/central/main.go +++ b/central/main.go @@ -218,6 +218,7 @@ import ( "github.com/stackrox/rox/pkg/grpc/errors" "github.com/stackrox/rox/pkg/grpc/ratelimit" "github.com/stackrox/rox/pkg/grpc/routes" + "github.com/stackrox/rox/pkg/grpc/versionheader" "github.com/stackrox/rox/pkg/httputil/proxy" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/memlimit" @@ -661,6 +662,8 @@ func startGRPCServer() { centralSAC.GetEnricher().GetPreAuthContextEnricher(authzTraceSink), ) + config.UnaryInterceptors = append(config.UnaryInterceptors, versionheader.CentralVersionServerInterceptor()) + // Telemetry client has to add interceptors before starting the server. c := phonehomeClient.Singleton() config.HTTPInterceptors = append(config.HTTPInterceptors, c.GetHTTPInterceptor()) diff --git a/pkg/clientconn/roxctl_command_header.go b/pkg/clientconn/roxctl_command_header.go index 80dd0bddb54d5..16750449bc5dc 100644 --- a/pkg/clientconn/roxctl_command_header.go +++ b/pkg/clientconn/roxctl_command_header.go @@ -12,3 +12,8 @@ const RoxctlCommandIndexHeader = "Rh-Roxctl-Command-Index" // ExecutionEnvironment is the HTTP header name with the custom execution // environment string. Can be supplied by roxctl, or any other API client. const ExecutionEnvironment = "Rh-Execution-Environment" + +// CentralVersionHeader is the gRPC response metadata key carrying the +// Central version. Set by Central for authenticated requests so that +// clients (e.g. roxctl) can detect version skew without an extra RPC. +const CentralVersionHeader = "Rh-Central-Version" diff --git a/pkg/grpc/versionheader/interceptor.go b/pkg/grpc/versionheader/interceptor.go new file mode 100644 index 0000000000000..98bc6b08bed29 --- /dev/null +++ b/pkg/grpc/versionheader/interceptor.go @@ -0,0 +1,27 @@ +package versionheader + +import ( + "context" + + "github.com/stackrox/rox/pkg/clientconn" + "github.com/stackrox/rox/pkg/grpc/authn" + "github.com/stackrox/rox/pkg/version" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// CentralVersionServerInterceptor returns a gRPC unary server interceptor that sets the +// Central version in the response header for authenticated requests. +func CentralVersionServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + setVersionHeader(ctx) + return handler(ctx, req) + } +} + +func setVersionHeader(ctx context.Context) { + if authn.IdentityFromContextOrNil(ctx) == nil { + return + } + _ = grpc.SetHeader(ctx, metadata.Pairs(clientconn.CentralVersionHeader, version.GetMainVersion())) +} diff --git a/pkg/testutils/centralgrpc/connect_to_central.go b/pkg/testutils/centralgrpc/connect_to_central.go index 2c44b6a2cfa1d..ce5dc1408d8ea 100644 --- a/pkg/testutils/centralgrpc/connect_to_central.go +++ b/pkg/testutils/centralgrpc/connect_to_central.go @@ -72,7 +72,7 @@ func RoxAPIEndpoint(t testutils.T) string { // UnauthenticatedGRPCConnectionToCentral is like GRPCConnectionToCentral, // but does not inject credentials into the request. func UnauthenticatedGRPCConnectionToCentral(t *testing.T) *grpc.ClientConn { - return grpcConnectionToCentral(t, nil) + return grpcConnectionToCentral(t) } // GRPCConnectionToCentral returns a GRPC connection to Central, which can be used in E2E tests. diff --git a/roxctl/common/connection.go b/roxctl/common/connection.go index 19246279db05e..a60d8a17b16e7 100644 --- a/roxctl/common/connection.go +++ b/roxctl/common/connection.go @@ -3,6 +3,7 @@ package common import ( "context" "crypto/tls" + "io" "net" "strings" "time" @@ -16,6 +17,7 @@ import ( "github.com/stackrox/rox/pkg/roxctl/common" "github.com/stackrox/rox/roxctl/common/auth" "github.com/stackrox/rox/roxctl/common/flags" + "github.com/stackrox/rox/roxctl/common/versioncheck" http1DowngradeClient "golang.stackrox.io/grpc-http1/client" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -33,6 +35,15 @@ func WithRetryTimeout(timeout time.Duration) GRPCOption { } } +// WithVersionCheck enables version compatibility checking on gRPC responses. +// Warnings are written to w at most once when a version incompatibility is +// detected from the Central version response header. +func WithVersionCheck(w io.Writer) GRPCOption { + return func(config *grpcConfig) { + config.versionCheckWriter = w + } +} + // GetGRPCConnection gets a grpc connection to Central with the correct auth func GetGRPCConnection(am auth.Method, connectionOpts ...GRPCOption) (*grpc.ClientConn, error) { endpoint, serverName, usePlaintext, err := ConnectNames() @@ -67,14 +78,15 @@ func GetGRPCConnection(am auth.Method, connectionOpts ...GRPCOption) (*grpc.Clie } type grpcConfig struct { - usePlaintext bool - insecure bool - opts clientconn.Options - serverName string - useDirectGRPC bool - forceHTTP1 bool - endpoint string - retryTimeout time.Duration + usePlaintext bool + insecure bool + opts clientconn.Options + serverName string + useDirectGRPC bool + forceHTTP1 bool + endpoint string + retryTimeout time.Duration + versionCheckWriter io.Writer } func makeCtxWithCommandHeader(ctx context.Context) context.Context { @@ -122,13 +134,19 @@ func createGRPCConn(c grpcConfig) (*grpc.ClientConn, error) { grpc_retry.WithRetriable(shouldRetry), } + unaryInterceptors := []grpc.UnaryClientInterceptor{ + addCommandHeaderUnaryInterceptor, + grpc_retry.UnaryClientInterceptor(retryOpts...), + } + if c.versionCheckWriter != nil { + unaryInterceptors = append(unaryInterceptors, versioncheck.CentralVersionClientInterceptor(c.versionCheckWriter)) + } + grpcDialOpts := []grpc.DialOption{ grpc.WithChainStreamInterceptor( addCommandHeaderStreamInterceptor, grpc_retry.StreamClientInterceptor(retryOpts...)), - grpc.WithChainUnaryInterceptor( - addCommandHeaderUnaryInterceptor, - grpc_retry.UnaryClientInterceptor(retryOpts...)), + grpc.WithChainUnaryInterceptor(unaryInterceptors...), } if c.usePlaintext { diff --git a/roxctl/common/environment/environment-impl.go b/roxctl/common/environment/environment-impl.go index 1833f212aa496..63627eedffd89 100644 --- a/roxctl/common/environment/environment-impl.go +++ b/roxctl/common/environment/environment-impl.go @@ -94,6 +94,7 @@ func (c *cliEnvironmentImpl) GRPCConnection(connectionOpts ...common.GRPCOption) if err != nil { return nil, errors.Wrap(err, "determining auth method") } + connectionOpts = append(connectionOpts, common.WithVersionCheck(c.io.ErrOut())) connection, err := common.GetGRPCConnection(am, connectionOpts...) return connection, errors.WithStack(err) } diff --git a/roxctl/common/versioncheck/check.go b/roxctl/common/versioncheck/check.go new file mode 100644 index 0000000000000..58f979fb799fd --- /dev/null +++ b/roxctl/common/versioncheck/check.go @@ -0,0 +1,87 @@ +package versioncheck + +import ( + "context" + "fmt" + "io" + "strings" + "sync/atomic" + + "github.com/stackrox/rox/pkg/clientconn" + "github.com/stackrox/rox/pkg/version" + "github.com/stackrox/rox/pkg/version/productstreams" + "github.com/stackrox/rox/pkg/version/versioncompatibility" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// CentralVersionClientInterceptor returns a gRPC unary client interceptor that reads +// the Central version from response header and emits a warning if the +// versions of Central and roxctl are incompatible. The warning is emitted at most once per +// interceptor instance. +func CentralVersionClientInterceptor(w io.Writer) grpc.UnaryClientInterceptor { + var checked atomic.Bool + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + var md metadata.MD + opts = append(opts, grpc.Header(&md)) + // Response headers are populated after the RPC completes. + err := invoker(ctx, method, req, reply, cc, opts...) + if vals := md.Get(clientconn.CentralVersionHeader); len(vals) > 0 { + if !checked.Swap(true) { + checkAndWarn(vals[0], w) + } + } + return err + } +} + +func checkAndWarn(centralVersion string, w io.Writer) bool { + remoteXY, err := productstreams.ParseXYFromVersionString(centralVersion) + if err != nil { + return false + } + roxctlVersion := version.GetMainVersion() + localXY, err := productstreams.ParseXYFromVersionString(roxctlVersion) + if err != nil { + return false + } + if localXY == remoteXY { + return false + } + + compat, err := versioncompatibility.ClassifyVersion(remoteXY) + if err != nil { + return false + } + if compat != versioncompatibility.IncompatibleAhead && compat != versioncompatibility.IncompatibleBehind { + return false + } + + versionRange, err := versioncompatibility.CompatibleVersions() + if err != nil { + return false + } + compatRange := formatVersionRange(versionRange) + + var direction string + if compat == versioncompatibility.IncompatibleAhead { + direction = "too old" + } else { + direction = "too new" + } + fmt.Fprintf(w, "Warning: Your roxctl %s is %s for this Central %s. "+ + "Correct functioning is not guaranteed. "+ + "Use roxctl version matching the Central version or at least such that the Central version is within the roxctl compatibility range.\n", + roxctlVersion, direction, centralVersion) + fmt.Fprintf(w, " roxctl: %s | Central: %s | Compatible Centrals: %s\n", + roxctlVersion, centralVersion, compatRange) + return true +} + +func formatVersionRange(versions []productstreams.XYVersion) string { + strs := make([]string, len(versions)) + for i, v := range versions { + strs[i] = v.String() + } + return strings.Join(strs, ", ") +} diff --git a/roxctl/common/versioncheck/check_test.go b/roxctl/common/versioncheck/check_test.go new file mode 100644 index 0000000000000..4551678f69001 --- /dev/null +++ b/roxctl/common/versioncheck/check_test.go @@ -0,0 +1,227 @@ +package versioncheck + +import ( + "bytes" + "context" + "net" + "testing" + + v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/pkg/clientconn" + "github.com/stackrox/rox/pkg/grpc/authn" + "github.com/stackrox/rox/pkg/grpc/versionheader" + "github.com/stackrox/rox/pkg/version/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" +) + +func TestCheckAndWarn(t *testing.T) { + cases := map[string]struct { + localVersion string + centralVersion string + expectWarning string + }{ + "same version": { + localVersion: "4.8.0", + centralVersion: "4.8.0", + }, + "same XY different patch": { + localVersion: "4.8.0", + centralVersion: "4.8.1", + }, + "compatible behind within range": { + localVersion: "4.8.0", + centralVersion: "4.6.0", + }, + "compatible ahead within range": { + localVersion: "4.8.0", + centralVersion: "4.10.0", + }, + "incompatible behind": { + localVersion: "4.8.0", + centralVersion: "4.3.0", + expectWarning: "too new", + }, + "incompatible ahead": { + localVersion: "4.8.0", + centralVersion: "4.15.0", + expectWarning: "too old", + }, + "invalid central version": { + localVersion: "4.8.0", + centralVersion: "invalid", + }, + "invalid local version": { + localVersion: "invalid", + centralVersion: "4.10.1", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + testutils.SetMainVersion(t, tc.localVersion) + + var buf bytes.Buffer + result := checkAndWarn(tc.centralVersion, &buf) + + assert.Equal(t, tc.expectWarning != "", result, "return value mismatch") + if tc.expectWarning != "" { + assert.Contains(t, buf.String(), tc.expectWarning) + assert.Contains(t, buf.String(), "Compatible Centrals:") + } else { + assert.Empty(t, buf.String()) + } + }) + } +} + +func TestCentralVersionClientInterceptor(t *testing.T) { + cases := map[string]struct { + centralVersion string + expectWarning string + }{ + "incompatible version warns": { + centralVersion: "4.2.0", + expectWarning: "too new", + }, + "compatible version is silent": { + centralVersion: "4.10.6", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + testutils.SetMainVersion(t, "4.8.0") + + var buf bytes.Buffer + conn := setupServerAndClient(t, + []grpc.UnaryServerInterceptor{injectVersionHeaderInterceptor(t, tc.centralVersion)}, + []grpc.UnaryClientInterceptor{CentralVersionClientInterceptor(&buf)}, + ) + + client := v1.NewMetadataServiceClient(conn) + _, err := client.GetMetadata(context.Background(), &v1.Empty{}) + require.NoError(t, err) + + if tc.expectWarning != "" { + assert.Contains(t, buf.String(), tc.expectWarning) + assert.Contains(t, buf.String(), "Compatible Centrals:") + } else { + assert.Empty(t, buf.String()) + } + }) + } +} + +func TestCentralVersionClientInterceptor_WithRealServerInterceptor(t *testing.T) { + // When the client is authenticated, CentralVersionServerInterceptor serves the same version so no warning is raised. + // When the client is not authenticated, CentralVersionServerInterceptor does not return the header and no warning is raised either. + cases := map[string]struct { + authenticated bool + }{ + "authenticated with matching versions is silent": {authenticated: true}, + "anonymous is silent": {authenticated: false}, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + testutils.SetMainVersion(t, "4.8.0") + + var serverInterceptors []grpc.UnaryServerInterceptor + if tc.authenticated { + serverInterceptors = append(serverInterceptors, injectIdentityInterceptor(t)) + } + serverInterceptors = append(serverInterceptors, versionheader.CentralVersionServerInterceptor()) + + var buf bytes.Buffer + conn := setupServerAndClient(t, + serverInterceptors, + []grpc.UnaryClientInterceptor{CentralVersionClientInterceptor(&buf)}, + ) + + client := v1.NewMetadataServiceClient(conn) + _, err := client.GetMetadata(context.Background(), &v1.Empty{}) + require.NoError(t, err) + + assert.Empty(t, buf.String()) + }) + } +} + +func TestCentralVersionClientInterceptor_WarnsOnlyOnce(t *testing.T) { + testutils.SetMainVersion(t, "4.8.0") + + var buf bytes.Buffer + conn := setupServerAndClient(t, + []grpc.UnaryServerInterceptor{ + injectIdentityInterceptor(t), + injectVersionHeaderInterceptor(t, "4.2.0"), + }, + []grpc.UnaryClientInterceptor{CentralVersionClientInterceptor(&buf)}, + ) + + client := v1.NewMetadataServiceClient(conn) + _, err := client.GetMetadata(context.Background(), &v1.Empty{}) + require.NoError(t, err) + assert.NotEmpty(t, buf.String()) + + buf.Reset() + + _, err = client.GetMetadata(context.Background(), &v1.Empty{}) + require.NoError(t, err) + assert.Empty(t, buf.String(), "warning should not be emitted a second time") +} + +// --- helpers and mocks --- + +type fakeIdentity struct { + authn.Identity +} + +type fakeMetadataServer struct { + v1.UnimplementedMetadataServiceServer +} + +func (f *fakeMetadataServer) GetMetadata(_ context.Context, _ *v1.Empty) (*v1.Metadata, error) { + return &v1.Metadata{Version: "test"}, nil +} + +func setupServerAndClient(t *testing.T, serverInterceptors []grpc.UnaryServerInterceptor, clientInterceptors []grpc.UnaryClientInterceptor) *grpc.ClientConn { + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer(grpc.ChainUnaryInterceptor(serverInterceptors...)) + v1.RegisterMetadataServiceServer(server, &fakeMetadataServer{}) + + go func() { + assert.NoError(t, server.Serve(listener)) + }() + t.Cleanup(server.Stop) + + conn, err := grpc.NewClient("passthrough:///bufnet", + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return listener.DialContext(ctx) + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithChainUnaryInterceptor(clientInterceptors...), + ) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, conn.Close()) }) + return conn +} + +func injectIdentityInterceptor(t testing.TB) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + ctx = authn.ContextWithIdentity(ctx, fakeIdentity{}, t) + return handler(ctx, req) + } +} + +func injectVersionHeaderInterceptor(t testing.TB, centralVersion string) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + require.NoError(t, grpc.SetHeader(ctx, metadata.Pairs(clientconn.CentralVersionHeader, centralVersion))) + return handler(ctx, req) + } +} diff --git a/tests/metadata_test.go b/tests/metadata_test.go index fa9521ff7ffb8..eb74145da8d70 100644 --- a/tests/metadata_test.go +++ b/tests/metadata_test.go @@ -10,20 +10,56 @@ import ( v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/pkg/buildinfo" + "github.com/stackrox/rox/pkg/clientconn" "github.com/stackrox/rox/pkg/testutils/centralgrpc" "github.com/stackrox/rox/pkg/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" ) func getMetadata(t *testing.T, conn *grpc.ClientConn) *v1.Metadata { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() service := v1.NewMetadataServiceClient(conn) - metadata, err := service.GetMetadata(ctx, &v1.Empty{}) + resp, err := service.GetMetadata(ctx, &v1.Empty{}) require.NoError(t, err) - return metadata + return resp +} + +func TestCentralVersionHeader(t *testing.T) { + conn := centralgrpc.GRPCConnectionToCentral(t) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + var metadataMD metadata.MD + resp, err := v1.NewMetadataServiceClient(conn).GetMetadata(ctx, &v1.Empty{}, grpc.Header(&metadataMD)) + require.NoError(t, err) + + metadataVals := metadataMD.Get(clientconn.CentralVersionHeader) + require.Len(t, metadataVals, 1, "expected %s response header from GetMetadata", clientconn.CentralVersionHeader) + assert.Equal(t, resp.GetVersion(), metadataVals[0]) + + pingVals := pingAndGetHeader(t, conn, ctx) + require.Len(t, pingVals, 1, "expected %s response header from Ping", clientconn.CentralVersionHeader) + assert.Equal(t, metadataVals[0], pingVals[0], "version header must be consistent across services") +} + +func TestCentralVersionHeader_AbsentForAnonymous(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + vals := pingAndGetHeader(t, centralgrpc.UnauthenticatedGRPCConnectionToCentral(t), ctx) + assert.Empty(t, vals, "anonymous requests must not receive %s", clientconn.CentralVersionHeader) +} + +func pingAndGetHeader(t *testing.T, conn *grpc.ClientConn, ctx context.Context) []string { + var md metadata.MD + _, err := v1.NewPingServiceClient(conn).Ping(ctx, &v1.Empty{}, grpc.Header(&md)) + require.NoError(t, err) + return md.Get(clientconn.CentralVersionHeader) } func TestMetadataIsSetCorrectly(t *testing.T) { From ad44dd4c54dacadfd05859bcd6da77e0bb98d696 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:21:04 +0200 Subject: [PATCH 408/638] chore(deps): bump github.com/onsi/gomega from 1.42.1 to 1.43.1 (#23069) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aacefbf59faf9..03585d079f1ea 100644 --- a/go.mod +++ b/go.mod @@ -92,7 +92,7 @@ require ( github.com/nxadm/tail v1.4.11 github.com/olekukonko/tablewriter v1.1.4 github.com/onsi/ginkgo/v2 v2.32.1 - github.com/onsi/gomega v1.42.1 + github.com/onsi/gomega v1.43.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/openshift-online/ocm-sdk-go v0.1.509 diff --git a/go.sum b/go.sum index 245ebecfd2d3f..a1b8d161668ef 100644 --- a/go.sum +++ b/go.sum @@ -1074,8 +1074,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= -github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= +github.com/onsi/gomega v1.43.1 h1:vGIPFuYrIO6/0Z09s0I0QQQgFchiX4+tb1re3MScJYo= +github.com/onsi/gomega v1.43.1/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= From 9e3bf38519fde104271ffb067f1f1e01243cea06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:21:09 +0200 Subject: [PATCH 409/638] chore(deps): bump github.com/RoaringBitmap/roaring/v2 from 2.26.0 to 2.29.0 (#23067) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 03585d079f1ea..78f190e0edf94 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/PagerDuty/go-pagerduty v1.8.0 - github.com/RoaringBitmap/roaring/v2 v2.26.0 + github.com/RoaringBitmap/roaring/v2 v2.29.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 github.com/adhocore/gronx v1.20.3 diff --git a/go.sum b/go.sum index a1b8d161668ef..f6cf445d5aa6b 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/RaduBerinde/axisds v0.1.0 h1:YItk/RmU5nvlsv/awo2Fjx97Mfpt4JfgtEVAGPrL github.com/RaduBerinde/axisds v0.1.0/go.mod h1:UHGJonU9z4YYGKJxSaC6/TNcLOBptpmM5m2Cksbnw0Y= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54 h1:bsU8Tzxr/PNz75ayvCnxKZWEYdLMPDkUgticP4a4Bvk= github.com/RaduBerinde/btreemap v0.0.0-20250419174037-3d62b7205d54/go.mod h1:0tr7FllbE9gJkHq7CVeeDDFAFKQVy5RnCSSNBOvdqbc= -github.com/RoaringBitmap/roaring/v2 v2.26.0 h1:K30ZxF4vZcIKvJsbmgfiep2K64f+dILJqkYGoj4xnwU= -github.com/RoaringBitmap/roaring/v2 v2.26.0/go.mod h1:BZufmFbox589n3j5eOmyTaLSGXbRLc2LmQvjKjzSEGU= +github.com/RoaringBitmap/roaring/v2 v2.29.0 h1:jSjxqZEqiF9W5dHUFsemupb9bnLaQJwZVe5yMetbsZg= +github.com/RoaringBitmap/roaring/v2 v2.29.0/go.mod h1:BZufmFbox589n3j5eOmyTaLSGXbRLc2LmQvjKjzSEGU= github.com/Shopify/toxiproxy/v2 v2.12.0 h1:d1x++lYZg/zijXPPcv7PH0MvHMzEI5aX/YuUi/Sw+yg= github.com/Shopify/toxiproxy/v2 v2.12.0/go.mod h1:R9Z38Pw6k2cGZWXHe7tbxjGW9azmY1KbDQJ1kd+h7Tk= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= From 44fddf00bce6ce722a7d0d4bcc6887b5fbe695a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:21:13 +0200 Subject: [PATCH 410/638] chore(deps): bump the aws-sdk-go-v2 group with 2 updates (#23065) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 78f190e0edf94..d72381dc6006c 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,9 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.33.5 github.com/aws/aws-sdk-go-v2/credentials v1.20.5 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8 github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2 github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 github.com/aws/smithy-go v1.28.2 diff --git a/go.sum b/go.sum index f6cf445d5aa6b..b9b635990640c 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ github.com/aws/aws-sdk-go-v2/credentials v1.20.5 h1:wklUVvHMc9xTQ3rcp49/ISpiMnhb github.com/aws/aws-sdk-go-v2/credentials v1.20.5/go.mod h1:fyEdrn6ccLFOkoK84j5bQyGTxp9zPt5l2XMhxf4DVZs= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 h1:AM4hHjww+PSFtt6E+UrBrPlZkWsePCLEt9AjkfQX+yM= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0/go.mod h1:3x/yXezeQjpOvBb4jEMxrS8SXvpdvJ5abv6l5c1gWM8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7 h1:omtMzSxsMhRDJzG/1bzT4dLA+jybY/nQLwEuoxt52yI= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.7/go.mod h1:oTPLfqQ08mCCN1qIkuZUsu0x6Iv/N7YEDax5qhCwN+o= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8 h1:U3VOx/+sECXsoZYT9/mUntdGQNaWHDpJk/Tp6SCfOGk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8/go.mod h1:QVOKByJxYvyW5jiGDh36l4dnTEq+mCZrsenNyrhbs7Q= github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 h1:Hp/VgjP0BysR3OgLlR057Vz2LcbbVnoWeJ+3qWiS/fY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3/go.mod h1:nwGV5qw7F1IZPgxCvA/ph8N2TAuz+BkRG/bXn808qMA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 h1:MUaM4f+kj1ZIBPZfUS8cxP1GKXXZtHJjAthy93AN7SM= @@ -243,8 +243,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3 h1:L8vIOxylma91Tc github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3/go.mod h1:fmPIZQzTExYuBNWFyi1P7IoDjvskgphXqK1yObMzusM= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0 h1:uB8ymkVosyourmGXCZHyWhJ4wuKA4xq3ii2dVMPtBZY= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0/go.mod h1:rK4RITSY/qJw3qVJ7p19fceOWuvrisqqOChFkX05n5I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1 h1:cFHmwLxZPvtoAlo79MboURL2+7b0TMHycnrcf5VZNXk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.1/go.mod h1:/uA+2Qj4jd5qBWagVC1AyzzDFXVK997E7U04w7Kw0wI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2 h1:2qsrxKfGgJrHBp4udIsZZVJGTX5lySaY9cJbZRB4J2Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2/go.mod h1:/uA+2Qj4jd5qBWagVC1AyzzDFXVK997E7U04w7Kw0wI= github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 h1:sccblHjf5Swf5xjlBTAUT0Jc0m/KZW0PQP511Cto83A= github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0/go.mod h1:39oJ1/fl7d10t0e9rtp/a+50tRLyDPSaccCZF3xXrcc= github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 h1:ZD5qFpWcaOKdTuhBi431pIDkCgrMkMlMT6jlpSPoIRI= From e45378f876ddc88c048540dc393824a233723b92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:21:28 +0200 Subject: [PATCH 411/638] chore(deps): bump google.golang.org/api from 0.298.0 to 0.299.0 (#23070) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 24 ++++++++++++------------ go.sum | 44 ++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index d72381dc6006c..b592b9c4e621a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.7 require ( cloud.google.com/go/artifactregistry v1.26.0 - cloud.google.com/go/compute/metadata v0.9.0 + cloud.google.com/go/compute/metadata v0.9.1 cloud.google.com/go/containeranalysis v0.19.0 cloud.google.com/go/securitycenter v1.46.0 cloud.google.com/go/storage v1.64.0 @@ -147,7 +147,7 @@ require ( golang.org/x/crypto v0.57.0 golang.org/x/mod v0.41.0 golang.org/x/net v0.59.0 - golang.org/x/oauth2 v0.36.0 + golang.org/x/oauth2 v0.37.0 golang.org/x/sync v0.23.0 golang.org/x/sys v0.48.0 golang.org/x/term v0.46.0 @@ -155,10 +155,10 @@ require ( golang.org/x/time v0.16.0 golang.org/x/tools v0.50.0 golang.stackrox.io/grpc-http1 v0.5.1 - google.golang.org/api v0.298.0 + google.golang.org/api v0.299.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 gopkg.in/mcuadros/go-syslog.v2 v2.3.0 @@ -189,7 +189,7 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect cel.dev/expr v0.25.2 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.23.2 // indirect + cloud.google.com/go/auth v0.23.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/iam v1.13.0 // indirect cloud.google.com/go/longrunning v1.2.0 // indirect @@ -200,7 +200,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect github.com/ComplianceAsCode/compliance-sdk v0.0.0-20250925092238-0b432985772e // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -269,7 +269,7 @@ require ( github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/structs v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/getsentry/sentry-go v0.34.0 // indirect @@ -317,8 +317,8 @@ require ( github.com/google/cel-go v0.31.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect + github.com/google/s2a-go v0.1.10 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.22 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect @@ -417,7 +417,7 @@ require ( github.com/spdx/tools-golang v0.5.7 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect github.com/stackrox/dotnet-scraper v0.0.0-20201023051640-72ef543323dd // indirect github.com/stackrox/istio-cves v0.0.0-20221007013142-0bde9b541ec8 // indirect github.com/stackrox/k8s-cves v0.0.0-20220818200547-7d0d1420c58d // indirect @@ -445,7 +445,7 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect @@ -458,7 +458,7 @@ require ( go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index b9b635990640c..28eccdfe29a7a 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/artifactregistry v1.26.0 h1:iq5kkdY2FJY8RkkNghUelb3EOcqyy7dVhfDGVk/Xw9g= cloud.google.com/go/artifactregistry v1.26.0/go.mod h1:c5FPi5GtDBP+OAr5kKhCBNQDT9ZgAyobXQjekx93VWs= -cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo= -cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0= +cloud.google.com/go/auth v0.23.3 h1:UMK+oBtuNGMCR/6i6mmySUItqjOazpJrbmZyhGbGBWo= +cloud.google.com/go/auth v0.23.3/go.mod h1:fClbry28fo7XkxhSeT6AQtAVAp6Jy0fW9N99PoPNPFM= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -45,8 +45,8 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= +cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= cloud.google.com/go/containeranalysis v0.19.0 h1:89ZhFvJHWzX9/jUJy/IEVbtSa2hv4+NtsvwWMEsUYnY= cloud.google.com/go/containeranalysis v0.19.0/go.mod h1:Zq0XHzUIa0oTa7H6aSR8HWqeJnoRI9syUcYJzfozjZQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -135,8 +135,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 h1:yzIYdwuro811Z27D3T80Wkd3rqZzb0K43nner7Eh1yE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis= @@ -452,8 +452,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -727,16 +727,16 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/s2a-go v0.1.10 h1:EMp+aOuXN6l8cE/gjF5Bt+vyZxsUuyCWe9chDWR/+uU= +github.com/google/s2a-go v0.1.10/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/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.20 h1:t/xL64VUoN69MuMRQuJETqYGOw4Z9mSRJK9epIEtwFk= -github.com/googleapis/enterprise-certificate-proxy v0.3.20/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= +github.com/googleapis/enterprise-certificate-proxy v0.3.22 h1:NU4XpII6jD+Dxcot94fqjE+AfJoE/lQP9q3faYGzC/c= +github.com/googleapis/enterprise-certificate-proxy v0.3.22/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1287,8 +1287,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= -github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= +github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E= +github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stackrox/docker-registry-client v0.2.0 h1:91TLimNkFgdwO9QF8rUC57DoLGiTAgDKm2KhLI2xkLU= github.com/stackrox/docker-registry-client v0.2.0/go.mod h1:4TU+pA11iczIvhtL0own2OJcJXc1o26tBHDivaXhlZU= github.com/stackrox/dotnet-scraper v0.0.0-20201023051640-72ef543323dd h1:vEjp7Q66zd4W72//Uk3uyVN50Mh/nFLbN9pb7CVK7mE= @@ -1462,8 +1462,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9a go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= @@ -1961,8 +1961,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.298.0 h1:YW18RkHBMZBA1ergX0m4biagzgbiPTb2uTsRsDPWNRY= -google.golang.org/api v0.298.0/go.mod h1:02qB8+Ox1ZFzcaKFMguy1nQLJmSIyvV6Ff4txJEXtl4= +google.golang.org/api v0.299.0 h1:b3K+ydSMd0kh6TQI6bJyApRQfqQX2MfSOaVkpM59mJw= +google.golang.org/api v0.299.0/go.mod h1:zlR3GVA8b2R5nv5Ij9UWe37StVB3cxDD7DBFi4ZFsHw= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2038,8 +2038,8 @@ google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAf google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2068,8 +2068,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 h1:ExN12ndbJ608cboPYflpTny6mXSzPrDLh0iTaVrRrds= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= From 79b9f802039c826bd6ab7230ed6797014a535fbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:21:45 +0200 Subject: [PATCH 412/638] chore(deps): bump github.com/prometheus/common from 0.70.1 to 0.71.0 (#23066) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b592b9c4e621a..1833db41889be 100644 --- a/go.mod +++ b/go.mod @@ -110,7 +110,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.70.1 + github.com/prometheus/common v0.71.0 github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815 github.com/quay/claircore/toolkit v1.7.0 github.com/quay/zlog/v2 v2.1.1 diff --git a/go.sum b/go.sum index 28eccdfe29a7a..9ed09da42219f 100644 --- a/go.sum +++ b/go.sum @@ -1160,8 +1160,8 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= -github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= +github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= +github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= From bac2a18decfb460494441fbc361da51895cc0e2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:40:02 +0200 Subject: [PATCH 413/638] chore(deps): bump github.com/containerd/containerd from 1.7.33 to 1.7.35 in /operator/tools/operator-sdk (#22758) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/operator-sdk/go.mod | 2 +- operator/tools/operator-sdk/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index f235f2ab529e9..017715960a436 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -28,7 +28,7 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/containerd/cgroups/v3 v3.1.2 // indirect - github.com/containerd/containerd v1.7.33 // indirect + github.com/containerd/containerd v1.7.35 // indirect github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index 89b1b7ac57e76..c16a85d690c9e 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -58,8 +58,8 @@ github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= -github.com/containerd/containerd v1.7.33 h1:iAkYGC/ifR/V+0eR4iXWHNGYUF0DF2PmGV5iz4Irj5M= -github.com/containerd/containerd v1.7.33/go.mod h1:gSbSCVjPCdkfJCjyrzz7aRC+xFlqVbatNpfHfVCYGUM= +github.com/containerd/containerd v1.7.35 h1:7AU2T1qI2OdNBmKkreWZ7kASHUNFItN5Hgejd9sY938= +github.com/containerd/containerd v1.7.35/go.mod h1:ozI//0TomTCLPhQREnx0IXDIQMg+Fk7yTtg9fNvU8EQ= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= From f57b351187f116a50a1f22f7575be0d154aaea67 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:10:46 +0200 Subject: [PATCH 414/638] chore(ci): Make byodb tests opt-in via label (#23049) --- .github/workflows/e2e-dispatch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index 50696c5561e63..178a967d166e7 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -105,7 +105,7 @@ jobs: print(f'{k} = {v}') jobs_registry = [ - Job(name='e2e-byodb-tests'), + Job(name='e2e-byodb-tests', require_pr_label=True), Job(name='e2e-db-backup-restore-tests'), Job(name='e2e-gke-upgrade-tests'), Job(name='e2e-nongroovy-tests'), From 241e72d82e2da997793eb14596406ff4dab13413 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:38:42 +0200 Subject: [PATCH 415/638] chore(deps): bump github.com/bufbuild/buf from 1.72.0 to 1.73.0 in /tools/proto (#22852) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: StackRox PR Fixxxer --- tools/proto/go.mod | 78 ++++++++++---------- tools/proto/go.sum | 173 ++++++++++++++++++++++----------------------- 2 files changed, 125 insertions(+), 126 deletions(-) diff --git a/tools/proto/go.mod b/tools/proto/go.mod index 756534936b74e..cf4a606c9fb9a 100644 --- a/tools/proto/go.mod +++ b/tools/proto/go.mod @@ -1,35 +1,36 @@ module github.com/stackrox/stackrox/tools/proto -go 1.26.3 +go 1.26.7 require ( - github.com/bufbuild/buf v1.72.0 + github.com/bufbuild/buf v1.73.0 github.com/favadi/protoc-go-inject-tag v1.4.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 google.golang.org/protobuf v1.36.12 ) require ( - buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20260626152828-968bf0468096.1 // indirect - buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 // indirect - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 // indirect - buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260713175918-10d915f5b43b.1 // indirect - buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260713175918-10d915f5b43b.1 // indirect - buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect - buf.build/go/app v0.2.1-0.20260626143626-be153867abea // indirect + buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20260722160903-4d94f3df3a7b.2 // indirect + buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.2 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260831211851-b4432e12a6e7.1 // indirect + buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.12-20260831211851-b4432e12a6e7.2 // indirect + buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.12-20241007202033-cf42259fcbfc.2 // indirect + buf.build/go/app v0.2.1-0.20260824172350-b0e76892c61a // indirect buf.build/go/bufplugin v0.10.0 // indirect buf.build/go/bufprivateusage v0.1.0 // indirect buf.build/go/interrupt v1.1.0 // indirect - buf.build/go/protovalidate v1.2.0 // indirect + buf.build/go/protovalidate v1.4.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect buf.build/go/spdx v0.2.0 // indirect buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 // indirect - cel.dev/expr v0.25.2 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect connectrpc.com/connect v1.20.0 // indirect connectrpc.com/otelconnect v0.9.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/bufbuild/protocompile v0.14.2-0.20260716165721-bb5762d29672 // indirect + github.com/bufbuild/protocompile v0.14.2-0.20260910151042-7436f7c76201 // indirect github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cli/browser v1.3.0 // indirect @@ -37,37 +38,36 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.6.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.8 // indirect - github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/cli v29.8.0+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.9 // indirect + github.com/docker/go-connections v0.8.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gofrs/flock v0.13.0 // indirect - github.com/google/cel-go v0.29.2 // indirect - github.com/google/go-containerregistry v0.21.7 // indirect + github.com/gofrs/flock v0.13.1 // indirect + github.com/google/go-containerregistry v0.22.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jdx/go-netrc v1.0.0 // indirect - github.com/klauspost/compress v1.19.0 // indirect + github.com/klauspost/compress v1.20.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/moby/api v1.55.0 // indirect - github.com/moby/moby/client v0.5.0 // indirect + github.com/moby/moby/api v1.56.0 // indirect + github.com/moby/moby/client v0.6.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9 // indirect + github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.60.0 // indirect + github.com/quic-go/quic-go v0.62.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/segmentio/encoding v0.5.4 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/tetratelabs/wazero v1.12.0 // indirect @@ -77,24 +77,24 @@ require ( go.lsp.dev/protocol v0.12.0 // indirect go.lsp.dev/uri v0.3.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.54.0 // indirect - golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect - golang.org/x/mod v0.38.0 // indirect - golang.org/x/net v0.57.0 // indirect - golang.org/x/sync v0.22.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.56.0 // indirect + golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect + golang.org/x/mod v0.40.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.40.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260904194346-d0f1323225a4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260904194346-d0f1323225a4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mvdan.cc/xurls/v2 v2.6.0 // indirect pluginrpc.com/pluginrpc v0.5.0 // indirect diff --git a/tools/proto/go.sum b/tools/proto/go.sum index bb43d3e61bf8b..ab10596db0edb 100644 --- a/tools/proto/go.sum +++ b/tools/proto/go.sum @@ -1,33 +1,35 @@ -buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20260626152828-968bf0468096.1 h1:bHi5/cwz7IPRhFcrRA4iytTjDI3WwFkv1m5K7y+YOTc= -buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20260626152828-968bf0468096.1/go.mod h1:1Znr6gmYBhbxWUPRrrVnSLXQsz8bvFVw1HHJq2bI3VQ= -buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 h1:HwzzCRS4ZrEm1++rzSDxHnO0DOjiT1b8I/24e8a4exY= -buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1/go.mod h1:8PRKXhgNes29Tjrnv8KdZzg3I1QceOkzibW1QK7EXv0= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 h1:fXh8CsdNpjRr8R5vFdqtIxPt/Lno2IIJlYOdZBIZn0w= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= -buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260713175918-10d915f5b43b.1 h1:c4w6fQ6wAhZLgVI+HZozlWX9I2aVPN34vrl/8cuovVc= -buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260713175918-10d915f5b43b.1/go.mod h1:mbcuFKpnCQsF8H2fBs1lN1dQVAiASPQKwFgTkXqYscI= -buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260713175918-10d915f5b43b.1 h1:qJUlFKLFdb3OFCpmA+r5AQbysgBD7WLRapE9lOs08r8= -buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260713175918-10d915f5b43b.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40= -buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 h1:iGPvEJltOXUMANWf0zajcRcbiOXLD90ZwPUFvbcuv6Q= -buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1/go.mod h1:nWVKKRA29zdt4uvkjka3i/y4mkrswyWwiu0TbdX0zts= -buf.build/go/app v0.2.1-0.20260626143626-be153867abea h1:krHaIyUJSTnurb0fbYQtsXkZWiWQ/ydOgfm61TPSODc= -buf.build/go/app v0.2.1-0.20260626143626-be153867abea/go.mod h1:V32mBaPWsfq6REAeZvvs/rQl7ZCl9Dn7eW1BBrmH0GQ= +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20260722160903-4d94f3df3a7b.2 h1:z5+rVknD0AvorY/QV9cb3ohUXw4E7HvxU4+2JUH6EHU= +buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20260722160903-4d94f3df3a7b.2/go.mod h1:Owcuj2phVyvThVYCD74iSV8ki0dAXw/uxkdcdZScdCc= +buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.2 h1:tE4A2RVukQOSd4K+6ndK+8s65FbjiBt2k5CiddXxCCo= +buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.2/go.mod h1:Gjxz0o7qPHokNsmlX8D+/aKd29NRLRr9CCUkPeMAzJ0= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260831211851-b4432e12a6e7.1 h1:BpToyjv+oz0qldatN/ZFTL+vVtPVavFCY+g4rRu03bs= +buf.build/gen/go/bufbuild/registry/connectrpc/go v1.20.0-20260831211851-b4432e12a6e7.1/go.mod h1:hMqlU8iWXqCU7UOBs8PTDG/z7BFnhIknJZ4XMWcBx3E= +buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.12-20260831211851-b4432e12a6e7.2 h1:cik2XvKM1UV8HgLWD2vnBvV4GgT5NfpLJBb9UgLKzzg= +buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.12-20260831211851-b4432e12a6e7.2/go.mod h1:91H8i3iR9GoomNkM9X13RTFL8cVNAHnS44BdgAnbqz4= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.12-20241007202033-cf42259fcbfc.2 h1:9xw8g8Q6zHLC7CO/jtpl2UK0kQticxIG+mI6N86f2/M= +buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.12-20241007202033-cf42259fcbfc.2/go.mod h1:+0bw+k8FY5MU3tojFh33HeA5xMfGWjAUQpr+maW5Z3o= +buf.build/go/app v0.2.1-0.20260824172350-b0e76892c61a h1:m3hkeJOeLSq/1yTSMLyV5ipY6u3XrfS3gV8ZIqkUJJA= +buf.build/go/app v0.2.1-0.20260824172350-b0e76892c61a/go.mod h1:w19H+Lh2cAV/uL1FhcdDlyoxLTm8SkQAHW3CfmXZbFU= buf.build/go/bufplugin v0.10.0 h1:vZBX0mq9as5UIBug8U+/DkGRaHNlM/HVOw59O8fvOIU= buf.build/go/bufplugin v0.10.0/go.mod h1:ax7obVurKDH1I2nR4pFTS+TE6K3kZhTmwDCN2YgdV8I= buf.build/go/bufprivateusage v0.1.0 h1:SzCoCcmzS3zyXHEXHeSQhGI7OTkgtljoknLzsUz9Gg4= buf.build/go/bufprivateusage v0.1.0/go.mod h1:GlCCJ3VVF7EqqU0CoRmo1FzAwwaKymEWSr+ty69xU5w= buf.build/go/interrupt v1.1.0 h1:olBuhgv9Sav4/9pkSLoxgiOsZDgM5VhRhvRpn3DL0lE= buf.build/go/interrupt v1.1.0/go.mod h1:ql56nXPG1oHlvZa6efNC7SKAQ/tUjS6z0mhJl0gyeRM= -buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0= -buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= buf.build/go/protoyaml v0.7.0/go.mod h1:+a0cavd0uMvirb87xdu2ZMMmjlIQoiH/N2Ich5MGSQ0= buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw= buf.build/go/spdx v0.2.0/go.mod h1:bXdwQFem9Si3nsbNy8aJKGPoaPi5DKwdeEp5/ArZ6w8= buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 h1:njYKSWoLiq2i5O7y2bPPU2Yzp7iAU0Wk9KJ2OoAhNiU= buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5/go.mod h1:DQmodNT9EHX94WzUaWiZK+/4EaFa/xZTc1gzfCxZVXU= -cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= -cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= connectrpc.com/grpcreflect v1.3.0 h1:Y4V+ACf8/vOb1XOc251Qun7jMB75gCUNw6llvB9csXc= @@ -42,10 +44,10 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6 github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= -github.com/bufbuild/buf v1.72.0 h1:VMmGFtCLrxyS2wkpghExmhhiqJDdmc8DcwAvsGJGJ94= -github.com/bufbuild/buf v1.72.0/go.mod h1:bhtIlPDo3q/PDw4yaTdx2+jxkc33Aq+ygFIi6Diz2yU= -github.com/bufbuild/protocompile v0.14.2-0.20260716165721-bb5762d29672 h1:6xykiXQPoF/hhOjAhwiHQ0kgcAvrDZw0Tjl1VQkpu5c= -github.com/bufbuild/protocompile v0.14.2-0.20260716165721-bb5762d29672/go.mod h1:jPUiZUFWc8E3Kc2Y4SRlGAdjde4amGkHY0BUACNS43E= +github.com/bufbuild/buf v1.73.0 h1:fDNKlxaf+PYi20dmVM7Yt3kOfXEhwrgKDLMNZFjztDs= +github.com/bufbuild/buf v1.73.0/go.mod h1:rfWpi8FmfTPW4CbLFIXZYAux2FWBiCMY6BUOlGqnR84= +github.com/bufbuild/protocompile v0.14.2-0.20260910151042-7436f7c76201 h1:jBaIQ1eixwqtjBKJk8gw+dKtmbkp3w8sLJtJ7kav1EE= +github.com/bufbuild/protocompile v0.14.2-0.20260910151042-7436f7c76201/go.mod h1:bX3ObJfML+aki7PJevkWLSlOfEZ8EnNsfCz0hiYkhiI= github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b h1:b7wvo9ZhjLzCp7tGbOUMvgtYTnd33zGSAmMxcdxMnhQ= github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -59,16 +61,14 @@ github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmC github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8= -github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= -github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= -github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= -github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/cli v29.8.0+incompatible h1:ih0c2jq/nN7QfES8zIfwSzIhRScjs4ehER+kZ60aeSk= +github.com/docker/cli v29.8.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.9 h1:BkydjIgZ46JnDbqyM2p2fc63KMw6y+KHL3Em/2AGJ7w= +github.com/docker/docker-credential-helpers v0.9.9/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M= +github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/favadi/protoc-go-inject-tag v1.4.0 h1:K3KXxbgRw5WT4f43LbglARGz/8jVsDOS7uMjG4oNvXY= @@ -76,19 +76,17 @@ github.com/favadi/protoc-go-inject-tag v1.4.0/go.mod h1:AZ+PK+QDKUOLlBRG0rYiKkUX github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= -github.com/google/cel-go v0.29.2 h1:ZtDxkeiMmz0mxbKDYiNkE5Lk7V5edMRcaaDf2jX002k= -github.com/google/cel-go v0.29.2/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= +github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= -github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/go-containerregistry v0.22.1 h1:RZuuSYhTvlDvtsK+NkutoCZ//C0X2ebLK8X8l3ULs84= +github.com/google/go-containerregistry v0.22.1/go.mod h1:bJR35SK8XgisYmhg/FMQ/5RK0S/XrOAqLBV5/LR2XE0= 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -97,8 +95,8 @@ github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ= github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8= github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88= github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM= -github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= -github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= +github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -107,20 +105,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= -github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= -github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= +github.com/moby/moby/api v1.56.0 h1:GQzua3NA599ASSIICx0iFgiJeO9YkdDARvQsm23ZZuQ= +github.com/moby/moby/api v1.56.0/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk= +github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= +github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9 h1:UyKlK0Ke63afxhHrgJAk8KlCt+kP9KYBRUsWG6lK2WM= -github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 h1:arwj11zP0yJIxIRiDn22E0H8PxfF7TsTrc2wIPFIsf4= @@ -129,8 +127,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0= -github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk= +github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= +github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= @@ -145,15 +143,15 @@ github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= @@ -168,18 +166,18 @@ go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -188,33 +186,34 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= -golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d h1:Jkpk39hlTZOIp3RbfvNX9R8Hv+Sw0X89nlU/xFOErsc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -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= +google.golang.org/genproto/googleapis/api v0.0.0-20260904194346-d0f1323225a4 h1:NCe/UiklGd/9xjT+ROBVhJ1kf6TRQaFedsR+z7u1gvo= +google.golang.org/genproto/googleapis/api v0.0.0-20260904194346-d0f1323225a4/go.mod h1:fJ2lYaWjqNknJyQBOCd0fA3HnEElJqGplH71a2txi+g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260904194346-d0f1323225a4 h1:5t+ZydAFj5kGVLrgCvLmpmCf9ylGRd64hpEronfRaws= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260904194346-d0f1323225a4/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= From d4769209b2ee9247025c86492a6b7cc2cb123bf7 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 04:47:01 +0000 Subject: [PATCH 416/638] chore(deps): refresh rpm lockfiles (#23018) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 320 ++++++++++++++++++++++++------------------------- 1 file changed, 160 insertions(+), 160 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index 458135bbf6806..e8926144697aa 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -39,48 +39,48 @@ arches: name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 12361 - checksum: sha256:7c4e8b7e0647a459b3b04e9deb17b6d5d02a43fa1bff0e8dc782f48e08fccd30 + size: 12453 + checksum: sha256:3d9ec6b63182ed0120fad760a8fc20a4bfa05e96134b4cc5c6fe42b9f85ec9c5 name: go-toolset - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-1.26.7-1.el9_8.aarch64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-1.26.7-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1520214 - checksum: sha256:2f1b635e07c0f74ac53e9a79e28e287ae47742599deec1138aa7a27f94b4d5c9 + size: 1520141 + checksum: sha256:27c00a1943d2e5c5e3a19308f6b8650e3740d784e8dc7e4aa0036693c958b1c4 name: golang - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.aarch64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-bin-1.26.7-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 42741902 - checksum: sha256:aff04f0bea0253c15c37d1f008d2826884cfdc401714c0d884c3172f2affce01 + size: 42755650 + checksum: sha256:4a3e09bbf75a009a9e4ef2b0f2703b517353335f9113d22f3562886390c13eb4 name: golang-bin - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.aarch64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-race-1.26.7-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1743223 - checksum: sha256:cb3fed044deab06fcf00a3426b635f4562cdfd6f5937912d9e2a854b88498c72 + size: 1743339 + checksum: sha256:80c03d0c817e3f37bae189287bb6b42fc18dcadeb22c90e57f01d6ecbceec4a7 name: golang-race - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/golang-src-1.26.7-2.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 12881267 - checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 + size: 12879471 + checksum: sha256:ff54c6f194fcf25d743af1af7893242085c798b7ce04056066fee993c2167166 name: golang-src - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.aarch64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2899913 - checksum: sha256:929179b11dddaa905f5261d8df9977ce70f311fb482d01ef13ff2da6d8c1e825 + size: 2916797 + checksum: sha256:2b5063d95b2c1611a168cdf1dae583a082940b9074c14e85bad921ee132b46fb name: kernel-headers - evr: 5.14.0-687.49.1.el9_8 - sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + evr: 5.14.0-687.52.1.el9_8 + sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -1040,12 +1040,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/g/golang-1.26.7-2.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 36535123 - checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 + size: 36539286 + checksum: sha256:7a9ea792be9d09b61bc321ce8b65a040430d06891d6372aeb929753c2f270770 name: golang - evr: 1.26.7-1.el9_8 + evr: 1.26.7-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 846236 @@ -1286,12 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152473969 - checksum: sha256:ce455287de0f71efd1128f4cd574f7d418b57ae9784903bbe8b8d3e68062a80b + size: 152521141 + checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 name: kernel - evr: 5.14.0-687.49.1.el9_8 + evr: 5.14.0-687.52.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/6ec982e105d54c866591c0a07c68f06bb302a8cc9e880f9c874bdf4d4c7fa5ca-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/52e584b12993891100ca340d6e265dc5402bb7ebdf9454c499a2d73f26886080-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms size: 67183 - checksum: sha256:6ec982e105d54c866591c0a07c68f06bb302a8cc9e880f9c874bdf4d4c7fa5ca + checksum: sha256:52e584b12993891100ca340d6e265dc5402bb7ebdf9454c499a2d73f26886080 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1676,48 +1676,48 @@ arches: name: glibc-devel evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 12381 - checksum: sha256:ff9550d877b563eb05b3a49b4f352a1b578a42cd234704cbc43db08825b9d632 + size: 12469 + checksum: sha256:e41a8424c10bc94cd0bfcd5e047dde7bced6d7f5e8e56ceeec3845ea9c0a91ce name: go-toolset - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-1.26.7-1.el9_8.ppc64le.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-1.26.7-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1520537 - checksum: sha256:d09b9280e3d43c13454aadceb3c00e0997063fc7d621b7b8a2359b4c737f619f + size: 1520306 + checksum: sha256:0666a0e9d74aba917e5dd385629548b8d4e6f13e29441f3dec00e2a928c0173a name: golang - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.ppc64le.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-bin-1.26.7-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 43924293 - checksum: sha256:db278a5a095d81a8100e884f5ffebcff5cca10b7c4f3fd3f9230b596c667026e + size: 43905181 + checksum: sha256:079e5c168a173ea7b7caa53a6b08c768fd9ce36c9dc746a45b2e47aec9ae8c62 name: golang-bin - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.ppc64le.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-race-1.26.7-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1663611 - checksum: sha256:ed9e4c84393cf7875bf23a34489fa235610df93ebb97dc9d2c52bb9d3d0733f4 + size: 1663702 + checksum: sha256:06cbd04d297dd3d522deab3c3c4b4226358efb4616eb55988edcac4c373a6617 name: golang-race - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/golang-src-1.26.7-2.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 12881267 - checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 + size: 12879471 + checksum: sha256:ff54c6f194fcf25d743af1af7893242085c798b7ce04056066fee993c2167166 name: golang-src - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.ppc64le.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2923685 - checksum: sha256:378e87949698681758321c003bcab9b909d3d48f5c0fa32b7c8cccdb8e079d7b + size: 2940597 + checksum: sha256:8dff161bb85c25e90d5540b565952a0abedb0cab3b926da46e3ba15806677490 name: kernel-headers - evr: 5.14.0-687.49.1.el9_8 - sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + evr: 5.14.0-687.52.1.el9_8 + sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -2684,12 +2684,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/g/golang-1.26.7-2.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 36535123 - checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 + size: 36539286 + checksum: sha256:7a9ea792be9d09b61bc321ce8b65a040430d06891d6372aeb929753c2f270770 name: golang - evr: 1.26.7-1.el9_8 + evr: 1.26.7-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 846236 @@ -2930,12 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152473969 - checksum: sha256:ce455287de0f71efd1128f4cd574f7d418b57ae9784903bbe8b8d3e68062a80b + size: 152521141 + checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 name: kernel - evr: 5.14.0-687.49.1.el9_8 + evr: 5.14.0-687.52.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/ec878a0f354462d95a8c6b76e74fc1a92f7381fb425bbae516e44b7f0c54ba8b-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/b6665d455966c348ea2e41ba6e1a1b9c9a87cf956694754d7198540e5be3d4a1-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms size: 68368 - checksum: sha256:ec878a0f354462d95a8c6b76e74fc1a92f7381fb425bbae516e44b7f0c54ba8b + checksum: sha256:b6665d455966c348ea2e41ba6e1a1b9c9a87cf956694754d7198540e5be3d4a1 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3326,48 +3326,48 @@ arches: name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 12353 - checksum: sha256:aba50425a0797a21ef3c7a5bc054ed4afb3c96a8802ea0758db89a4bf974a6f5 + size: 12445 + checksum: sha256:a0a8e830dc1a6cb78d888682650f74a03e99a3e06f0d9e944f083d58cf479d5e name: go-toolset - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-1.26.7-1.el9_8.s390x.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-1.26.7-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1520130 - checksum: sha256:e7bee02758864c0cb61bdd3e8a1318b5c32195df2ec07bb8e914639b381acbcb + size: 1520127 + checksum: sha256:1a996cac6c20254f109cc904e8e42866520dd686d85accea215e0e359d9c41d6 name: golang - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.s390x.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-bin-1.26.7-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 46549574 - checksum: sha256:e03b18d614adf56123dc37c84535013b366e645e270ec105c2489e6a569703bd + size: 46533766 + checksum: sha256:948cdcf452abbbd590d9552a1eae7ef061c78bcd717c80d059def1c9db440089 name: golang-bin - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.s390x.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-race-1.26.7-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1711630 - checksum: sha256:7e441ca5a2954e97ffb978489bec3b13378708fde3fe471f7aca78f3b6089094 + size: 1711787 + checksum: sha256:cdf8536fbba9d019882ad9e90b0b34d4997077eac595fef7adcb631768f4c492 name: golang-race - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/golang-src-1.26.7-2.el9_8.noarch.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 12881267 - checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 + size: 12879471 + checksum: sha256:ff54c6f194fcf25d743af1af7893242085c798b7ce04056066fee993c2167166 name: golang-src - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.s390x.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2929913 - checksum: sha256:d3d0b654585b3db69cb1423296869e3c272aef1c658cfd9d103d5e8c81884986 + size: 2946745 + checksum: sha256:df7aa11358870f2b9e0e27bd07827ba0cdc54c93128adb11898636a31861da94 name: kernel-headers - evr: 5.14.0-687.49.1.el9_8 - sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + evr: 5.14.0-687.52.1.el9_8 + sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -4321,12 +4321,12 @@ arches: evr: 1.2.11-40.el9 sourcerpm: zlib-1.2.11-40.el9.src.rpm source: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/g/golang-1.26.7-2.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 36535123 - checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 + size: 36539286 + checksum: sha256:7a9ea792be9d09b61bc321ce8b65a040430d06891d6372aeb929753c2f270770 name: golang - evr: 1.26.7-1.el9_8 + evr: 1.26.7-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 846236 @@ -4567,12 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152473969 - checksum: sha256:ce455287de0f71efd1128f4cd574f7d418b57ae9784903bbe8b8d3e68062a80b + size: 152521141 + checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 name: kernel - evr: 5.14.0-687.49.1.el9_8 + evr: 5.14.0-687.52.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/ca31328d7c6e06797a1779175be3da1217c64a74e6a50188219aad4651b50d9a-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/6932c6640f14bb347e360c92a27479a02b616928e7675123cc01ecb870ae20db-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms size: 64964 - checksum: sha256:ca31328d7c6e06797a1779175be3da1217c64a74e6a50188219aad4651b50d9a + checksum: sha256:6932c6640f14bb347e360c92a27479a02b616928e7675123cc01ecb870ae20db - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4964,48 +4964,48 @@ arches: name: glibc-headers evr: 2.34-275.el9_8 sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/go-toolset-1.26.7-1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 12393 - checksum: sha256:e8d30ba284a372bbdbf1f899231ad3c59ddac6a783415f0784d194a1f4d314f1 + size: 12485 + checksum: sha256:d90108b27b68942ac00068421d4cb3551320fb8345bdabf5684d6868dca0b25c name: go-toolset - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-1.26.7-1.el9_8.x86_64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-1.26.7-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1520026 - checksum: sha256:ab786d635b438822c9fa3ddc2a0f056e1cf9ab83bcfcca285c2ac9714a9b903c + size: 1519594 + checksum: sha256:120adc77ba187e12ba0f564e989ad2a495a3cd4db854ff8950bb0fa820d440d3 name: golang - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-bin-1.26.7-1.el9_8.x86_64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-bin-1.26.7-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 47194775 - checksum: sha256:d68cc646382e339b5860d44cdf3c23c135523dc52a5cf52c4919f25130ad7c07 + size: 47210487 + checksum: sha256:d62137e48551c075bf6addd0946a49d833a63e57eee66199220a2102039abc32 name: golang-bin - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-race-1.26.7-1.el9_8.x86_64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-race-1.26.7-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1742998 - checksum: sha256:c4f7bcb59a53fb8881935b88869ccb6cac3facf6444b344e2f5adb76e16cf932 + size: 1743083 + checksum: sha256:afbb626a86be3ed06007041a4d1c9c1fc82ab06d79ab9e8e1f32ece08427290a name: golang-race - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-src-1.26.7-1.el9_8.noarch.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/golang-src-1.26.7-2.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 12881267 - checksum: sha256:b570b95df0bc6429486a71a10fb433f56bb3a694f5de8a777131b2a841e81181 + size: 12879471 + checksum: sha256:ff54c6f194fcf25d743af1af7893242085c798b7ce04056066fee993c2167166 name: golang-src - evr: 1.26.7-1.el9_8 - sourcerpm: golang-1.26.7-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.49.1.el9_8.x86_64.rpm + evr: 1.26.7-2.el9_8 + sourcerpm: golang-1.26.7-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2939221 - checksum: sha256:ac9fe2b15be1ea2445c28abcaa21b62a118e56ef5984aea1c9d204885786f99d + size: 2956089 + checksum: sha256:97fdbd1cd14ee0a1f5818edc96e0c3e0f1ebfcaacdd7c63f33d2b791ea177f94 name: kernel-headers - evr: 5.14.0-687.49.1.el9_8 - sourcerpm: kernel-5.14.0-687.49.1.el9_8.src.rpm + evr: 5.14.0-687.52.1.el9_8 + sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5944,12 +5944,12 @@ arches: checksum: sha256:5fa267daa0e7df3e072c17e81db41b809b256da8cfb0372669d9a1bea937a01a name: delve evr: 1.26.1-1.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/g/golang-1.26.7-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/g/golang-1.26.7-2.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 36535123 - checksum: sha256:3e46d00b71773429b84079992fff32610eda8917d7319d09c3cee5e6e4bd5bf5 + size: 36539286 + checksum: sha256:7a9ea792be9d09b61bc321ce8b65a040430d06891d6372aeb929753c2f270770 name: golang - evr: 1.26.7-1.el9_8 + evr: 1.26.7-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/l/libmpc-1.2.1-4.el9.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 846236 @@ -6190,12 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.49.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152473969 - checksum: sha256:ce455287de0f71efd1128f4cd574f7d418b57ae9784903bbe8b8d3e68062a80b + size: 152521141 + checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 name: kernel - evr: 5.14.0-687.49.1.el9_8 + evr: 5.14.0-687.52.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/cda6cd8154f7c7b97f9797580de964f58c6ebac82457afb91832fe5f8c399da6-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/d6a984811cecd6e42c3cfecf4be2d1281ed1bf4913f95a424b2bcec27cad24c8-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms size: 70416 - checksum: sha256:cda6cd8154f7c7b97f9797580de964f58c6ebac82457afb91832fe5f8c399da6 + checksum: sha256:d6a984811cecd6e42c3cfecf4be2d1281ed1bf4913f95a424b2bcec27cad24c8 From 6636fb5d2a7f8499e4edd086f294fa3ba2ded223 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2026 07:44:32 +0000 Subject: [PATCH 417/638] chore(deps): update quay.io/rhacs-eng/konflux-tasks:latest docker digest to 243b1db (#23055) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index ded1ab7f050ca..14c58b159f06f 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 85bdfaa2ce8f1..0f30a4cc32a21 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 1fa37a222d808..572e070cb4e13 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index a6f8b2dbbb411..62f2e080d7f0a 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index cd0acc1e74e2a..a86d6052e7593 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index f67d85c66a613..7bed9f7f584a3 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 10ce4bb760115..fda4a68727954 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 1285b72837557..c6a3c4d137c68 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:06b877f34bc873eddbf28ad8a921bf007533c1ce8dd89e8d2e1503b79344c9ae + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 - name: kind value: task resolver: bundles From 830bd764406f5b8f3621baebe5b8f312d5cfe42c Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:10:56 -0500 Subject: [PATCH 418/638] ROX-36973: Prevent delegated scan hang when no Scanner V2 (#22836) Co-authored-by: Kirk Bater --- sensor/common/scan/scan.go | 9 +- sensor/common/scan/scan_test.go | 59 ++++++++++ sensor/common/scannerclient/grpc_client.go | 103 ------------------ sensor/common/scannerclient/singleton.go | 32 ++++-- sensor/common/scannerclient/singleton_test.go | 90 +++++++++++++++ 5 files changed, 178 insertions(+), 115 deletions(-) create mode 100644 sensor/common/scannerclient/singleton_test.go diff --git a/sensor/common/scan/scan.go b/sensor/common/scan/scan.go index 897f58e37b318..afcfe2346d431 100644 --- a/sensor/common/scan/scan.go +++ b/sensor/common/scan/scan.go @@ -154,7 +154,8 @@ func (s *LocalScan) EnrichLocalImageInNamespace(ctx context.Context, centralClie // Check if there is a local Scanner. // No need to continue if there is no local Scanner. - if s.scannerClientSingleton() == nil { + scannerClient := s.scannerClientSingleton() + if scannerClient == nil { return nil, errors.Join(ErrNoLocalScanner, ErrEnrichNotStarted) } @@ -197,7 +198,7 @@ func (s *LocalScan) EnrichLocalImageInNamespace(ctx context.Context, centralClie // Only proceed if metadata was fetched successfully if errorList.Empty() { // Perform partial scan (image analysis / identify components) via local scanner. - scannerResp = s.fetchImageAnalysis(ctx, errorList, reg, pullSourceImage) + scannerResp = s.fetchImageAnalysis(ctx, errorList, reg, pullSourceImage, scannerClient) // Fetch signatures associated with image from registry. Do this even if the scan above failed, because that // doesn't necessarily mean signatures cannot be fetched @@ -413,9 +414,9 @@ func (s *LocalScan) enrichImageWithMetadata(ctx context.Context, errorList *erro } // fetchImageAnalysis analyzes an image via the local scanner. -func (s *LocalScan) fetchImageAnalysis(ctx context.Context, errorList *errorhelpers.ErrorList, registry registryTypes.ImageRegistry, image *storage.Image) *scannerclient.ImageAnalysis { +func (s *LocalScan) fetchImageAnalysis(ctx context.Context, errorList *errorhelpers.ErrorList, registry registryTypes.ImageRegistry, image *storage.Image, scannerClient scannerclient.ScannerClient) *scannerclient.ImageAnalysis { // Scan the image via local scanner. - scannerResp, err := s.scanImg(ctx, image, registry, s.scannerClientSingleton()) + scannerResp, err := s.scanImg(ctx, image, registry, scannerClient) if err != nil { log.Debugf("Scan for image %q with id %v failed: %v", image.GetName().GetFullName(), image.GetId(), err) image.Notes = append(image.Notes, storage.Image_MISSING_SCAN_DATA) diff --git a/sensor/common/scan/scan_test.go b/sensor/common/scan/scan_test.go index d40bd633c82b4..cb86d76abb3d6 100644 --- a/sensor/common/scan/scan_test.go +++ b/sensor/common/scan/scan_test.go @@ -122,6 +122,65 @@ func (suite *scanTestSuite) TestLocalEnrichment() { suite.Assert().True(getRegistriesTriggered, "get registry was not triggered") } +// TestScannerClientCapturedOnce is a regression guard for a TOCTOU nil-client +// dereference: the scanner client singleton must be consulted once per +// enrichment and the client captured at the nil-check must be the one handed to +// the scan. Otherwise a concurrent reset of the singleton (e.g. on Central +// reconnect) could return non-nil at the check but nil at the scan, panicking +// when a method is invoked on the nil client. +func (suite *scanTestSuite) TestScannerClientCapturedOnce() { + fakeRegStore := &fakeRegistryStore{} + mirrorStore := mirrorStoreMocks.NewMockStore(gomock.NewController(suite.T())) + + // Returns a valid client on the first call, then nil, simulating a + // concurrent singleton reset occurring mid-enrichment. + capturedClient := &emptyClient{} + singletonCalls := 0 + singleton := func() scannerclient.ScannerClient { + singletonCalls++ + if singletonCalls == 1 { + return capturedClient + } + return nil + } + + var scannedWithClient scannerclient.ScannerClient + recordingScan := func(_ context.Context, _ *storage.Image, + reg registryTypes.ImageRegistry, client scannerclient.ScannerClient) (*scannerclient.ImageAnalysis, error) { + scannedWithClient = client + return successfulScan(context.Background(), nil, reg, client) + } + + scan := LocalScan{ + scanImg: recordingScan, + fetchSignaturesWithRetry: successfulFetchSignatures, + getPullSecretRegistries: func(*storage.ImageName, string, []string) ([]registryTypes.ImageRegistry, error) { + return []registryTypes.ImageRegistry{&fakeRegistry{fail: false}}, nil + }, + getGlobalRegistries: func(*storage.ImageName) ([]registryTypes.ImageRegistry, error) { + return []registryTypes.ImageRegistry{&fakeRegistry{fail: false}}, nil + }, + scannerClientSingleton: singleton, + scanSemaphore: semaphore.NewWeighted(10), + getCentralRegistries: fakeRegStore.GetMatchingCentralRegistryIntegrations, + mirrorStore: mirrorStore, + maxSemaphoreWaitTime: defaultMaxSemaphoreWaitTime, + } + + containerImg, err := utils.GenerateImageFromString("docker.io/nginx") + suite.Require().NoError(err, "failed creating test image") + + imageServiceClient := suite.createMockImageServiceClient(types.ToImage(containerImg), false) + mirrorStore.EXPECT().PullSources(containerImg.GetName().GetFullName()) + + // Must not panic even though the singleton starts returning nil after the check. + _, err = scan.EnrichLocalImageInNamespace(context.Background(), imageServiceClient, genScanReq(containerImg, "fake-namespace", "", false)) + suite.Require().NoError(err, "unexpected error when enriching image") + + suite.Assert().Equal(1, singletonCalls, "scanner client singleton should be consulted exactly once") + suite.Assert().Same(capturedClient, scannedWithClient, "scan should use the client captured at the nil-check") +} + func (suite *scanTestSuite) TestEnrichImageFailures() { type testCase struct { scanImg func(ctx context.Context, image *storage.Image, diff --git a/sensor/common/scannerclient/grpc_client.go b/sensor/common/scannerclient/grpc_client.go index 1dd8853914d86..6f689846963b2 100644 --- a/sensor/common/scannerclient/grpc_client.go +++ b/sensor/common/scannerclient/grpc_client.go @@ -3,24 +3,18 @@ package scannerclient import ( "context" "fmt" - "strings" "github.com/google/go-containerregistry/pkg/authn" "github.com/pkg/errors" v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" "github.com/stackrox/rox/generated/storage" - "github.com/stackrox/rox/pkg/clientconn" "github.com/stackrox/rox/pkg/env" - "github.com/stackrox/rox/pkg/images/utils" "github.com/stackrox/rox/pkg/logging" - "github.com/stackrox/rox/pkg/mtls" "github.com/stackrox/rox/pkg/registries/types" pkgscanner "github.com/stackrox/rox/pkg/scannerv4" "github.com/stackrox/rox/pkg/scannerv4/client" "github.com/stackrox/rox/sensor/common/centralcabundle" scannerV1 "github.com/stackrox/scanner/generated/scanner/api/v1" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" ) var ( @@ -45,13 +39,6 @@ type ImageAnalysis struct { V4Contents *v4.Contents } -// v2Client is the client for StackRox Scanner based on Clair V2, also -// known as Scanner V2. -type v2Client struct { - scannerV1.ImageScanServiceClient - conn *grpc.ClientConn -} - // v4Client is the client for StackRox Scanner Indexer, also // known as Scanner V4 Indexer. type v4Client struct { @@ -103,57 +90,6 @@ func (i *ImageAnalysis) GetIndexerVersion() string { return "" } -// getScannerEndpoint reads and validate the Scanner gRPC endpoint setting. If -// the endpoint is empty or not configured properly (invalid) the value is -// returned and error will be set. -func getScannerEndpoint(s env.Setting) (string, error) { - e := s.Setting() - if e == "" { - return e, errors.Errorf("%s is not set or empty", s.EnvVar()) - } - e = strings.TrimPrefix(e, "https://") - if strings.Contains(e, "://") { - return e, errors.Errorf("%s has unsupported scheme: %s", s.EnvVar(), e) - } - return e, nil -} - -// dial the scanner and returns a new ScannerClient. The function is non-blocking and -// returns a non-nil error upon configuration error. -func dial(endpoint string, certID mtls.Subject) (*grpc.ClientConn, error) { - tlsConfig, err := clientconn.TLSConfig(certID, clientconn.TLSConfigOptions{ - UseClientCert: clientconn.MustUseClientCert, - }) - if err != nil { - return nil, fmt.Errorf("TLS config failed: %w", err) - } - // This is non-blocking. If we ever want this to block, then add the - // grpc.WithBlock() DialOption. - log.Infof("dialing scanner at %s", endpoint) - conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) - if err != nil { - return nil, fmt.Errorf("grpc dial failed: %w", err) - } - return conn, nil -} - -// dialV2 connect to scanner V1 gRPC and return a new ScannerClient. -func dialV2() (ScannerClient, error) { - endpoint, err := getScannerEndpoint(env.ScannerSlimGRPCEndpoint) - if err != nil { - return nil, err - } - log.Infof("dialing scanner-v2 client: %s", endpoint) - conn, err := dial(endpoint, mtls.ScannerSubject) - if err != nil { - return nil, err - } - return &v2Client{ - ImageScanServiceClient: scannerV1.NewImageScanServiceClient(conn), - conn: conn, - }, nil -} - // dialV4 connect to scanner V4 gRPC and return a new ScannerClient. func dialV4() (ScannerClient, error) { ctx := context.Background() @@ -172,45 +108,6 @@ func dialV4() (ScannerClient, error) { return &v4Client{client: c}, nil } -// GetImageAnalysis retrieves the image analysis results for the given image. -func (c *v2Client) GetImageAnalysis(ctx context.Context, image *storage.Image, cfg *types.Config) (*ImageAnalysis, error) { - imgName := image.GetName().GetFullName() - - // The WaitForReady option will cause invocations to block (until server ready or - // ctx done/expires) This was added so that on fresh installation of sensor when - // scanner is not ready yet, local scans will not all fail and have to wait for - // next reprocess to succeed - resp, err := c.GetImageComponents(ctx, &scannerV1.GetImageComponentsRequest{ - Image: utils.GetFullyQualifiedFullName(image), - Registry: &scannerV1.RegistryData{ - Url: cfg.URL, - Username: cfg.Username, - Password: cfg.Password, - Insecure: cfg.Insecure, - }, - }, grpc.WaitForReady(true)) - if err != nil { - log.Debugf("Unable to get image components from local Scanner for image %s: %v", imgName, err) - return nil, errors.Wrap(err, "getting image components from scanner") - } - - log.Debugf("Received image components from local Scanner for image: %q", imgName) - - return &ImageAnalysis{ - ScanStatus: resp.GetStatus(), - ScanNotes: resp.GetNotes(), - V1Components: resp.GetComponents(), - }, nil -} - -// Close closes and cleanup the client connection. -func (c *v2Client) Close() error { - if err := c.conn.Close(); err != nil { - return errors.Wrap(err, "closing v2 scanner gRPC connection") - } - return nil -} - func convertIndexReportToAnalysis(ir *v4.IndexReport, indexerVersion string) *ImageAnalysis { var st scannerV1.ScanStatus switch ir.GetState() { diff --git a/sensor/common/scannerclient/singleton.go b/sensor/common/scannerclient/singleton.go index a1851e02c7187..1b38b06b2cd20 100644 --- a/sensor/common/scannerclient/singleton.go +++ b/sensor/common/scannerclient/singleton.go @@ -12,12 +12,14 @@ import ( var ( scannerClient ScannerClient scannerClientMutex sync.Mutex + loggedNoCentralV4 bool isScannerV4Enabled = features.ScannerV4.Enabled() ) // GRPCClientSingleton returns a gRPC ScannerClient to a local Scanner. -// Only one ScannerClient per Sensor is required. +// Only one ScannerClient per Sensor is required. A nil client may +// be returned. func GRPCClientSingleton() ScannerClient { scannerClientMutex.Lock() defer scannerClientMutex.Unlock() @@ -31,14 +33,27 @@ func GRPCClientSingleton() ScannerClient { env.LocalImageScanningEnabled.EnvVar()) return nil } - var err error - if isScannerV4Enabled && centralcaps.Has(centralsensor.ScannerV4Supported) { - log.Info("Creating Scanner V4 client") - scannerClient, err = dialV4() - } else { - log.Info("Creating Scanner V2 client") - scannerClient, err = dialV2() + + // Scanner V4 is the only local scanner Sensor connects to. Using it requires + // both Scanner V4 to be installed locally and Central to advertise support + // for Scanner V4. + // When either is missing there is no local scanner to talk to, so we return a nil + // client. + if !isScannerV4Enabled { + log.Warn("Local image scanning is enabled but Scanner V4 is disabled in Sensor; no local scanner will be used") + return nil } + if !centralcaps.Has(centralsensor.ScannerV4Supported) { + if !loggedNoCentralV4 { + log.Info("Local image scanning is enabled but Central does not support Scanner V4; no local scanner will be used") + loggedNoCentralV4 = true + } + return nil + } + + log.Info("Creating Scanner V4 client") + var err error + scannerClient, err = dialV4() utils.Should(err) return scannerClient @@ -50,6 +65,7 @@ func resetGRPCClient() { scannerClientMutex.Lock() defer scannerClientMutex.Unlock() + loggedNoCentralV4 = false if scannerClient == nil { return } diff --git a/sensor/common/scannerclient/singleton_test.go b/sensor/common/scannerclient/singleton_test.go new file mode 100644 index 0000000000000..cec1350033c66 --- /dev/null +++ b/sensor/common/scannerclient/singleton_test.go @@ -0,0 +1,90 @@ +package scannerclient + +import ( + "strconv" + "testing" + + "github.com/stackrox/rox/pkg/centralsensor" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/logging" + "github.com/stackrox/rox/sensor/common/centralcaps" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type infoCountingLogger struct { + logging.Logger + infoCalls int +} + +func (l *infoCountingLogger) Info(_ ...interface{}) { + l.infoCalls++ +} + +func TestGRPCClientSingleton(t *testing.T) { + tests := map[string]struct { + localImageScanningEnabled bool + scannerV4Enabled bool + centralCaps []centralsensor.CentralCapability + }{ + "local image scanning disabled": { + localImageScanningEnabled: false, + scannerV4Enabled: true, + centralCaps: []centralsensor.CentralCapability{centralsensor.ScannerV4Supported}, + }, + "scanner v4 disabled in sensor": { + localImageScanningEnabled: true, + scannerV4Enabled: false, + centralCaps: []centralsensor.CentralCapability{centralsensor.ScannerV4Supported}, + }, + "central does not support scanner v4": { + localImageScanningEnabled: true, + scannerV4Enabled: true, + centralCaps: nil, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + t.Setenv(env.LocalImageScanningEnabled.EnvVar(), strconv.FormatBool(tt.localImageScanningEnabled)) + + origScannerV4Enabled := isScannerV4Enabled + isScannerV4Enabled = tt.scannerV4Enabled + t.Cleanup(func() { isScannerV4Enabled = origScannerV4Enabled }) + + centralcaps.Set(tt.centralCaps) + t.Cleanup(func() { centralcaps.Set(nil) }) + + scannerClient = nil + t.Cleanup(func() { scannerClient = nil }) + + assert.Nil(t, GRPCClientSingleton()) + }) + } +} + +func TestGRPCClientSingletonLogsMissingCentralCapabilityOncePerReset(t *testing.T) { + t.Setenv(env.LocalImageScanningEnabled.EnvVar(), "true") + centralcaps.Set(nil) + t.Cleanup(func() { centralcaps.Set(nil) }) + + origScannerV4Enabled := isScannerV4Enabled + isScannerV4Enabled = true + t.Cleanup(func() { isScannerV4Enabled = origScannerV4Enabled }) + + resetGRPCClient() + t.Cleanup(resetGRPCClient) + + origLog := log + countingLog := &infoCountingLogger{Logger: origLog} + log = countingLog + t.Cleanup(func() { log = origLog }) + + assert.Nil(t, GRPCClientSingleton()) + assert.Nil(t, GRPCClientSingleton()) + require.Equal(t, 1, countingLog.infoCalls) + + resetGRPCClient() // The client is still nil, but the log should be enabled again. + assert.Nil(t, GRPCClientSingleton()) + assert.Equal(t, 2, countingLog.infoCalls) +} From 91bc09fb5e55e38f0749e7609304e264a9e1b02c Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:14:24 -0500 Subject: [PATCH 419/638] ROX-32329: Report unhealthy scanner (#22862) --- pkg/clusterhealth/status.go | 10 +++ pkg/clusterhealth/status_test.go | 85 +++++++++++++++++++ sensor/kubernetes/clusterhealth/updater.go | 35 +++++--- .../kubernetes/clusterhealth/updater_test.go | 78 +++++++++++++++++ 4 files changed, 194 insertions(+), 14 deletions(-) diff --git a/pkg/clusterhealth/status.go b/pkg/clusterhealth/status.go index 1d451ca4aaf36..a5fe930db215d 100644 --- a/pkg/clusterhealth/status.go +++ b/pkg/clusterhealth/status.go @@ -121,6 +121,16 @@ func PopulateLocalScannerStatus(localScannerHealthInfo *storage.ScannerHealthInf return storage.ClusterHealthStatus_UNINITIALIZED } + // A non-nil ScannerHealthInfo means local image scanning is enabled on the secured + // cluster. If Sensor could not gather scanner health it reports the reason(s) via + // StatusErrors and leaves the pod counts unset, e.g. a misconfiguration (local scanning + // enabled but Scanner V4 is not) or a missing scanner deployment. Reporting UNHEALTHY + // distinguishes this "enabled but broken" state from the "not installed" case above and + // surfaces the problem to the user. + if len(localScannerHealthInfo.GetStatusErrors()) > 0 { + return storage.ClusterHealthStatus_UNHEALTHY + } + desiredPods := localScannerHealthInfo.GetTotalDesiredAnalyzerPods() readyPods := localScannerHealthInfo.GetTotalReadyAnalyzerPods() diff --git a/pkg/clusterhealth/status_test.go b/pkg/clusterhealth/status_test.go index 440e0537497b9..4f6228e4cc3bb 100644 --- a/pkg/clusterhealth/status_test.go +++ b/pkg/clusterhealth/status_test.go @@ -270,3 +270,88 @@ func TestOverallHealth(t *testing.T) { }) } } + +func TestPopulateLocalScannerStatus(t *testing.T) { + cases := map[string]struct { + scannerHealthInfo *storage.ScannerHealthInfo + expected storage.ClusterHealthStatus_HealthStatusLabel + }{ + "nil: local scanning not enabled (optional component)": { + scannerHealthInfo: nil, + expected: storage.ClusterHealthStatus_UNINITIALIZED, + }, + "status errors: enabled but misconfigured (no scanner v4)": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + StatusErrors: []string{"local image scanning is enabled but Scanner V4 is not enabled"}, + }, + expected: storage.ClusterHealthStatus_UNHEALTHY, + }, + "status errors take precedence over pod counts": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + StatusErrors: []string{"unable to find scanner deployment"}, + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(3), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(3), + TotalReadyDbPodsOpt: dbPodsReady(1), + }, + expected: storage.ClusterHealthStatus_UNHEALTHY, + }, + "no pod counts, no errors: uninitialized": { + scannerHealthInfo: &storage.ScannerHealthInfo{}, + expected: storage.ClusterHealthStatus_UNINITIALIZED, + }, + "desired analyzer pods zero: uninitialized": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(0), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(0), + }, + expected: storage.ClusterHealthStatus_UNINITIALIZED, + }, + "no ready db pods: unhealthy": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(3), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(3), + TotalReadyDbPodsOpt: dbPodsReady(0), + }, + expected: storage.ClusterHealthStatus_UNHEALTHY, + }, + "all analyzer and db pods ready: healthy": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(3), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(3), + TotalReadyDbPodsOpt: dbPodsReady(1), + }, + expected: storage.ClusterHealthStatus_HEALTHY, + }, + "some analyzer pods not ready: degraded": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(3), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(2), + TotalReadyDbPodsOpt: dbPodsReady(1), + }, + expected: storage.ClusterHealthStatus_DEGRADED, + }, + "most analyzer pods not ready: unhealthy": { + scannerHealthInfo: &storage.ScannerHealthInfo{ + TotalDesiredAnalyzerPodsOpt: analyzerPodsDesired(3), + TotalReadyAnalyzerPodsOpt: analyzerPodsReady(1), + TotalReadyDbPodsOpt: dbPodsReady(1), + }, + expected: storage.ClusterHealthStatus_UNHEALTHY, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, c.expected, PopulateLocalScannerStatus(c.scannerHealthInfo)) + }) + } +} + +func analyzerPodsDesired(num int32) *storage.ScannerHealthInfo_TotalDesiredAnalyzerPods { + return &storage.ScannerHealthInfo_TotalDesiredAnalyzerPods{TotalDesiredAnalyzerPods: num} +} +func analyzerPodsReady(num int32) *storage.ScannerHealthInfo_TotalReadyAnalyzerPods { + return &storage.ScannerHealthInfo_TotalReadyAnalyzerPods{TotalReadyAnalyzerPods: num} +} +func dbPodsReady(num int32) *storage.ScannerHealthInfo_TotalReadyDbPods { + return &storage.ScannerHealthInfo_TotalReadyDbPods{TotalReadyDbPods: num} +} diff --git a/sensor/kubernetes/clusterhealth/updater.go b/sensor/kubernetes/clusterhealth/updater.go index 3b206f7f82869..c902553822d82 100644 --- a/sensor/kubernetes/clusterhealth/updater.go +++ b/sensor/kubernetes/clusterhealth/updater.go @@ -33,9 +33,6 @@ const ( admissionControlDeploymentName = "admission-control" admissionControlContainerName = "admission-control" - localScannerDeploymentName = "scanner" - localScannerDBDeploymentName = "scanner-db" - localScannerV4IndexerDeploymentName = "scanner-v4-indexer" localScannerV4DBDeploymentName = "scanner-v4-db" ) @@ -205,19 +202,29 @@ func (u *updaterImpl) getLocalScannerInfo() *storage.ScannerHealthInfo { return nil } - // It's possible that both Scanner and Scanner V4 are installed in the secured cluster - // at the same time, but only one will be used by Sensor at any given time, therefore - // only report the health of the active scanner. - scannerV4Active := features.ScannerV4.Enabled() && centralcaps.Has(centralsensor.ScannerV4Supported) - - analyzerDeploymentName := localScannerDeploymentName - dbDeploymentName := localScannerDBDeploymentName - if scannerV4Active { - analyzerDeploymentName = localScannerV4IndexerDeploymentName - dbDeploymentName = localScannerV4DBDeploymentName + // Local image scanning requires Scanner V4; Scanner V2 is no longer supported. + // When Scanner V4 is not active, local scanning cannot function. Pod state is irrelevant + // in these cases. The two failure modes are reported distinctly: + // - Scanner V4 is not enabled on this secured cluster, or + // - Scanner V4 is enabled but Central does not advertise Scanner V4 support. + if !features.ScannerV4.Enabled() { + return &storage.ScannerHealthInfo{ + StatusErrors: []string{ + "local image scanning is enabled but Scanner V4 is not enabled in secured cluster; local image " + + "scanning requires Scanner V4", + }, + } + } + if !centralcaps.Has(centralsensor.ScannerV4Supported) { + return &storage.ScannerHealthInfo{ + StatusErrors: []string{ + "local image scanning and Scanner V4 are enabled in secured cluster, but Central is not reporting " + + "support for Scanner V4; enable Scanner V4 in Central to enable local image scanning", + }, + } } - result := u.getScannerHealthInfo(analyzerDeploymentName, dbDeploymentName) + result := u.getScannerHealthInfo(localScannerV4IndexerDeploymentName, localScannerV4DBDeploymentName) if len(result.GetStatusErrors()) > 0 { log.Errorf("Errors while getting local scanner info: %v", result.GetStatusErrors()) } diff --git a/sensor/kubernetes/clusterhealth/updater_test.go b/sensor/kubernetes/clusterhealth/updater_test.go index ff1c9e8d2f0b2..7f2ea8ea312fa 100644 --- a/sensor/kubernetes/clusterhealth/updater_test.go +++ b/sensor/kubernetes/clusterhealth/updater_test.go @@ -9,8 +9,11 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/centralsensor" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/namespaces" "github.com/stackrox/rox/sensor/common" + "github.com/stackrox/rox/sensor/common/centralcaps" "github.com/stackrox/rox/sensor/common/message" "github.com/stretchr/testify/suite" appsV1 "k8s.io/api/apps/v1" @@ -290,6 +293,81 @@ func (s *UpdaterTestSuite) TestExpiredMessage() { } } +func (s *UpdaterTestSuite) TestLocalScannerDisabled() { + s.T().Setenv("ROX_LOCAL_IMAGE_SCANNING_ENABLED", "false") + + updater := s.createNewUpdater(updateInterval) + s.Nil(updater.getLocalScannerInfo(), "no scanner health info should be reported when local scanning is disabled") +} + +func (s *UpdaterTestSuite) TestLocalScannerV4NotSupportedByCentral() { + s.T().Setenv("ROX_LOCAL_IMAGE_SCANNING_ENABLED", "true") + s.T().Setenv(features.ScannerV4.EnvVar(), "true") + s.setCentralCaps() // Central does not advertise ScannerV4Supported. + + updater := s.createNewUpdater(updateInterval) + info := updater.getLocalScannerInfo() + + s.Require().NotNil(info) + // Pod counts must not be reported: the failure is a misconfiguration, not pod state. + s.Nil(info.GetTotalDesiredAnalyzerPodsOpt()) + s.Nil(info.GetTotalReadyAnalyzerPodsOpt()) + s.Require().Len(info.GetStatusErrors(), 1) + s.Contains(info.GetStatusErrors()[0], "Central is not reporting support for Scanner V4") +} + +func (s *UpdaterTestSuite) TestLocalScannerV4FeatureDisabled() { + s.T().Setenv("ROX_LOCAL_IMAGE_SCANNING_ENABLED", "true") + s.T().Setenv(features.ScannerV4.EnvVar(), "false") + s.setCentralCaps(centralsensor.ScannerV4Supported) + + updater := s.createNewUpdater(updateInterval) + info := updater.getLocalScannerInfo() + + s.Require().NotNil(info) + s.Nil(info.GetTotalDesiredAnalyzerPodsOpt()) + s.Require().Len(info.GetStatusErrors(), 1) + s.Contains(info.GetStatusErrors()[0], "Scanner V4 is not enabled") + // This case is distinct from Central lacking Scanner V4 support. + s.NotContains(info.GetStatusErrors()[0], "Central") +} + +func (s *UpdaterTestSuite) TestLocalScannerV4Healthy() { + s.T().Setenv("ROX_LOCAL_IMAGE_SCANNING_ENABLED", "true") + s.T().Setenv(features.ScannerV4.EnvVar(), "true") + s.setCentralCaps(centralsensor.ScannerV4Supported) + s.addDeployment(makeScannerDeployment("scanner-v4-indexer", 3, 3)) + s.addDeployment(makeScannerDeployment("scanner-v4-db", 1, 1)) + + updater := s.createNewUpdater(updateInterval) + info := updater.getLocalScannerInfo() + + s.Require().NotNil(info) + s.Empty(info.GetStatusErrors()) + s.Equal(int32(3), info.GetTotalDesiredAnalyzerPods()) + s.Equal(int32(3), info.GetTotalReadyAnalyzerPods()) + s.Equal(int32(1), info.GetTotalDesiredDbPods()) + s.Equal(int32(1), info.GetTotalReadyDbPods()) +} + +func (s *UpdaterTestSuite) setCentralCaps(caps ...centralsensor.CentralCapability) { + centralcaps.Set(caps) + s.T().Cleanup(func() { centralcaps.Set(nil) }) +} + +func makeScannerDeployment(name string, replicas, ready int32) appsV1.Deployment { + return appsV1.Deployment{ + ObjectMeta: metaV1.ObjectMeta{ + Name: name, + Namespace: "stackrox-mock-ns", + }, + Status: appsV1.DeploymentStatus{ + Replicas: replicas, + ReadyReplicas: ready, + }, + } +} + func (s *UpdaterTestSuite) createNewUpdater(interval time.Duration) *updaterImpl { updaterComponent := NewUpdater(s.client, interval) updater, ok := updaterComponent.(*updaterImpl) From 61b105b67c7abe13c399ed5869791aaeef5e1787 Mon Sep 17 00:00:00 2001 From: Johannes Malsam Date: Mon, 28 Sep 2026 10:36:30 +0200 Subject: [PATCH 420/638] ROX-35137: add pruning for orphaned deployments (#23030) --- CHANGELOG.md | 1 + .../deployment/datastore/datastore_impl.go | 27 +++--- .../datastore/datastore_impl_test.go | 47 ++++++++++ central/postgres/pruning.go | 34 +++++++ central/postgres/pruning_test.go | 62 +++++++++++++ central/pruning/pruning.go | 28 ++++++ central/pruning/pruning_test.go | 88 +++++++++++++++++++ 7 files changed, 273 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec9b661ee677..d7336e61d2a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Deprecated Features ### Technical Changes +- ROX-35137: Central now prunes deployments whose cluster no longer exists in the database. - ROX-37048: Central writes its version in `Rh-Central-Version` gRPC response header to authenticated clients. `roxctl` compares this version with its own version and reports a warning to stderr if the version skew is outside of a supported range. diff --git a/central/deployment/datastore/datastore_impl.go b/central/deployment/datastore/datastore_impl.go index 196a6af2b98fa..4ee891e512bfa 100644 --- a/central/deployment/datastore/datastore_impl.go +++ b/central/deployment/datastore/datastore_impl.go @@ -361,7 +361,8 @@ func (ds *datastoreImpl) RemoveDeployment(ctx context.Context, clusterID, id str return sac.ErrResourceAccessDenied } // Dedupe the removed deployments. This can happen because Pods have many completion states - // and we may receive multiple Remove calls + // and we may receive multiple Remove calls. Marking up front also stops concurrent UpsertFlows + // from writing flows for the deployment while it is being removed. if ds.deletedDeploymentCache != nil { if ds.deletedDeploymentCache.Contains(id) { return nil @@ -387,22 +388,20 @@ func (ds *datastoreImpl) RemoveDeployment(ctx context.Context, clusterID, id str errorList.AddError(err) } - flowStore, err := ds.networkFlows.GetFlowStore(deleteRelatedCtx, clusterID) - if err != nil { - errorList.AddError(err) - return errorList.ToError() - } - - if err := flowStore.RemoveFlowsForDeployment(deleteRelatedCtx, id); err != nil { - errorList.AddError(err) + // clusterID is nullable in the DB; guard against an empty value to avoid an error from flow store. + if clusterID != "" { + // A missing flow store must not block the delete, else deployments of a deleted cluster + // could never be pruned. + if flowStore, err := ds.networkFlows.GetFlowStore(deleteRelatedCtx, clusterID); err != nil { + errorList.AddError(err) + } else if err := flowStore.RemoveFlowsForDeployment(deleteRelatedCtx, id); err != nil { + errorList.AddError(err) + } } // Delete should be last to ensure that the above is always cleaned up even in the case of crash - err = ds.keyedMutex.DoStatusWithLock(id, func() error { - if err := ds.deploymentStore.Delete(ctx, id); err != nil { - return err - } - return nil + err := ds.keyedMutex.DoStatusWithLock(id, func() error { + return ds.deploymentStore.Delete(ctx, id) }) if err != nil { errorList.AddError(err) diff --git a/central/deployment/datastore/datastore_impl_test.go b/central/deployment/datastore/datastore_impl_test.go index 04c1d610f3f37..4440e5631294f 100644 --- a/central/deployment/datastore/datastore_impl_test.go +++ b/central/deployment/datastore/datastore_impl_test.go @@ -2,10 +2,13 @@ package datastore import ( "context" + "errors" "testing" storeMocks "github.com/stackrox/rox/central/deployment/datastore/internal/store/mocks" + flowMocks "github.com/stackrox/rox/central/networkgraph/flow/datastore/mocks" matcherMocks "github.com/stackrox/rox/central/platform/matcher/mocks" + baselineMocks "github.com/stackrox/rox/central/processbaseline/datastore/mocks" "github.com/stackrox/rox/central/ranking" riskMocks "github.com/stackrox/rox/central/risk/datastore/mocks" v1 "github.com/stackrox/rox/generated/api/v1" @@ -98,6 +101,50 @@ func (suite *DeploymentDataStoreTestSuite) TestInitializeRanker() { suite.Equal(int64(3), deploymentRanker.GetRankForID("3")) } +// Regression: a failed flow store lookup must not block the row delete, else +// deployments of an already-deleted cluster could never be pruned. +func (suite *DeploymentDataStoreTestSuite) TestRemoveDeploymentDeletesRowWhenFlowStoreLookupFails() { + baselines := baselineMocks.NewMockDataStore(suite.mockCtrl) + flows := flowMocks.NewMockClusterDataStore(suite.mockCtrl) + + ds := newDatastoreImpl(suite.storage, nil, nil, baselines, flows, suite.riskStore, nil, suite.filter, + ranking.NewRanker(), ranking.NewRanker(), ranking.NewRanker(), suite.matcher) + + const clusterID = "c1" + const deploymentID = "d1" + + suite.riskStore.EXPECT().RemoveRisk(gomock.Any(), deploymentID, gomock.Any()).Return(nil) + baselines.EXPECT().RemoveProcessBaselinesByDeployment(gomock.Any(), deploymentID).Return(nil) + flows.EXPECT().GetFlowStore(gomock.Any(), clusterID).Return(nil, errors.New("flow store gone")) + // The deployment row must still be deleted despite the flow-store failure. + suite.storage.EXPECT().Delete(gomock.Any(), deploymentID).Return(nil) + + err := ds.RemoveDeployment(suite.ctx, clusterID, deploymentID) + suite.Require().Error(err) + suite.ErrorContains(err, "flow store gone") +} + +// An empty clusterID (e.g. an orphaned deployment whose clusterid column is NULL) has no +// flow partition, so the flow store lookup must be skipped rather than dereferencing a nil +// store, while the row is still deleted. +func (suite *DeploymentDataStoreTestSuite) TestRemoveDeploymentSkipsFlowStoreWhenClusterIDEmpty() { + baselines := baselineMocks.NewMockDataStore(suite.mockCtrl) + flows := flowMocks.NewMockClusterDataStore(suite.mockCtrl) + + ds := newDatastoreImpl(suite.storage, nil, nil, baselines, flows, suite.riskStore, nil, suite.filter, + ranking.NewRanker(), ranking.NewRanker(), ranking.NewRanker(), suite.matcher) + + const deploymentID = "d1" + + suite.riskStore.EXPECT().RemoveRisk(gomock.Any(), deploymentID, gomock.Any()).Return(nil) + baselines.EXPECT().RemoveProcessBaselinesByDeployment(gomock.Any(), deploymentID).Return(nil) + // flows.GetFlowStore must not be called for an empty clusterID; the mock has no + // expectation set, so any call would fail the test. + suite.storage.EXPECT().Delete(gomock.Any(), deploymentID).Return(nil) + + suite.Require().NoError(ds.RemoveDeployment(suite.ctx, "", deploymentID)) +} + func walkMockFunc(deployments []*storage.Deployment) func(_ context.Context, _ *v1.Query, fn func(group *storage.Deployment) error) error { return func(_ context.Context, _ *v1.Query, fn func(deployment *storage.Deployment) error) error { for _, g := range deployments { diff --git a/central/postgres/pruning.go b/central/postgres/pruning.go index 14cdad7d9e2b3..2bb614e068468 100644 --- a/central/postgres/pruning.go +++ b/central/postgres/pruning.go @@ -29,6 +29,9 @@ const ( getAllOrphanedNodes = `SELECT id FROM nodes WHERE NOT EXISTS (SELECT 1 FROM clusters WHERE nodes.clusterid = clusters.Id)` + getAllOrphanedDeployments = `SELECT id, clusterid FROM deployments WHERE NOT EXISTS + (SELECT 1 FROM clusters WHERE deployments.clusterid = clusters.Id)` + getOrphanedProcessesByDeployment = `SELECT id FROM process_indicators pi WHERE NOT EXISTS (SELECT 1 FROM deployments WHERE pi.deploymentid = deployments.Id) AND (signal_time < now() AT time zone 'utc' - INTERVAL '%d MINUTES' OR signal_time IS NULL)` @@ -189,6 +192,37 @@ func GetOrphanedNodeIDs(ctx context.Context, pool postgres.DB) ([]string, error) }) } +// OrphanedDeployment holds a deployment id with its clusterid +type OrphanedDeployment struct { + ID string + ClusterID string +} + +// GetOrphanedDeploymentIDs returns the deployments that have a cluster that has been removed. +func GetOrphanedDeploymentIDs(ctx context.Context, pool postgres.DB) ([]OrphanedDeployment, error) { + return pgutils.Retry2(ctx, func() ([]OrphanedDeployment, error) { + ctx, cancel := context.WithTimeout(ctx, orphanedQueryTimeout) + defer cancel() + + rows, err := pool.Query(ctx, getAllOrphanedDeployments) + if err != nil { + return nil, errors.Wrap(err, "failed to get orphaned deployments") + } + return pgx.CollectRows(rows, func(r pgx.CollectableRow) (OrphanedDeployment, error) { + var d OrphanedDeployment + // clusterid is nullable; scan into a pointer so a NULL does not abort the pruning cycle. + var clusterID *string + if err := r.Scan(&d.ID, &clusterID); err != nil { + return OrphanedDeployment{}, errors.Wrap(err, "scanning orphaned deployment") + } + if clusterID != nil { + d.ClusterID = *clusterID + } + return d, nil + }) + }) +} + // GetOrphanedProcessIDsByDeployment returns the process ids that have a deployment that has been removed. func GetOrphanedProcessIDsByDeployment(ctx context.Context, pool postgres.DB, orphanWindow time.Duration) ([]string, error) { return pgutils.Retry2(ctx, func() ([]string, error) { diff --git a/central/postgres/pruning_test.go b/central/postgres/pruning_test.go index c788459a7588d..ade6dd5c22796 100644 --- a/central/postgres/pruning_test.go +++ b/central/postgres/pruning_test.go @@ -233,6 +233,68 @@ func (s *PostgresPruningSuite) TestGetOrphanedPodIDs() { s.Equal(len(idsToPrune), cluster2PodCount) } +func (s *PostgresPruningSuite) TestGetOrphanedDeploymentIDs() { + deploymentDS, err := deploymentStore.GetTestPostgresDataStore(s.T(), s.testDB.DB) + s.Require().NoError(err) + + clusterDS, err := clusterStore.GetTestPostgresDataStore(s.T(), s.testDB.DB) + s.Require().NoError(err) + + liveClusterID, err := clusterDS.AddCluster(s.ctx, &storage.Cluster{Name: "testOrphanDeploymentCluster", MainImage: "docker.io/stackrox/rox:latest"}) + s.Require().NoError(err) + + // Deployment tied to a live cluster - never orphaned. + liveDeploymentID := uuid.NewV4().String() + s.Require().NoError(deploymentDS.UpsertDeployment(s.ctx, &storage.Deployment{Id: liveDeploymentID, ClusterId: liveClusterID})) + + // Deployments whose clusterid has no matching cluster row - orphaned. This mirrors + // the field scenario where a cluster is hard-deleted but its deployments survive. + orphanClusterID := fixtureconsts.Cluster1 + orphanedIDs := set.NewStringSet() + for range 3 { + id := uuid.NewV4().String() + orphanedIDs.Add(id) + s.Require().NoError(deploymentDS.UpsertDeployment(s.ctx, &storage.Deployment{Id: id, ClusterId: orphanClusterID})) + } + + orphaned, err := GetOrphanedDeploymentIDs(s.ctx, s.testDB.DB) + s.Require().NoError(err) + + got := set.NewStringSet() + for _, d := range orphaned { + got.Add(d.ID) + s.Equal(orphanClusterID, d.ClusterID) + } + s.Equal(orphanedIDs, got) + s.NotContains(got.AsSlice(), liveDeploymentID) +} + +// A deployment with a NULL clusterid must not abort the sweep; it is orphaned (no matching +// cluster row) and must be returned with an empty ClusterID. +func (s *PostgresPruningSuite) TestGetOrphanedDeploymentIDsHandlesNullClusterID() { + deploymentDS, err := deploymentStore.GetTestPostgresDataStore(s.T(), s.testDB.DB) + s.Require().NoError(err) + + // Upsert with a non-matching cluster id, then null out the collumn, to verify + // the pruning works in the unlikely case deployments without clusterid made it to the DB + nullClusterDeploymentID := uuid.NewV4().String() + s.Require().NoError(deploymentDS.UpsertDeployment(s.ctx, &storage.Deployment{Id: nullClusterDeploymentID, ClusterId: fixtureconsts.Cluster1})) + _, err = s.testDB.DB.Exec(s.ctx, "UPDATE deployments SET clusterid = NULL WHERE id = $1", nullClusterDeploymentID) + s.Require().NoError(err) + + orphaned, err := GetOrphanedDeploymentIDs(s.ctx, s.testDB.DB) + s.Require().NoError(err) + + got := set.NewStringSet() + for _, d := range orphaned { + got.Add(d.ID) + if d.ID == nullClusterDeploymentID { + s.Empty(d.ClusterID) + } + } + s.Contains(got.AsSlice(), nullClusterDeploymentID) +} + func (s *PostgresPruningSuite) TestRemoveOrphanedProcesses() { cases := []struct { name string diff --git a/central/pruning/pruning.go b/central/pruning/pruning.go index 5673b35d77fff..dbae515c7df27 100644 --- a/central/pruning/pruning.go +++ b/central/pruning/pruning.go @@ -515,6 +515,30 @@ func (g *garbageCollectorImpl) removeOrphanedPods() { } } +// Remove deployments whose cluster has been deleted: fire-and-forget cluster-deletion cleanup can orphan them. No orphan window needed - cluster deletion is a hard delete that +// never re-adopts its deployments. +func (g *garbageCollectorImpl) removeOrphanedDeployments() { + defer metrics.SetPruningDuration(time.Now(), "Deployments") + deploymentsToRemove, err := postgres.GetOrphanedDeploymentIDs(pruningCtx, g.postgres) + if err != nil { + log.Errorf("Error finding orphaned deployments: %v", err) + return + } + + if len(deploymentsToRemove) == 0 { + log.Info("[Pruning] Found no orphaned deployments...") + return + } + log.Infof("[Pruning] Found %d orphaned deployments (from formerly deleted clusters). Deleting...", + len(deploymentsToRemove)) + + for _, d := range deploymentsToRemove { + if err := g.deployments.RemoveDeployment(pruningCtx, d.ClusterID, d.ID); err != nil { + log.Errorf("Failed to remove deployment with id %s: %v", d.ID, err) + } + } +} + // Remove nodes where the cluster has been deleted. func (g *garbageCollectorImpl) removeOrphanedNodes() { defer metrics.SetPruningDuration(time.Now(), "Nodes") @@ -588,6 +612,10 @@ func (g *garbageCollectorImpl) removeOrphanedResources() { } clusterIDSet := set.NewFrozenStringSet(clusterIDs...) + // Before the deploymentSet snapshot and alert/process/risk sweeps so they observe the + // deletions this cycle. Child rows (deployments_containers, etc.) cascade. + g.removeOrphanedDeployments() + deploymentIDs, err := g.deployments.GetDeploymentIDs(pruningCtx) if err != nil { log.Error(errors.Wrap(err, "unable to fetch deployment IDs in pruning")) diff --git a/central/pruning/pruning_test.go b/central/pruning/pruning_test.go index 6cd525a70115c..a61060837fb1b 100644 --- a/central/pruning/pruning_test.go +++ b/central/pruning/pruning_test.go @@ -2406,6 +2406,94 @@ func (s *PruningTestSuite) TestRemoveOrphanedPods() { s.Equal(updatedCount, podCount-cluster2PodCount) } +func (s *PruningTestSuite) TestRemoveOrphanedDeployments() { + ctrl := gomock.NewController(s.T()) + + mockRisk := riskDatastoreMocks.NewMockDataStore(ctrl) + mockRisk.EXPECT().RemoveRisk(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + mockBaseline := processBaselineDatastoreMocks.NewMockDataStore(ctrl) + mockBaseline.EXPECT().RemoveProcessBaselinesByDeployment(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + mockFilter := filterMocks.NewMockFilter(ctrl) + mockFilter.EXPECT().Delete(gomock.Any()).AnyTimes() + + flowStore := networkFlowDatastoreMocks.NewMockFlowDataStore(ctrl) + flowStore.EXPECT().RemoveFlowsForDeployment(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + flows := networkFlowDatastoreMocks.NewMockClusterDataStore(ctrl) + flows.EXPECT().GetFlowStore(gomock.Any(), gomock.Any()).AnyTimes().Return(flowStore, nil) + + deployments, err := deploymentDatastore.New(s.pool, nil, nil, mockBaseline, flows, mockRisk, nil, mockFilter, + ranking.NewRanker(), ranking.NewRanker(), ranking.NewRanker(), + platformmatcher.GetTestPlatformMatcherWithDefaultPlatformComponentConfig(ctrl)) + s.Require().NoError(err) + + _, _, clusterDS := s.generateClusterDataStructures() + liveClusterID, err := clusterDS.AddCluster(s.ctx, &storage.Cluster{Name: "testOrphanDeploymentCluster", MainImage: "docker.io/stackrox/rox:latest"}) + s.Require().NoError(err) + + // Deployment on a live (healthy) cluster - must survive the sweep. + liveDeployment := newDeployment("id1") + liveDeployment.Id = fixtureconsts.Deployment2 + liveDeployment.ClusterId = liveClusterID + s.Require().NoError(deployments.UpsertDeployment(s.ctx, liveDeployment)) + + // Clusters still in the DB but unhealthy/disconnected must keep their deployments - + // the sweep keys on the cluster row existing, not on health. + unhealthyClusters := []struct { + name string + deploymentID string + status storage.ClusterHealthStatus_HealthStatusLabel + }{ + {"testUnhealthyDeploymentCluster", fixtureconsts.Deployment3, storage.ClusterHealthStatus_UNHEALTHY}, + {"testDisconnectedDeploymentCluster", fixtureconsts.Deployment4, storage.ClusterHealthStatus_UNAVAILABLE}, + } + var unhealthyDeployments []*storage.Deployment + for _, c := range unhealthyClusters { + clusterID, err := clusterDS.AddCluster(s.ctx, &storage.Cluster{Name: c.name, MainImage: "docker.io/stackrox/rox:latest"}) + s.Require().NoError(err) + s.Require().NoError(clusterDS.UpdateClusterHealth(s.ctx, clusterID, + &storage.ClusterHealthStatus{SensorHealthStatus: c.status})) + + dep := newDeployment("img") + dep.Id = c.deploymentID + dep.ClusterId = clusterID + s.Require().NoError(deployments.UpsertDeployment(s.ctx, dep)) + unhealthyDeployments = append(unhealthyDeployments, dep) + } + + // Deployment whose clusterid has no cluster row - orphaned. + orphanedDeployment := newDeployment("id2") + orphanedDeployment.ClusterId = fixtureconsts.Cluster1 + s.Require().NoError(deployments.UpsertDeployment(s.ctx, orphanedDeployment)) + + gci := &garbageCollectorImpl{ + deployments: deployments, + postgres: s.pool, + } + + gci.removeOrphanedDeployments() + + _, exists, err := deployments.GetDeployment(s.ctx, orphanedDeployment.GetId()) + s.Require().NoError(err) + s.False(exists, "orphaned deployment should be pruned") + + _, exists, err = deployments.GetDeployment(s.ctx, liveDeployment.GetId()) + s.Require().NoError(err) + s.True(exists, "deployment of a live cluster must not be pruned") + + for _, dep := range unhealthyDeployments { + _, exists, err = deployments.GetDeployment(s.ctx, dep.GetId()) + s.Require().NoError(err) + s.True(exists, "deployment of a cluster still in the DB must not be pruned") + } + + // Cascaded deletion of deployments_containers rows is what makes the referenced + // image prunable - assert none remain. + var containerCount int + err = s.pool.QueryRow(s.ctx, "SELECT count(*) FROM deployments_containers WHERE deployments_id = $1", orphanedDeployment.GetId()).Scan(&containerCount) + s.Require().NoError(err) + s.Zero(containerCount) +} + func (s *PruningTestSuite) TestRemoveOrphanedNodes() { _, _, clusterDS := s.generateClusterDataStructures() From be5de333dd7f10bd4ab2ec7791d01adf641c55ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:41:09 +0200 Subject: [PATCH 421/638] chore(deps): bump github.com/go-jose/go-jose/v4 from 4.1.4 to 4.1.5 (#23093) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1833db41889be..b7ebe0893b68b 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/facebookincubator/nvdtools v0.1.5 github.com/fatih/color v1.19.0 github.com/georgysavva/scany/v2 v2.1.4 - github.com/go-jose/go-jose/v4 v4.1.4 + github.com/go-jose/go-jose/v4 v4.1.5 github.com/go-logr/logr v1.4.4 github.com/go-logr/zapr v1.3.0 github.com/go-viper/mapstructure/v2 v2.5.0 diff --git a/go.sum b/go.sum index 9ed09da42219f..89a6674db775b 100644 --- a/go.sum +++ b/go.sum @@ -501,8 +501,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= +github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= From b2009cc26fa47ccea75560ef5e33db30e61287ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:41:25 +0200 Subject: [PATCH 422/638] chore(deps): bump github.com/prometheus/client_model from 0.6.2 to 0.6.3 (#23095) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b7ebe0893b68b..7147aebfbf4a2 100644 --- a/go.mod +++ b/go.mod @@ -109,7 +109,7 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.24.1 - github.com/prometheus/client_model v0.6.2 + github.com/prometheus/client_model v0.6.3 github.com/prometheus/common v0.71.0 github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815 github.com/quay/claircore/toolkit v1.7.0 diff --git a/go.sum b/go.sum index 89a6674db775b..13a709a3439d5 100644 --- a/go.sum +++ b/go.sum @@ -1154,8 +1154,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= +github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= From a8957aedbaaa2d3d8c950f54fa07fd112e9659a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:41:30 +0200 Subject: [PATCH 423/638] chore(deps): bump the k8s-io group with 7 updates (#23090) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 44 ++++++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 7147aebfbf4a2..7269101c9b41d 100644 --- a/go.mod +++ b/go.mod @@ -168,13 +168,13 @@ require ( gorm.io/driver/postgres v1.6.2 gorm.io/gorm v1.31.2 helm.sh/helm/v3 v3.21.3 - k8s.io/api v0.36.4 - k8s.io/apiextensions-apiserver v0.36.4 - k8s.io/apimachinery v0.36.4 - k8s.io/apiserver v0.36.4 - k8s.io/cli-runtime v0.36.4 - k8s.io/client-go v0.36.4 - k8s.io/kubectl v0.36.4 + k8s.io/api v0.36.5 + k8s.io/apiextensions-apiserver v0.36.5 + k8s.io/apimachinery v0.36.5 + k8s.io/apiserver v0.36.5 + k8s.io/cli-runtime v0.36.5 + k8s.io/client-go v0.36.5 + k8s.io/kubectl v0.36.5 k8s.io/kubelet v0.32.13 k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 kubevirt.io/api v1.9.0 @@ -464,13 +464,13 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/code-generator v0.36.4 // indirect - k8s.io/component-base v0.36.4 // indirect - k8s.io/component-helpers v0.36.4 // indirect + k8s.io/code-generator v0.36.5 // indirect + k8s.io/component-base v0.36.5 // indirect + k8s.io/component-helpers v0.36.5 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.31.0 // indirect - k8s.io/streaming v0.36.4 // indirect + k8s.io/streaming v0.36.5 // indirect kubevirt.io/containerized-data-importer-api v1.64.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect modernc.org/libc v1.75.6 // indirect diff --git a/go.sum b/go.sum index 13a709a3439d5..15446cb92c4a8 100644 --- a/go.sum +++ b/go.sum @@ -2132,26 +2132,26 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= -k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= -k8s.io/apiextensions-apiserver v0.36.4 h1:SfvCVt+4CqKWvzuVytYDT5g9hyb9MztoiYELIkPVrFc= -k8s.io/apiextensions-apiserver v0.36.4/go.mod h1:JT9V2Ju7ys1FY4zbSpmX9XOvKB3/BwsODc4hFQEa+Xo= +k8s.io/api v0.36.5 h1:vtL/ByHmw7suLt+SGVMPZnuj8QdfZ0kN1vvMvHRCY9c= +k8s.io/api v0.36.5/go.mod h1:erfc3/3d6z30KO2Kf+BmOewE6CajqZTz1Hcd+FQqg60= +k8s.io/apiextensions-apiserver v0.36.5 h1:pZcOcB7ur1ou+VeQSGChj3cvhrL2nA3gERMUaCHVJNQ= +k8s.io/apiextensions-apiserver v0.36.5/go.mod h1:l1uFNtfzcEBqgrYEiOIYwu+6R1RCkuZIiEvfPE6RpNs= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= -k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= -k8s.io/apiserver v0.36.4 h1:AtKjaf2eUiX5G6TfF2IOlhuUuvMsHh49Ivr1+4fZ2gA= -k8s.io/apiserver v0.36.4/go.mod h1:RyiGghXP67hb0Ll+7iLJ6GGv2JpEzCn7ljbiA+L3cJ0= -k8s.io/cli-runtime v0.36.4 h1:OHvManCwP1k9GiC5tXRFxHhzZIQQFCsrHlt7OspKo3w= -k8s.io/cli-runtime v0.36.4/go.mod h1:qQSj2FJgQos6GHpS/ge7wTdQMZm9XFWlesWgV6h7qZY= -k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= -k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= +k8s.io/apimachinery v0.36.5 h1:X5Xbg6G4om0ma6LOcGbmUmo+B5Nq7mmIpAWhk+tgCh8= +k8s.io/apimachinery v0.36.5/go.mod h1:oPTSicSaDHDdoGwOs5DFPo6YS2wdTnzFHIYMEglfJ7I= +k8s.io/apiserver v0.36.5 h1:KX599gn2a5fQXeoCrDNdpND+AJ9ymq3u4XXvAslwnlo= +k8s.io/apiserver v0.36.5/go.mod h1:wxPt+sgzaPkk+WOgsrwsGB5eyRFXlRuPd2EoZSvh5k8= +k8s.io/cli-runtime v0.36.5 h1:TwL178KVy8bRTe6h2nfvSwkcn/B2nDZpOqYqE8e+Eg0= +k8s.io/cli-runtime v0.36.5/go.mod h1:cPBZUIJIH8ElAFPy+5v2ypmhQOlzyNS36Sm+K3fr+/I= +k8s.io/client-go v0.36.5 h1:D79FgevPon0NIBcjUHMy+QnALtihLGTBqOvIGCgoZmw= +k8s.io/client-go v0.36.5/go.mod h1:JAGQ3N4Z8E3H0W0Nm6NBg1EvB9+/wtEoBPDe8XGkHsI= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/code-generator v0.36.4 h1:KNW2Alv5LVoGiooEYh1GY982CGyg53u1inHu6Zi4bIA= -k8s.io/code-generator v0.36.4/go.mod h1:JmRrlYwTOn2glEuGOfNnlXTGbz3FfMg7t7Dq/8lHcc4= -k8s.io/component-base v0.36.4 h1:tz75yC2xgq3kd7vPdBtR8do5iMx0OHf6Zd1kuaxDB84= -k8s.io/component-base v0.36.4/go.mod h1:DCwb306U8ou89NNAp45Csuy8ok+1rp1ELDVPhzN5AWc= -k8s.io/component-helpers v0.36.4 h1:B5+SnptAlwTVJO0MW1JLsZVp4KevlADRg0CX5YFzjbU= -k8s.io/component-helpers v0.36.4/go.mod h1:RECUDkRdVuxcNOAl9/EK7lmy1TIjoWkBHs6f0uOyoCs= +k8s.io/code-generator v0.36.5 h1:fpPI4R6mXMx9kWZnSq7MAlaeKS+T4dga9NYSqVJi3Pg= +k8s.io/code-generator v0.36.5/go.mod h1:hcPwUWz7zAAEfME+HgcvQePfBZmY/B/NMQUV7Rw3yIk= +k8s.io/component-base v0.36.5 h1:4lvIJ/QZDOchuIRqIna6JqjqO2MoDsH7JdaQ8FJtLVw= +k8s.io/component-base v0.36.5/go.mod h1:ZCmi+txXm31F2uYY5p9FwQsww0g5BM0pI/gNsH9uAMY= +k8s.io/component-helpers v0.36.5 h1:ZRChXMmBeKd0U34CgYHgQiQLzV0O971DTR3JSMUpgRU= +k8s.io/component-helpers v0.36.5/go.mod h1:cSPYaE5E3h0f8kHMCNYX7h9+XBWUNZ7Qt0VPzepw8z0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= @@ -2167,12 +2167,12 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.4 h1:xZd9g1bFBd7hpb1oKjK8lT9jRL18dtgr4DAPQG1Oksk= -k8s.io/kubectl v0.36.4/go.mod h1:STWlr78cdEa1hHpr55wpcboaqchvfDueKRNDa1zOd1w= +k8s.io/kubectl v0.36.5 h1:466PfDKAiTUbgQSmnjYlashGcMn1zZ0IsOVo7oK1CIk= +k8s.io/kubectl v0.36.5/go.mod h1:nBQ7q6lEydoSESFe9DdQD9r6FS/k0/78C8hp1sC44zw= k8s.io/kubelet v0.32.13 h1:pGSrLTytcmuIlq4yvuRFnF4RdQjQh1FfsmPeRZXDKTo= k8s.io/kubelet v0.32.13/go.mod h1:XrwKgyKhVUE8TO/w9Lqq93UTM5W6t7ePItIgjvSmV/4= -k8s.io/streaming v0.36.4 h1:RS5YlhrdBN2pKGVjgygGntdu6SNdsduyjGWGe3cX0vo= -k8s.io/streaming v0.36.4/go.mod h1:tJ6S2bZa2HxIBauguBbCWSCYyd93Grfz1+z3tcOvlDE= +k8s.io/streaming v0.36.5 h1:gcY9VwgT4J/idqwS9ZrzJtH8ouHvZuFEyDWofcaL+F4= +k8s.io/streaming v0.36.5/go.mod h1:tJ6S2bZa2HxIBauguBbCWSCYyd93Grfz1+z3tcOvlDE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= From e7e4c2fa489469415537b4a3b075b3e4bacb3c62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:41:38 +0200 Subject: [PATCH 424/638] chore(deps): bump the aws-sdk-go-v2 group with 9 updates (#23092) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 36 ++++++++++++++--------------- go.sum | 72 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 7269101c9b41d..49fa33efacce6 100644 --- a/go.mod +++ b/go.mod @@ -25,15 +25,15 @@ require ( github.com/VividCortex/ewma v1.2.0 github.com/adhocore/gronx v1.20.3 github.com/andygrunwald/go-jira v1.17.0 - github.com/aws/aws-sdk-go-v2 v1.47.0 - github.com/aws/aws-sdk-go-v2/config v1.33.5 - github.com/aws/aws-sdk-go-v2/credentials v1.20.5 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8 - github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2 - github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 + github.com/aws/aws-sdk-go-v2 v1.47.1 + github.com/aws/aws-sdk-go-v2/config v1.33.6 + github.com/aws/aws-sdk-go-v2/credentials v1.20.6 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10 + github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4 + github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 github.com/aws/smithy-go v1.28.2 github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/cenkalti/backoff/v4 v4.3.0 @@ -217,16 +217,16 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beevik/etree v1.7.0 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 15446cb92c4a8..0ee403d774643 100644 --- a/go.sum +++ b/go.sum @@ -213,48 +213,48 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.47.0 h1:0jsHallhJCeaU0Ko48c/3FK1ctOQ7NpzggxriJOQ8MQ= -github.com/aws/aws-sdk-go-v2 v1.47.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= +github.com/aws/aws-sdk-go-v2 v1.47.1 h1:uOIZnp4PK3ZhKI0dNrJrhTEsLxbpXHTAJlwoS1pvAtw= +github.com/aws/aws-sdk-go-v2 v1.47.1/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8= -github.com/aws/aws-sdk-go-v2/config v1.33.5 h1:UA1dmokBFOLFoOyVBhO6HjM6edy0MIk5AZSkJVcksQw= -github.com/aws/aws-sdk-go-v2/config v1.33.5/go.mod h1:Dop8axzz0xx38GExIYWXdeyc8QQ7Cr+nPsxpD/LYy4U= -github.com/aws/aws-sdk-go-v2/credentials v1.20.5 h1:wklUVvHMc9xTQ3rcp49/ISpiMnhbCicJcA6n6S8m7J8= -github.com/aws/aws-sdk-go-v2/credentials v1.20.5/go.mod h1:fyEdrn6ccLFOkoK84j5bQyGTxp9zPt5l2XMhxf4DVZs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 h1:AM4hHjww+PSFtt6E+UrBrPlZkWsePCLEt9AjkfQX+yM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0/go.mod h1:3x/yXezeQjpOvBb4jEMxrS8SXvpdvJ5abv6l5c1gWM8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8 h1:U3VOx/+sECXsoZYT9/mUntdGQNaWHDpJk/Tp6SCfOGk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.8/go.mod h1:QVOKByJxYvyW5jiGDh36l4dnTEq+mCZrsenNyrhbs7Q= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 h1:Hp/VgjP0BysR3OgLlR057Vz2LcbbVnoWeJ+3qWiS/fY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3/go.mod h1:nwGV5qw7F1IZPgxCvA/ph8N2TAuz+BkRG/bXn808qMA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 h1:MUaM4f+kj1ZIBPZfUS8cxP1GKXXZtHJjAthy93AN7SM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3/go.mod h1:6YmVmEVRI5ZZzRjCSsb9SryKH0hAlMRdgA7kG9aDvBU= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 h1:fuSCw4Z2qfRCztMPO3GXJNSiEp6Wee+WOLwrHHUMy9c= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3/go.mod h1:6SxcHheD1pPR5+kWm1wGvjlL/YqUsh267sAfEmN4K7A= -github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0 h1:9i19IigAYxnAxTUQcsxkiIXeeytHllTOetWBhr1DTQs= -github.com/aws/aws-sdk-go-v2/service/ecr v1.66.0/go.mod h1:iSlv4VibruxMhWfiKrNbn97Yjhe2855EFrgUitSESfM= +github.com/aws/aws-sdk-go-v2/config v1.33.6 h1:MBjkSTLczek/UgiK+EYPIoRTqE7gP8vtW3OFbFo7Nug= +github.com/aws/aws-sdk-go-v2/config v1.33.6/go.mod h1:grRAFzdAZJrwcbasJRg2MPvIrVjtlfXllHssN6+E1JE= +github.com/aws/aws-sdk-go-v2/credentials v1.20.6 h1:NpAFXCU7NzXNkdGK3zQTtsRJ+3v9tZQV0xcdRw8uBdw= +github.com/aws/aws-sdk-go-v2/credentials v1.20.6/go.mod h1:mcZCoiPnyMvP8VMNbygNX5lLqSlkYJIMPODylQMurOk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 h1:8gALAAmacnIXh+z6VkdDanv4/IkG5APdg4DZLDTmLog= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1/go.mod h1:Z7IJhJU+poOdJjUR2wpyY21ossQ1XS/R3Lk9Msq5kM4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10 h1:OYuXRtpSLUZA6TrtqfU42xi1zTS8uCpQlTode7VhDjE= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10/go.mod h1:rWXRqN139C+pJzsA88pZRee5NBB1FqcDIo7dG9NlX48= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 h1:CLq4+8UHCI+ZZYl/EuJxXovaIVN2xeeT8JV+dsApQ5E= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4/go.mod h1:Wv4q5sAM04xAMkoOedxLx2inVf6K5FdxYp+A61L+q/0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 h1:dD4MR81I7YkpEBRk6UP9rocC2QnT3qVuXwzlYTtfGEs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4/go.mod h1:EcXV1kAFd5XwSkDHlj94gnF3q5CkJyYiIJfH8N0VmrE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 h1:7Wo47d/xn/7KttCSBd8EGYeZ7ULRFRkUHr6vkZPBzVQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4/go.mod h1:tDB2IVC1xC3vX8o+6uRlzhTxP3g1b77CZXFX/oD2FnQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1 h1:H63vyEXid/tHpv/UlvQUyM1c2QK5WgQRB3MK5gnAo8A= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1/go.mod h1:WglfLchOYcHrYOwNV7jERuy0Xc+7jArLkEnQay93auY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3 h1:BHKCSX4QXERe8So8rbWqaM7owqOmDJxATXgJwGng22A= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.3/go.mod h1:GqWeeKfYfezihA2KfFL9l7ohEdZWe1tuFWh3GfyNSnE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 h1:bON1rJf67TSTDCKg816AAIE4xSTtoo9tl0XRkO72R+I= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3/go.mod h1:c5BBpjJcQXpfeq9iASyVKA3T6vX6B6LEXY4mL/gklDY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3 h1:L8vIOxylma91TcR96NFTEC07G3JDwSl+CvK2b+IODms= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.3/go.mod h1:fmPIZQzTExYuBNWFyi1P7IoDjvskgphXqK1yObMzusM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 h1:/TYsZXdA8UTa+WCtCYSAJIr1vwl0+eho6TUgJGwFFO8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5/go.mod h1:qPqp1Uwd/BqdhPufv6oem9j5J7HNsgc2V22dUiDPn+s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 h1:29SvnfGhXjTl8ONxFwbj2rs6lbhiFXD2CgFQmbT/bXY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4/go.mod h1:wm04I5DMuNVvZHFe/dHnUxincvNbbK7AiNBbYsQivek= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 h1:pPiWfgeNxqluKEph7hvU88kuGKBPOWzO+Dk9t2zqqNs= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4/go.mod h1:YlwGoIUDG/3kBQbdNOVs/xKZ9J01G8e/6D1mRBj9uTk= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0 h1:uB8ymkVosyourmGXCZHyWhJ4wuKA4xq3ii2dVMPtBZY= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0/go.mod h1:rK4RITSY/qJw3qVJ7p19fceOWuvrisqqOChFkX05n5I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2 h1:2qsrxKfGgJrHBp4udIsZZVJGTX5lySaY9cJbZRB4J2Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.2/go.mod h1:/uA+2Qj4jd5qBWagVC1AyzzDFXVK997E7U04w7Kw0wI= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0 h1:sccblHjf5Swf5xjlBTAUT0Jc0m/KZW0PQP511Cto83A= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.0/go.mod h1:39oJ1/fl7d10t0e9rtp/a+50tRLyDPSaccCZF3xXrcc= -github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 h1:ZD5qFpWcaOKdTuhBi431pIDkCgrMkMlMT6jlpSPoIRI= -github.com/aws/aws-sdk-go-v2/service/signin v1.10.0/go.mod h1:8Nuuf+tR346PjJ3MvZPh9pekbLiLQFWJhzMXfwy7alA= -github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 h1:JGeeBcMlhg1xtOXYpeCaTQBZObtXMPQCUqBcmr65NRA= -github.com/aws/aws-sdk-go-v2/service/sso v1.38.0/go.mod h1:XwteswG9EOMRFm73UT0t+MbTwyLxMrEXkU6e+v92Lzo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 h1:obhahQXDEdVEv8y5bTKXR30LVaxYe1kyYM0L7l2Iq+k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0/go.mod h1:6twZZ/aXHNy1vXUO8koUbp++MYzMASkOgEBdkbJYmO0= -github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 h1:Zpnqa6XtrNzXZnwbdCqHOXpXhMsa01ql/pcRQ1sb4hk= -github.com/aws/aws-sdk-go-v2/service/sts v1.51.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4 h1:n6kO3OlBvnDEksQpvBLbAldjHwGlu8kErvhHJkhlaRY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4/go.mod h1:9APRWGLFITKD+xzWSIyT9V7QV4bNlEuIieWlzXgGFlI= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1 h1:WwNVhW53/6HoJ+VoTOQ+DvO0fnGUa9k32FgKxEHyWhM= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1/go.mod h1:FpfFeYyrCUwWWbEz5BDgFz3H5QsdaIn45RwvcIyZhrc= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 h1:DzCCWLzcIRQ77F3DEUljud7bEjTgFOIKXP52NmVRyhU= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.1/go.mod h1:xpo/geVldu8payT375WekctUzopG/hBU7miiqItMUlw= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 h1:Umtl/0YZhng4xndfW3lKJrYYP7NLEjI6bGXVomwLcs0= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.1/go.mod h1:rRD/dnm7q0HYE/I5TMaPgkWyyUGLcwuxHLABsLnQ3e0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 h1:orIWdNiLgzrhu/11RcPPKO/SBzUUymbUQuZbSPImghg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1/go.mod h1:skwM/xsbR/1ReUTesv9BhpJp1VjajR7DWQnuVLwiXsQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 h1:0HOqZXRvMytH6bFHVIc0oJX07sZjfhz0zXtjs6gdE8s= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.1/go.mod h1:26zA0GhDrLo+yiLI2yXWxqB1PdsShfLikoI7GOEgugM= github.com/aws/smithy-go v1.28.2 h1:myhcykQcatTul2B/zITjDk203G7t0awUAs1hVry5Bvg= github.com/aws/smithy-go v1.28.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= From 10ba110632efb555041f5f2caa8f9f786c751cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:18:27 +0200 Subject: [PATCH 425/638] chore(deps): bump sigs.k8s.io/controller-tools from 0.21.0 to 0.22.0 in /operator/tools/controller-gen (#22730) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: StackRox PR Fixxxer Co-authored-by: Yann Brillouet --- .../config.stackrox.io_securitypolicies.yaml | 2 +- .../config.stackrox.io_securitypolicies.yaml | 2 +- .../config.stackrox.io_securitypolicies.yaml | 2 +- .../platform.stackrox.io_centrals.yaml | 2 +- .../platform.stackrox.io_securedclusters.yaml | 2 +- .../bases/platform.stackrox.io_centrals.yaml | 2 +- .../platform.stackrox.io_securedclusters.yaml | 2 +- operator/tools/controller-gen/go.mod | 64 +++-- operator/tools/controller-gen/go.sum | 256 +++++++++--------- 9 files changed, 160 insertions(+), 174 deletions(-) diff --git a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml index 6329c0e2b6373..61a83b1baa76d 100644 --- a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml +++ b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: securitypolicies.config.stackrox.io spec: group: config.stackrox.io diff --git a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml index 6329c0e2b6373..61a83b1baa76d 100644 --- a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml +++ b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: securitypolicies.config.stackrox.io spec: group: config.stackrox.io diff --git a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml index 51c227281ddf2..3b94d5f785b32 100644 --- a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml +++ b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 labels: app: rhacs-operator name: securitypolicies.config.stackrox.io diff --git a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml index 157f231ecac11..5789d721f9c43 100644 --- a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 labels: app: rhacs-operator name: centrals.platform.stackrox.io diff --git a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml index 42d024df618ec..91434ef91868e 100644 --- a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 labels: app: rhacs-operator name: securedclusters.platform.stackrox.io diff --git a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml index 4dd017d30ccea..6134c58800ff5 100644 --- a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: centrals.platform.stackrox.io spec: group: platform.stackrox.io diff --git a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml index e3f7245601a5e..30e63d97f9a99 100644 --- a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.21.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: securedclusters.platform.stackrox.io spec: group: platform.stackrox.io diff --git a/operator/tools/controller-gen/go.mod b/operator/tools/controller-gen/go.mod index 5acc2fc959bab..1896df6710f05 100644 --- a/operator/tools/controller-gen/go.mod +++ b/operator/tools/controller-gen/go.mod @@ -2,31 +2,31 @@ module github.com/stackrox/rox/operator/tools/controller-gen go 1.26.3 -require sigs.k8s.io/controller-tools v0.21.0 +require sigs.k8s.io/controller-tools v0.22.0 require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.19.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/swag v0.26.0 // indirect - github.com/go-openapi/swag/cmdutils v0.26.0 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/fileutils v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/mangling v0.26.0 // indirect - github.com/go-openapi/swag/netutils v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -35,27 +35,25 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/tools v0.44.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.39.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.49.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.0 // indirect - k8s.io/apiextensions-apiserver v0.36.0 // indirect - k8s.io/apimachinery v0.36.0 // indirect - k8s.io/code-generator v0.36.0 // indirect - k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect + k8s.io/api v0.37.0 // indirect + k8s.io/apiextensions-apiserver v0.37.0 // indirect + k8s.io/apimachinery v0.37.0 // indirect + k8s.io/code-generator v0.37.0 // indirect + k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/controller-gen/go.sum b/operator/tools/controller-gen/go.sum index 88ed54599b220..74c70924ce78c 100644 --- a/operator/tools/controller-gen/go.sum +++ b/operator/tools/controller-gen/go.sum @@ -1,7 +1,9 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -11,7 +13,6 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -20,69 +21,67 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M= -github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.29.2 h1:ZtDxkeiMmz0mxbKDYiNkE5Lk7V5edMRcaaDf2jX002k= +github.com/google/cel-go v0.29.2/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -95,33 +94,27 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU= +github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -135,102 +128,97 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= +golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +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.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= -k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= -k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= -k8s.io/code-generator v0.36.0 h1:XWAkrhnArm0VWMmSFO7kyB+wE2LROwep7hEH0GPGkqA= -k8s.io/code-generator v0.36.0/go.mod h1:Tr2UhfBRdlyRoadfob9aPCmmGe8PUs5XPK9MEJ2nx+w= -k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= -k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= +k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= +k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= +k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= +k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +k8s.io/apiserver v0.37.0 h1:TXg7OxsOWrAH8J4Zi/gBAZuMw1Dfdd+6cca2h4qjRqo= +k8s.io/apiserver v0.37.0/go.mod h1:OddHDF4gy9qyIb8o/3+qaeP6S0vEObWLgOygVqXksv0= +k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= +k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= +k8s.io/code-generator v0.37.0 h1:AC915wukzlVHHODAQYxvQ25WKibPh95faJ2kxf8dzuo= +k8s.io/code-generator v0.37.0/go.mod h1:qg7E/uDlyvevVRL1V8+h2z9UWmi/8gxaRka/lVXUBdk= +k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM= +k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 h1:3L6PNkMLXkU/pz3jWzaaIUz0Rs2V9h+5O51AeRC7poc= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3/go.mod h1:yvyl3l9E+UxlqOMUULdKTAYB0rEhsmjr7+2Vb/1pCSo= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-tools v0.21.0 h1:KXDQza3bgjlPY6xLR63tI/40gzjhyUAvkCrwzd2/6cs= -sigs.k8s.io/controller-tools v0.21.0/go.mod h1:DLIypi3Q2+azVAP8jr/mHXJgveYYHFjhnNOUuBJ10JE= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0/go.mod h1:tJo1aepTXyR+8Xs3sUsGBDk4Ub2AM5dPAPKJx0mpm5c= +sigs.k8s.io/controller-tools v0.22.0 h1:eG3FAVja/KnlXKIWg95udIFz1cMyAtMjP11cqBh3t+k= +sigs.k8s.io/controller-tools v0.22.0/go.mod h1:VizwUStoZK7rReCj704czGGrB7mLxXTiJSJt7wN5ilI= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From f36f7ef5e2464745c0dd20f8c6830ade429ddb33 Mon Sep 17 00:00:00 2001 From: Lance Bragstad <94937504+rhmdnd@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:16:08 -0500 Subject: [PATCH 426/638] ROX-36278: fix(search): drop IMAGES_V2 from default global search set (#22989) Co-authored-by: Claude Opus 4.8 (1M context) --- central/search/category.go | 11 +++++++---- qa-tests-backend/src/test/groovy/GlobalSearch.groovy | 4 ---- qa-tests-backend/src/test/groovy/SACTest.groovy | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/central/search/category.go b/central/search/category.go index 97ee74cf890e4..418f0eb32a2ce 100644 --- a/central/search/category.go +++ b/central/search/category.go @@ -2,13 +2,19 @@ package search import ( v1 "github.com/stackrox/rox/generated/api/v1" - "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/set" ) // GetGlobalSearchCategories returns a set of search categories func GetGlobalSearchCategories() set.Set[v1.SearchCategory] { // globalSearchCategories is exposed for e2e options test + // + // IMAGES_V2 is intentionally omitted. With FlattenImageData enabled, IMAGES is + // already routed to the imagesV2 store (see central/search/service), so adding + // IMAGES_V2 here would run the same image search twice and return every image + // twice (both labeled IMAGES). IMAGES remains the public category used by the UI + // and API clients; IMAGES_V2 is the internal storage/schema category and is still + // searchable when requested explicitly. globalSearchCategories := set.NewSet( v1.SearchCategory_ALERTS, v1.SearchCategory_CLUSTERS, @@ -25,8 +31,5 @@ func GetGlobalSearchCategories() set.Set[v1.SearchCategory] { v1.SearchCategory_IMAGE_INTEGRATIONS, v1.SearchCategory_POLICY_CATEGORIES, ) - if features.FlattenImageData.Enabled() { - globalSearchCategories.Add(v1.SearchCategory_IMAGES_V2) - } return globalSearchCategories } diff --git a/qa-tests-backend/src/test/groovy/GlobalSearch.groovy b/qa-tests-backend/src/test/groovy/GlobalSearch.groovy index 04056f42a13f5..e4f563e85bf19 100644 --- a/qa-tests-backend/src/test/groovy/GlobalSearch.groovy +++ b/qa-tests-backend/src/test/groovy/GlobalSearch.groovy @@ -34,10 +34,6 @@ class GlobalSearch extends BaseSpecification { SearchServiceOuterClass.SearchCategory.NAMESPACES, SearchServiceOuterClass.SearchCategory.IMAGES, SearchServiceOuterClass.SearchCategory.DEPLOYMENTS) - if (flattenImageDataEnabled) { - EXPECTED_DEPLOYMENT_CATEGORIES.add(SearchServiceOuterClass.SearchCategory.IMAGES_V2) - EXPECTED_IMAGE_CATEGORIES.add(SearchServiceOuterClass.SearchCategory.IMAGES_V2) - } orchestrator.createDeployment(DEPLOYMENT) assert Services.waitForDeployment(DEPLOYMENT) diff --git a/qa-tests-backend/src/test/groovy/SACTest.groovy b/qa-tests-backend/src/test/groovy/SACTest.groovy index dd6a10fa6d83a..c9bd307501e2f 100644 --- a/qa-tests-backend/src/test/groovy/SACTest.groovy +++ b/qa-tests-backend/src/test/groovy/SACTest.groovy @@ -404,7 +404,7 @@ class SACTest extends BaseSpecification { tokenName | category | numResults NOACCESSTOKEN | "Cluster" | 0 "searchDeploymentsToken" | "Deployment" | 1 - "searchImagesToken" | "Image" | (flattenImageDataEnabled ? 2 : 1) + "searchImagesToken" | "Image" | 1 } @Unroll From 485693ff3725b5938d889e1c94e1b4180dd260bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:02:09 +0200 Subject: [PATCH 427/638] chore(deps): bump github.com/googleapis/gax-go/v2 from 2.24.1 to 2.26.0 (#23094) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 49fa33efacce6..6f16abd351e89 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/google/go-containerregistry v0.21.9 github.com/google/go-github/v91 v91.0.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.24.1 + github.com/googleapis/gax-go/v2 v2.26.0 github.com/gorilla/schema v1.4.1 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/pyroscope-go v1.4.2 diff --git a/go.sum b/go.sum index 0ee403d774643..08d7c0c89b8ff 100644 --- a/go.sum +++ b/go.sum @@ -741,8 +741,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0= -github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0= +github.com/googleapis/gax-go/v2 v2.26.0 h1:EQd25QY0qQk+S8OZA5wRflUckH5jD2Uw0zj2l23f/lc= +github.com/googleapis/gax-go/v2 v2.26.0/go.mod h1:sMKqnMesnKH+3wiRJROcttA+cJoZoGbZl1vDQ8XYtGk= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= From 3378f8316caf1c0293d6cc0fbd4a2b3270f45cc8 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 28 Sep 2026 09:51:57 -0400 Subject: [PATCH 428/638] ROX-37151: Delete ROX_CISA_KEV in ui (#22982) --- .../Components/CompoundSearchFilter/attributes/imageCVE.ts | 2 +- .../Policies/Wizard/Step3/policyCriteriaDescriptors.tsx | 1 - .../WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx | 1 - .../WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx | 1 - .../WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx | 1 - .../Containers/Vulnerabilities/components/CvePageHeader.tsx | 6 +----- ui/apps/platform/src/types/featureFlag.ts | 1 - 7 files changed, 2 insertions(+), 11 deletions(-) diff --git a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts index c8bb26a4920c4..b37c0e88a79ce 100644 --- a/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts +++ b/ui/apps/platform/src/Components/CompoundSearchFilter/attributes/imageCVE.ts @@ -51,7 +51,7 @@ export const KnownExploit: CompoundSearchFilterAttribute = { }, ], }, - featureFlagDependency: ['ROX_SCANNER_V4', 'ROX_CISA_KEV'], + featureFlagDependency: ['ROX_SCANNER_V4'], }; // The filter value is the VulnOrigin enum name (e.g. VULN_ORIGIN_RED_HAT), which the diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx index 3443e3bb474eb..336ca7fbe0bc6 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx +++ b/ui/apps/platform/src/Containers/Policies/Wizard/Step3/policyCriteriaDescriptors.tsx @@ -703,7 +703,6 @@ export const policyCriteriaDescriptors: Descriptor[] = [ defaultValue: true, canBooleanLogic: false, lifecycleStages: ['BUILD', 'DEPLOY', 'RUNTIME'], - featureFlagDependency: ['ROX_CISA_KEV'], }, { label: 'Fixed by', diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx index 04d2e2b39b3be..f1a03409d619b 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/DeploymentVulnerabilitiesTable.tsx @@ -202,7 +202,6 @@ function DeploymentVulnerabilitiesTable({ const labels: ReactNode[] = []; if ( isFeatureFlagEnabled('ROX_SCANNER_V4') && - isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { labels.push(); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx index 0ea0997869e6f..b4e68f0706a33 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/ImageVulnerabilitiesTable.tsx @@ -293,7 +293,6 @@ function ImageVulnerabilitiesTable({ const labels: ReactNode[] = []; if ( isFeatureFlagEnabled('ROX_SCANNER_V4') && - isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { labels.push(); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx index 2152811ad30f7..0446283adc510 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Tables/WorkloadCVEOverviewTable.tsx @@ -351,7 +351,6 @@ function WorkloadCVEOverviewTable({ const labels: ReactNode[] = []; if ( isFeatureFlagEnabled('ROX_SCANNER_V4') && - isFeatureFlagEnabled('ROX_CISA_KEV') && hasKnownExploit(cveBaseInfo?.exploit) ) { labels.push(); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/components/CvePageHeader.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/components/CvePageHeader.tsx index 90fb0b09986ae..6d5dc038a70de 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/components/CvePageHeader.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/components/CvePageHeader.tsx @@ -52,11 +52,7 @@ function CvePageHeader({ data }: CvePageHeaderProps) { const hasEpssProbabilityLabel = isEpssProbabilityColumnEnabled && Boolean(cveBaseInfo); // not (yet) for Node CVE const labels: ReactNode[] = []; - if ( - isFeatureFlagEnabled('ROX_SCANNER_V4') && - isFeatureFlagEnabled('ROX_CISA_KEV') && - hasKnownExploit(cveBaseInfo?.exploit) - ) { + if (isFeatureFlagEnabled('ROX_SCANNER_V4') && hasKnownExploit(cveBaseInfo?.exploit)) { labels.push(); if (hasKnownRansomwareCampaignUse(cveBaseInfo?.exploit)) { labels.push( diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index 4e9e1d3845522..ed63d06aee6b8 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -2,7 +2,6 @@ // However, add strings in alphabetical order to minimize merge conflicts when multiple people add or delete strings. // prettier-ignore export type FeatureFlagEnvVar = - | 'ROX_CISA_KEV' | 'ROX_CVE_FIX_TIMESTAMP' | 'ROX_DEPRECATED_COMPLIANCE_DASHBOARD' | 'ROX_EVALUATION_FILTER' From 24812d5cbcab78528ae5afdb08e8a5958717dc4d Mon Sep 17 00:00:00 2001 From: David Shrewsberry <99685630+dashrews78@users.noreply.github.com> Date: Mon, 28 Sep 2026 10:12:10 -0400 Subject: [PATCH 429/638] ROX-36944: Upgrade test reduced to versions +/= 3 (#22791) Co-authored-by: Mauro Ezequiel Moltrasio --- .github/workflows/e2e-gke-upgrade-tests.yaml | 25 ++++- tests/e2e/lib.sh | 8 +- tests/e2e/run.sh | 2 +- tests/upgrade/lib.sh | 37 +++++-- tests/upgrade/postgres_run.sh | 28 ++++-- tests/upgrade/postgres_upgrade_run.sh | 100 +++++++++++++------ 6 files changed, 144 insertions(+), 56 deletions(-) diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index 9d33fc971103d..40f394eb5c724 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -59,7 +59,8 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + # Leave room for the scale workload alongside both Scanner versions. + NUM_NODES: "4" GKE_SPOT: "false" timeout-minutes: 20 run: | @@ -180,9 +181,26 @@ jobs: gcp-account: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} directory: ${{ env.ARTIFACT_DIR }} + check-postgres-upgrade: + if: github.event_name == 'workflow_dispatch' || inputs.should-run + runs-on: ubuntu-latest + outputs: + required: ${{ steps.check.outputs.required }} + steps: + - name: Checkout repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Check whether PostgreSQL needs a major-version upgrade + id: check + run: tests/upgrade/postgres_upgrade_run.sh --check + # Verifies we can do Postgres major version upgrade. gke-upgrade-tests-postgres: - if: github.event_name == 'workflow_dispatch' || inputs.should-run + needs: check-postgres-upgrade + if: needs.check-postgres-upgrade.outputs.required == 'true' runs-on: ubuntu-latest env: MAIN_IMAGE_TAG: ${{ inputs.tag }} @@ -210,7 +228,8 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + # Leave room for the scale workload alongside both Scanner versions. + NUM_NODES: "4" GKE_SPOT: "false" timeout-minutes: 20 run: | diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index 3f074bec50f45..712fbab269e35 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -1812,8 +1812,8 @@ _record_build_info() { set_ci_shared_export "build" "${build_info}" } -restore_4_6_postgres_backup() { - info "Restoring a 4.6 postgres backup" +restore_postgres_backup() { + info "Restoring a postgres backup" require_environment "API_ENDPOINT" require_environment "ROX_ADMIN_PASSWORD" @@ -1823,10 +1823,10 @@ restore_4_6_postgres_backup() { if is_CI; then setup_gcp fi - gsutil cp gs://stackrox-ci-upgrade-test-fixtures/upgrade-test-dbs/postgres_db_4_6.sql.zip . + gsutil cp gs://stackrox-ci-upgrade-test-fixtures/upgrade-test-dbs/postgres_db_4.10.0.sql.zip . roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ - central db restore --timeout 5m postgres_db_4_6.sql.zip + central db restore --timeout 5m postgres_db_4.10.0.sql.zip } update_public_config() { diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index 04a2c1a505d55..7d53d1e42a9d7 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -94,7 +94,7 @@ test_e2e() { # Give some time for previous tests to finish up wait_for_api - restore_4_6_postgres_backup + restore_postgres_backup wait_for_api trap cleanup_workload_identities EXIT diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index cbe1d1ee0997c..5c0f21b0d70fb 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -108,7 +108,11 @@ get_target_bg_migration_seqnum() { deploy_earlier_postgres_central() { info "Deploying: $EARLIER_TAG..." - make cli + # Older checkouts force GOTOOLCHAIN=local during dependency checks. + # Put the toolchain selected for this checkout first on PATH for the build. + local build_goroot + build_goroot="$(go env GOROOT)" + PATH="${build_goroot}/bin:${PATH}" make cli PATH="bin/$TEST_HOST_PLATFORM:$PATH" command -v roxctl PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl version @@ -120,7 +124,20 @@ deploy_earlier_postgres_central() { export ROX_ADMIN_PASSWORD PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output central-services --image-defaults opensource --output-dir /tmp/early-stackrox-central-services-chart --remove + # Scanner V4 is installed by default in 4.10. Set its DB storage class now, + # because the later Helm upgrade cannot change an existing PVC's class. + local helm_extra_args=() + if [[ -n "${SCANNER_V4_DB_STORAGE_CLASS:-}" ]]; then + if [[ "${SCANNER_V4_DB_STORAGE_CLASS}" == "faster" ]]; then + kubectl apply -f "${TEST_ROOT}/deploy/common/ssd-storageclass.yaml" + fi + helm_extra_args+=(--set "scannerV4.db.persistence.persistentVolumeClaim.storageClass=${SCANNER_V4_DB_STORAGE_CLASS}") + fi + + # The generated chart can inherit the PR tag, which has no Scanner V2 images. + # Use the released Scanner V2 images for the initial deployment. helm install -n stackrox --create-namespace stackrox-central-services /tmp/early-stackrox-central-services-chart \ + "${helm_extra_args[@]}" \ --set central.adminPassword.value="${ROX_ADMIN_PASSWORD}" \ --set central.db.enabled=true \ --set central.db.persistence.persistentVolumeClaim.size="${PVC_SIZE:-100Gi}" \ @@ -128,7 +145,9 @@ deploy_earlier_postgres_central() { --set central.exposure.loadBalancer.enabled=true \ --set system.enablePodSecurityPolicies=false \ --set central.image.tag="${EARLIER_TAG}" \ - --set central.db.image.tag="${EARLIER_TAG}" + --set central.db.image.tag="${EARLIER_TAG}" \ + --set scanner.image.tag="${EARLIER_TAG}" \ + --set scanner.dbImage.tag="${EARLIER_TAG}" # Installing this way returns faster than the scripts but everything isn't running when it finishes like with # the scripts. So we will give it a minute for things to get started before we proceed @@ -249,31 +268,31 @@ EOT wait_for_scanner_V4 "$namespace" } -restore_4_6_backup() { - info "Restoring a 4.6 backup into a newer central" +restore_backup() { + info "Restoring a backup into a newer central" - restore_4_6_postgres_backup + restore_postgres_backup } force_rollback() { - info "Forcing a rollback to $FORCE_ROLLBACK_VERSION" + info "Forcing a rollback to ${EARLIER_TAG}" local upgradeStatus upgradeStatus=$(curl -sSk -X GET --config <(curl_cfg user "admin:${ROX_ADMIN_PASSWORD}") https://"${API_ENDPOINT}"/v1/centralhealth/upgradestatus) echo "upgrade status: ${upgradeStatus}" test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.version' -r)" "$(make --quiet --no-print-directory tag)" - test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.forceRollbackTo' -r)" "$FORCE_ROLLBACK_VERSION" + test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.forceRollbackTo' -r)" "${EARLIER_TAG}" test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.canRollbackAfterUpgrade' -r)" "true" kubectl -n stackrox get configmap/central-config -o yaml | yq e '{"data": .data}' - >/tmp/force_rollback_patch local central_config - central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"$FORCE_ROLLBACK_VERSION\"" -) + central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"${EARLIER_TAG}\"" -) local config_patch config_patch=$(yq e ".data[\"central-config.yaml\"] |= \"$central_config\"" /tmp/force_rollback_patch) echo "config patch: $config_patch" kubectl -n stackrox patch configmap/central-config -p "$config_patch" - kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:$FORCE_ROLLBACK_VERSION" + kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:${EARLIER_TAG}" } validate_sensor_bundle_via_upgrader() { diff --git a/tests/upgrade/postgres_run.sh b/tests/upgrade/postgres_run.sh index 8d954ee285d85..3e8236a2b0146 100755 --- a/tests/upgrade/postgres_run.sh +++ b/tests/upgrade/postgres_run.sh @@ -7,11 +7,11 @@ set -euo pipefail TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" -EARLIER_TAG="4.6.2" -EARLIER_SHA="ecff2a443c8b9a2dc7bf606162da89da81dd8e9e" +EARLIER_TAG="4.10.0" +EARLIER_SHA="7b817fa511ac4533cdf2d79a1b8e04d4d7557ad2" CURRENT_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" COLLECTOR_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory collector-tag)"}" -PREVIOUS_RELEASES=("4.6.10" "4.7.9" "4.8.11" "4.9.11" "4.10.7" "4.11.3") +PREVIOUS_RELEASES=("4.10.7" "4.11.3") # shellcheck source=../../scripts/lib.sh source "$TEST_ROOT/scripts/lib.sh" @@ -76,9 +76,6 @@ test_upgrade_paths() { local log_output_dir="$1" - # To test we remain backwards compatible rollback to 4.6.x - FORCE_ROLLBACK_VERSION="${EARLIER_TAG}" - cd "$REPO_FOR_TIME_TRAVEL" git checkout "$EARLIER_SHA" @@ -95,7 +92,7 @@ test_upgrade_paths() { wait_for_api setup_client_TLS_certs - restore_4_6_backup + restore_backup wait_for_api # Run with some scale to have data populated to migrate @@ -260,7 +257,7 @@ test_upgrade_paths() { } force_rollback_to_previous_postgres() { - info "Forcing a rollback to $FORCE_ROLLBACK_VERSION" + info "Forcing a rollback to ${EARLIER_TAG}" local upgradeStatus upgradeStatus=$(curl -sSk -X GET --config <(curl_cfg user "admin:${ROX_ADMIN_PASSWORD}") https://"${API_ENDPOINT}"/v1/centralhealth/upgradestatus) @@ -270,7 +267,7 @@ force_rollback_to_previous_postgres() { kubectl -n stackrox get configmap/central-config -o yaml | yq e '{"data": .data}' - >/tmp/force_rollback_patch local central_config - central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"$FORCE_ROLLBACK_VERSION\"" -) + central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"${EARLIER_TAG}\"" -) local config_patch config_patch=$(yq e ".data[\"central-config.yaml\"] |= \"$central_config\"" /tmp/force_rollback_patch) echo "config patch: $config_patch" @@ -284,7 +281,7 @@ force_rollback_to_previous_postgres() { kubectl -n stackrox set env deploy/sensor ROX_PROCESSES_LISTENING_ON_PORT=false kubectl -n stackrox patch configmap/central-config -p "$config_patch" - kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:$FORCE_ROLLBACK_VERSION" + kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:${EARLIER_TAG}" # Do not rollback central-db image, since downgrade from PG15 to PG13 is # not possible. @@ -297,7 +294,8 @@ deploy_scaled_workload() { PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output secured-cluster-services --image-defaults opensource --output-dir /tmp/early-stackrox-secured-services-chart --remove - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml + PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ + central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml helm install -n stackrox --create-namespace \ stackrox-secured-cluster-services /tmp/early-stackrox-secured-services-chart \ @@ -311,6 +309,14 @@ deploy_scaled_workload() { sensor_wait ./scale/launch_workload.sh scale-test + + # The historical scale script requests 5 CPUs per component. Leave room for + # both scanners by reducing Central and Central DB's CPU reservations. + kubectl -n stackrox patch deploy/central --type=strategic -p \ + '{"spec":{"template":{"spec":{"containers":[{"name":"central","resources":{"requests":{"cpu":"2"}}}]}}}}' + # Init-container requests also count toward the pod's CPU reservation. + kubectl -n stackrox patch deploy/central-db --type=strategic -p \ + '{"spec":{"template":{"spec":{"containers":[{"name":"central-db","resources":{"requests":{"cpu":"2"}}}],"initContainers":[{"name":"init-db","resources":{"requests":{"cpu":"2"}}}]}}}}' wait_for_api info "Sleep for a bit to let the scale build" diff --git a/tests/upgrade/postgres_upgrade_run.sh b/tests/upgrade/postgres_upgrade_run.sh index 28c1e62969ece..510e3d584cb10 100755 --- a/tests/upgrade/postgres_upgrade_run.sh +++ b/tests/upgrade/postgres_upgrade_run.sh @@ -3,29 +3,43 @@ set -euo pipefail -# Tests upgrade to Postgres. +# Tests PostgreSQL major-version upgrades. TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" -EARLIER_TAG="4.7.2" -EARLIER_SHA="ef2060ed332c7b8513cb9eb52b9745df9a8285cc" -CURRENT_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" -PREVIOUS_RELEASES=("4.7.3") +EARLIER_TAG="4.10.0" +EARLIER_SHA="7b817fa511ac4533cdf2d79a1b8e04d4d7557ad2" # shellcheck source=../../scripts/lib.sh source "$TEST_ROOT/scripts/lib.sh" -# shellcheck source=../../scripts/ci/lib.sh -source "$TEST_ROOT/scripts/ci/lib.sh" -# shellcheck source=../../scripts/ci/sensor-wait.sh -source "$TEST_ROOT/scripts/ci/sensor-wait.sh" -# shellcheck source=../../scripts/setup-certs.sh -source "$TEST_ROOT/tests/scripts/setup-certs.sh" -# shellcheck source=../../tests/e2e/lib.sh -source "$TEST_ROOT/tests/e2e/lib.sh" -# shellcheck source=../../tests/upgrade/lib.sh -source "$TEST_ROOT/tests/upgrade/lib.sh" -# shellcheck source=../../tests/upgrade/validation.sh -source "$TEST_ROOT/tests/upgrade/validation.sh" + +postgres_major_version() { + # Current images use ARG PG_VERSION; older releases put the major directly in FROM. + local version + version="$(sed -n \ + -e 's/^[[:space:]]*ARG[[:space:]]\+PG_VERSION=\([1-9][0-9]*\)[[:space:]]*$/\1/p' \ + -e 's/^[[:space:]]*FROM[[:space:]].*\/postgresql-\([1-9][0-9]*\)-.*$/\1/p')" + # Missing or ambiguous versions must fail the check, not silently skip coverage. + [[ "$version" =~ ^[1-9][0-9]*$ ]] || return 1 + echo "$version" +} + +check_postgres_upgrade() { + local earlier_version current_version + earlier_version="$(git -C "$TEST_ROOT" show "$EARLIER_SHA:image/postgres/Dockerfile" | postgres_major_version)" || \ + die "Cannot determine PostgreSQL major version for $EARLIER_TAG ($EARLIER_SHA)" + current_version="$(postgres_major_version < "$TEST_ROOT/image/postgres/Dockerfile")" || \ + die "Cannot determine PostgreSQL major version for the current checkout" + + POSTGRES_UPGRADE_REQUIRED=false + if (( current_version > earlier_version )); then + POSTGRES_UPGRADE_REQUIRED=true + POSTGRES_UPGRADE_REASON="Running PostgreSQL upgrade suite: $EARLIER_TAG uses PostgreSQL $earlier_version; current checkout uses PostgreSQL $current_version." + else + POSTGRES_UPGRADE_REASON="Skipping PostgreSQL upgrade suite: $EARLIER_TAG uses PostgreSQL $earlier_version; current checkout uses PostgreSQL $current_version. No major-version upgrade." + fi + info "$POSTGRES_UPGRADE_REASON" +} test_upgrade() { info "Starting postgres upgrade test" @@ -79,8 +93,6 @@ test_upgrade_path() { local log_output_dir="$1" - FORCE_ROLLBACK_VERSION="4.7.2" - cd "$REPO_FOR_TIME_TRAVEL" git checkout "$EARLIER_SHA" @@ -99,7 +111,7 @@ test_upgrade_path() { # It's damn fiddly, restore is needed because later test will search for a # default secured cluster, created by it :( - restore_4_6_backup + restore_backup wait_for_api # Run with some scale to have data populated to migrate @@ -191,8 +203,6 @@ test_not_enough_disk_space() { local log_output_dir="$1" - FORCE_ROLLBACK_VERSION="4.7.2" - cd "$REPO_FOR_TIME_TRAVEL" git checkout "$EARLIER_SHA" @@ -213,7 +223,7 @@ test_not_enough_disk_space() { # It's damn fiddly, restore is needed because later test will search for a # default secured cluster, created by it :( - restore_4_6_backup + restore_backup wait_for_api # Do not apply scaled workload to control disk space, do fallocate instead @@ -277,7 +287,7 @@ test_not_enough_disk_space() { } force_rollback_to_previous_postgres() { - info "Forcing a rollback to $FORCE_ROLLBACK_VERSION" + info "Forcing a rollback to ${EARLIER_TAG}" local upgradeStatus upgradeStatus=$(curl -sSk -X GET --config <(curl_cfg user "admin:${ROX_ADMIN_PASSWORD}") https://"${API_ENDPOINT}"/v1/centralhealth/upgradestatus) @@ -287,7 +297,7 @@ force_rollback_to_previous_postgres() { kubectl -n stackrox get configmap/central-config -o yaml | yq e '{"data": .data}' - >/tmp/force_rollback_patch local central_config - central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"$FORCE_ROLLBACK_VERSION\"" -) + central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"${EARLIER_TAG}\"" -) local config_patch config_patch=$(yq e ".data[\"central-config.yaml\"] |= \"$central_config\"" /tmp/force_rollback_patch) echo "config patch: $config_patch" @@ -301,7 +311,7 @@ force_rollback_to_previous_postgres() { kubectl -n stackrox set env deploy/sensor ROX_PROCESSES_LISTENING_ON_PORT=false kubectl -n stackrox patch configmap/central-config -p "$config_patch" - kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:$FORCE_ROLLBACK_VERSION" + kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:${EARLIER_TAG}" # Do not rollback central-db image, since downgrade from PG15 to PG13 is # not possible. @@ -317,7 +327,8 @@ deploy_scaled_workload() { # Make sure no init bundle from previous runs is there rm -f /tmp/cluster-init-bundle.yaml - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml + PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ + central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml helm install -n stackrox --create-namespace \ stackrox-secured-cluster-services /tmp/early-stackrox-secured-services-chart \ @@ -331,6 +342,14 @@ deploy_scaled_workload() { sensor_wait ./scale/launch_workload.sh scale-test + + # The historical scale script requests 5 CPUs per component. Leave room for + # both scanners by reducing Central and Central DB's CPU reservations. + kubectl -n stackrox patch deploy/central --type=strategic -p \ + '{"spec":{"template":{"spec":{"containers":[{"name":"central","resources":{"requests":{"cpu":"2"}}}]}}}}' + # Init-container requests also count toward the pod's CPU reservation. + kubectl -n stackrox patch deploy/central-db --type=strategic -p \ + '{"spec":{"template":{"spec":{"containers":[{"name":"central-db","resources":{"requests":{"cpu":"2"}}}],"initContainers":[{"name":"init-db","resources":{"requests":{"cpu":"2"}}}]}}}}' wait_for_api info "Sleep for a bit to let the scale build" @@ -344,5 +363,30 @@ deploy_scaled_workload() { } if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then - test_upgrade "$*" + check_postgres_upgrade + if [[ "${1:-}" == "--check" ]]; then + # CI checks eligibility before provisioning a cluster or running pre/post-test hooks. + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "required=$POSTGRES_UPGRADE_REQUIRED" >> "$GITHUB_OUTPUT" + fi + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + echo "$POSTGRES_UPGRADE_REASON" >> "$GITHUB_STEP_SUMMARY" + fi + elif [[ "$POSTGRES_UPGRADE_REQUIRED" == true ]]; then + CURRENT_TAG="${MAIN_IMAGE_TAG:-"$(make --quiet --no-print-directory tag)"}" + # Load deployment helpers only when the suite will actually run. + # shellcheck source=../../scripts/ci/lib.sh + source "$TEST_ROOT/scripts/ci/lib.sh" + # shellcheck source=../../scripts/ci/sensor-wait.sh + source "$TEST_ROOT/scripts/ci/sensor-wait.sh" + # shellcheck source=../../scripts/setup-certs.sh + source "$TEST_ROOT/tests/scripts/setup-certs.sh" + # shellcheck source=../../tests/e2e/lib.sh + source "$TEST_ROOT/tests/e2e/lib.sh" + # shellcheck source=../../tests/upgrade/lib.sh + source "$TEST_ROOT/tests/upgrade/lib.sh" + # shellcheck source=../../tests/upgrade/validation.sh + source "$TEST_ROOT/tests/upgrade/validation.sh" + test_upgrade "$@" + fi fi From 4900c4351fc16b8719e52c188e9399475d83cfe0 Mon Sep 17 00:00:00 2001 From: Aleksandr Kurlov Date: Mon, 28 Sep 2026 18:17:53 +0200 Subject: [PATCH 430/638] ROX-36575: Serve roxctl binaries from tars (#22683) Co-authored-by: Misha Sugakov <537715+msugakov@users.noreply.github.com> --- CHANGELOG.md | 2 +- central/cli/download.go | 76 ++++++++++++++++++- central/cli/download_test.go | 139 ++++++++++++++++++++++++++++++++++ image/rhel/Dockerfile | 16 +++- image/rhel/konflux.Dockerfile | 11 ++- 5 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 central/cli/download_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d7336e61d2a50..ccbe071da4de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,8 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Deprecated Features ### Technical Changes +- ROX-36534: roxctl binaries in the Central image are now stored as `.tar.gz` archives. Central extracts and streams the binary on download, so the user-facing download behavior is unchanged. - ROX-35137: Central now prunes deployments whose cluster no longer exists in the database. - - ROX-37048: Central writes its version in `Rh-Central-Version` gRPC response header to authenticated clients. `roxctl` compares this version with its own version and reports a warning to stderr if the version skew is outside of a supported range. ## [5.0.0] diff --git a/central/cli/download.go b/central/cli/download.go index e0ad06abf0ecc..cdc705a28dcb3 100644 --- a/central/cli/download.go +++ b/central/cli/download.go @@ -1,17 +1,85 @@ package cli import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" "net/http" + "os" "path/filepath" + "strconv" + "strings" + + "github.com/stackrox/rox/pkg/logging" + "github.com/stackrox/rox/pkg/utils" ) const downloadPath = "/assets/downloads/cli" -// Handler returns a handler for serving files from Central's downloads folder +var log = logging.LoggerForModule() + +// Handler serves roxctl binaries from Central UI. +// Binaries are stored as .tar.gz and extracted on the fly on each request. func Handler() http.HandlerFunc { + return handlerWithDir(downloadPath) +} + +func handlerWithDir(dir string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // Need to remove the rest of the URL path so that it just contains the file wanted - r.URL.Path = filepath.Base(r.URL.Path) - http.FileServer(http.Dir(downloadPath)).ServeHTTP(w, r) + if r.Method != http.MethodGet && r.Method != http.MethodHead { + w.Header().Set("Allow", http.MethodGet+", "+http.MethodHead) + http.Error(w, fmt.Sprintf("method %s not allowed", r.Method), http.StatusMethodNotAllowed) + return + } + + filename := filepath.Base(r.URL.Path) + tarPath := filepath.Join(dir, strings.TrimSuffix(filename, ".exe")+".tar.gz") + + f, err := os.Open(tarPath) + if err != nil { + log.Warnf("error opening tarball for requested binary %q: %v", filename, err) + http.Error(w, "not found", http.StatusNotFound) + return + } + defer utils.IgnoreError(f.Close) + + gz, err := gzip.NewReader(f) + if err != nil { + log.Warnf("error opening gzip reader for %s: %v", tarPath, err) + http.Error(w, "invalid archive", http.StatusInternalServerError) + return + } + defer utils.IgnoreError(gz.Close) + + tr := tar.NewReader(gz) + for { + hdr, err := tr.Next() + if err == io.EOF { + log.Warnf("binary %q not found in %s; tarball entry name may not match the requested filename", filename, tarPath) + http.Error(w, "not found", http.StatusNotFound) + return + } + if err != nil { + log.Warnf("error reading archive %s: %v", tarPath, err) + http.Error(w, "error reading archive", http.StatusInternalServerError) + return + } + if hdr.Name != filename { + continue + } + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Length", strconv.FormatInt(hdr.Size, 10)) + w.Header().Set("Accept-Ranges", "none") + w.Header().Set("Cache-Control", "no-cache") + if r.Method == http.MethodGet { + if _, err = io.Copy(w, tr); err != nil { + log.Warnf("failed to stream %s: %v", filename, err) + panic(http.ErrAbortHandler) + } + } + return + } } } diff --git a/central/cli/download_test.go b/central/cli/download_test.go new file mode 100644 index 0000000000000..7a32eee16e2d3 --- /dev/null +++ b/central/cli/download_test.go @@ -0,0 +1,139 @@ +package cli + +import ( + "archive/tar" + "compress/gzip" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func writeTarball(t *testing.T, dir, tarName, entryName string, content []byte) { + t.Helper() + f, err := os.Create(filepath.Join(dir, tarName+".tar.gz")) + require.NoError(t, err) + + gz := gzip.NewWriter(f) + tw := tar.NewWriter(gz) + err = tw.WriteHeader(&tar.Header{ + Name: entryName, + Size: int64(len(content)), + Mode: 0755, + }) + require.NoError(t, err) + _, err = tw.Write(content) + require.NoError(t, err) + require.NoError(t, tw.Close()) + require.NoError(t, gz.Close()) + require.NoError(t, f.Close()) +} + +type HandlerSuite struct { + suite.Suite + dir string + handler http.HandlerFunc + payload []byte +} + +func TestHandlerSuite(t *testing.T) { + suite.Run(t, new(HandlerSuite)) +} + +func (s *HandlerSuite) SetupTest() { + s.dir = s.T().TempDir() + s.handler = handlerWithDir(s.dir) + s.payload = []byte("fake roxctl binary content") + writeTarball(s.T(), s.dir, "roxctl-linux-amd64", "roxctl-linux-amd64", s.payload) + // Windows: tarball strips .exe from the name (required by release pipeline), + // but the entry inside the tarball preserves .exe. + writeTarball(s.T(), s.dir, "roxctl-windows-amd64", "roxctl-windows-amd64.exe", s.payload) +} + +func (s *HandlerSuite) TestServesLinuxBinaryViaGET() { + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + s.handler(rr, req) + s.Equal(http.StatusOK, rr.Code) + s.Equal(`attachment; filename="roxctl-linux-amd64"`, rr.Header().Get("Content-Disposition")) + s.Equal("application/octet-stream", rr.Header().Get("Content-Type")) + s.Equal("26", rr.Header().Get("Content-Length")) + s.Equal("none", rr.Header().Get("Accept-Ranges")) + s.Equal("no-cache", rr.Header().Get("Cache-Control")) + s.Equal(s.payload, rr.Body.Bytes()) +} + +func (s *HandlerSuite) TestServesWindowsBinaryViaGET() { + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-windows-amd64.exe", nil) + rr := httptest.NewRecorder() + s.handler(rr, req) + s.Equal(http.StatusOK, rr.Code) + s.Equal(`attachment; filename="roxctl-windows-amd64.exe"`, rr.Header().Get("Content-Disposition")) + s.Equal(s.payload, rr.Body.Bytes()) +} + +func (s *HandlerSuite) TestReturnsHeadersButNoBodyForHEAD() { + req := httptest.NewRequest(http.MethodHead, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + s.handler(rr, req) + s.Equal(http.StatusOK, rr.Code) + s.Equal("26", rr.Header().Get("Content-Length")) + s.Empty(rr.Body.Bytes()) +} + +func (s *HandlerSuite) TestReturns404ForMissingTarball() { + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-darwin-amd64", nil) + rr := httptest.NewRecorder() + s.handler(rr, req) + s.Equal(http.StatusNotFound, rr.Code) +} + +func (s *HandlerSuite) TestReturns404WhenBinaryNotInTarball() { + dir := s.T().TempDir() + // Tarball exists but entry name does not match the requested filename. + writeTarball(s.T(), dir, "roxctl-linux-amd64", "wrong-entry-name", s.payload) + handler := handlerWithDir(dir) + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + handler(rr, req) + s.Equal(http.StatusNotFound, rr.Code) +} + +func (s *HandlerSuite) TestReturns405ForUnsupportedMethod() { + req := httptest.NewRequest(http.MethodPost, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + s.handler(rr, req) + s.Equal(http.StatusMethodNotAllowed, rr.Code) + s.Equal("GET, HEAD", rr.Header().Get("Allow")) +} + +func (s *HandlerSuite) TestReturns500ForCorruptedGzip() { + dir := s.T().TempDir() + err := os.WriteFile(filepath.Join(dir, "roxctl-linux-amd64.tar.gz"), []byte("not a gzip"), 0600) + s.Require().NoError(err) + handler := handlerWithDir(dir) + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + handler(rr, req) + s.Equal(http.StatusInternalServerError, rr.Code) +} + +func (s *HandlerSuite) TestReturns500ForCorruptedTar() { + dir := s.T().TempDir() + f, err := os.Create(filepath.Join(dir, "roxctl-linux-amd64.tar.gz")) + s.Require().NoError(err) + gz := gzip.NewWriter(f) + _, err = gz.Write([]byte("valid gzip but not a tar")) + s.Require().NoError(err) + s.Require().NoError(gz.Close()) + s.Require().NoError(f.Close()) + handler := handlerWithDir(dir) + req := httptest.NewRequest(http.MethodGet, "/api/cli/download/roxctl-linux-amd64", nil) + rr := httptest.NewRecorder() + handler(rr, req) + s.Equal(http.StatusInternalServerError, rr.Code) +} diff --git a/image/rhel/Dockerfile b/image/rhel/Dockerfile index ecd99837e048e..73ebed2028f8d 100644 --- a/image/rhel/Dockerfile +++ b/image/rhel/Dockerfile @@ -39,6 +39,17 @@ RUN rpm --import /tmp/RPM-GPG-KEY-CentOS-Official && \ dnf clean all --installroot=/out/ && \ rm -rf /out/var/cache/dnf /out/var/cache/yum +FROM ubi-base AS cli-tarballs + +RUN dnf install -y pigz +COPY bin/roxctl* /tmp/roxctl/ +RUN cd /tmp/roxctl && \ + shopt -s nullglob && \ + for f in roxctl-*; do \ + [[ -f "$f" && -x "$f" ]] || continue; \ + tar -I pigz -cvf "${f%.exe}.tar.gz" "$f"; \ + done + FROM ubi-micro-base ARG LABEL_VERSION @@ -94,9 +105,8 @@ COPY bin/admission-control /stackrox/bin/admission-control COPY bin/config-controller /stackrox/bin/config-controller COPY bin/central-worker /stackrox/bin/central-worker COPY bin/roxagent /stackrox/bin/roxagent -COPY bin/roxctl* /assets/downloads/cli/ - -RUN ln -s /assets/downloads/cli/roxctl-linux-${TARGET_ARCH} /stackrox/roxctl +COPY --from=cli-tarballs /tmp/roxctl/roxctl-*.tar.gz /assets/downloads/cli/ +COPY --from=cli-tarballs /tmp/roxctl/roxctl-linux-${TARGET_ARCH} /stackrox/roxctl EXPOSE 8443 diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index aa3797bec1801..e441fdf710356 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -43,6 +43,11 @@ RUN cd /go/src/github.com/stackrox/rox/app/image/rhel/bin && \ tar -I pigz -cvf "${f%.exe}.tar.gz" "$f"; \ done +RUN arch=$(uname -m) ; \ + case $arch in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac ; \ + cp /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl-linux-${arch} \ + /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl + FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:bb046ef111dd20f890fe9879a37df8e829ed661177fae2339ba7eeb495f46c60 as ui-builder @@ -104,7 +109,8 @@ COPY --from=ui-builder /go/src/github.com/stackrox/rox/app/ui/build /ui/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/migrator /stackrox/bin/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/central /stackrox/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/compliance /stackrox/bin/ -COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl* /assets/downloads/cli/ +COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl-*.tar.gz /assets/downloads/cli/ +COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl /stackrox/roxctl COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/kubernetes-sensor /stackrox/bin/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/sensor-upgrader /stackrox/bin/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/admission-control /stackrox/bin/ @@ -112,9 +118,6 @@ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/config COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxagent /stackrox/bin/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/bin/central-worker /stackrox/bin/ COPY --from=go-builder /go/src/github.com/stackrox/rox/app/image/rhel/static-bin/* /stackrox/ -RUN GOARCH=$(uname -m) ; \ - case $GOARCH in x86_64) GOARCH=amd64 ;; aarch64) GOARCH=arm64 ;; esac ; \ - ln -s /assets/downloads/cli/roxctl-linux-$GOARCH /stackrox/roxctl ARG BUILD_TAG From 52a9ad6184532310e0740adc7da7bbd1d3d59d72 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 28 Sep 2026 16:52:03 -0400 Subject: [PATCH 431/638] ROX-37231: Delete ROX_FLATTEN_IMAGE_DATA in ui (#23051) --- .../workloadCves/imageSingle.test.ts | 17 ++--- .../VulnMgmt/widgets/CvesByCvssScore.jsx | 4 +- .../VulnMgmt/widgets/TopRiskiestEntities.jsx | 4 +- .../Image/DeploymentResourceTable.tsx | 18 ----- .../WorkloadCves/Image/ImagePage.tsx | 36 +--------- .../WorkloadCves/Image/ImagePageResources.tsx | 27 +------ .../Image/ImagePageVulnerabilities.tsx | 71 ++++--------------- .../WorkloadCves/Overview/useImages.ts | 7 +- .../components/ImageDetailBadges.tsx | 34 --------- .../useInvalidateVulnerabilityQueries.ts | 4 +- ui/apps/platform/src/types/featureFlag.ts | 1 - 11 files changed, 29 insertions(+), 194 deletions(-) diff --git a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/imageSingle.test.ts b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/imageSingle.test.ts index 8ce81d5520e73..c784b117e70ed 100644 --- a/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/imageSingle.test.ts +++ b/ui/apps/platform/cypress/integration/vulnerabilities/workloadCves/imageSingle.test.ts @@ -49,20 +49,11 @@ describe('Workload CVE Image Single page', () => { // see: https://issues.redhat.com/browse/ROX-24254 // https://github.com/stackrox/stackrox/pull/6156 it('should display nested component data correctly when processed via apollo client', () => { - const isFlattenImageData = hasFeatureFlag('ROX_FLATTEN_IMAGE_DATA'); - const imageRootKey = isFlattenImageData ? 'imageV2' : 'image'; - const opname = 'getCVEsForImage'; const imageData = { - id: isFlattenImageData - ? '4c657931-d333-5cb8-8f0d-7e3836525ec7' - : 'sha256:010fec71f42f4b5e65f3f56f10af94a7c05c9c271a9bbc3026684ba170698cb5', - ...(isFlattenImageData - ? { - digest: 'sha256:010fec71f42f4b5e65f3f56f10af94a7c05c9c271a9bbc3026684ba170698cb5', - } - : {}), + id: '4c657931-d333-5cb8-8f0d-7e3836525ec7', + digest: 'sha256:010fec71f42f4b5e65f3f56f10af94a7c05c9c271a9bbc3026684ba170698cb5', name: { registry: 'quay.io', remote: 'openshift-release-dev/ocp-v4.0-art-dev', @@ -82,7 +73,7 @@ describe('Workload CVE Image Single page', () => { }, __typename: 'ImageMetadata', }, - __typename: isFlattenImageData ? 'ImageV2' : 'Image', + __typename: 'ImageV2', imageCVECountBySeverity: { unknown: { total: 0, @@ -182,7 +173,7 @@ describe('Workload CVE Image Single page', () => { const body = { data: { - [imageRootKey]: imageData, + imageV2: imageData, }, }; diff --git a/ui/apps/platform/src/Containers/VulnMgmt/widgets/CvesByCvssScore.jsx b/ui/apps/platform/src/Containers/VulnMgmt/widgets/CvesByCvssScore.jsx index 19c500517c944..2aa1642136b1b 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/widgets/CvesByCvssScore.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/widgets/CvesByCvssScore.jsx @@ -12,7 +12,6 @@ import workflowStateContext from 'Containers/workflowStateContext'; import { vulnerabilitySeverityColorMap } from 'constants/severityColors'; import { vulnerabilitySeverityLabels } from 'messages/common'; import { getScopeQuery } from 'Containers/VulnMgmt/Entity/VulnMgmtPolicyQueryUtil'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import ViewAllButton from './ViewAllButton'; @@ -57,8 +56,7 @@ const vulnerabilitySeverities = [ ]; const CvesByCvssScore = ({ entityContext, parentContext }) => { - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); + const isNewImageDataModelEnabled = true; let queryToUse = IMAGE_CVES_QUERY; let linkTypeToUse = entityTypes.IMAGE_CVE; diff --git a/ui/apps/platform/src/Containers/VulnMgmt/widgets/TopRiskiestEntities.jsx b/ui/apps/platform/src/Containers/VulnMgmt/widgets/TopRiskiestEntities.jsx index 01f17ab001edb..ca9e5c4d83f18 100644 --- a/ui/apps/platform/src/Containers/VulnMgmt/widgets/TopRiskiestEntities.jsx +++ b/ui/apps/platform/src/Containers/VulnMgmt/widgets/TopRiskiestEntities.jsx @@ -20,7 +20,6 @@ import { entityNounOrdinaryCasePlural, entityNounSentenceCaseSingular, } from '../entitiesForVulnerabilityManagement'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import NumberedList from './NumberedList'; import ViewAllButton from './ViewAllButton'; @@ -325,8 +324,7 @@ const processData = (data, entityType, workflowState) => { }; const TopRiskiestEntities = ({ entityContext, search, limit }) => { - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); + const isNewImageDataModelEnabled = true; const entities = getEntitiesByContext(entityContext); const [selectedEntity, setSelectedEntity] = useState(entities[0].value); diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/DeploymentResourceTable.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/DeploymentResourceTable.tsx index 1be1393ee613c..ae50ae8db38e4 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/DeploymentResourceTable.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/DeploymentResourceTable.tsx @@ -44,24 +44,6 @@ export type DeploymentResources = { }[]; }; -export const deploymentResourcesFragment = gql` - fragment DeploymentResources on Image { - deploymentCount(query: $query) - deployments(query: $query, pagination: $pagination) { - id - name - type - clusterName - namespace - created - } - } -`; - -/** - * Same fields as DeploymentResources but on ImageV2; when ROX_FLATTEN_IMAGE_DATA is enabled, - * we call ImageV2 resolver which returns ImageV2 type. - */ export const deploymentResourcesV2Fragment = gql` fragment DeploymentResourcesV2 on ImageV2 { deploymentCount(query: $query) diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx index 82662b930a8f2..738d703b69800 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePage.tsx @@ -27,7 +27,6 @@ import PageTitle from 'Components/PageTitle'; import useURLStringUnion from 'hooks/useURLStringUnion'; import EmptyStateTemplate from 'Components/EmptyStateTemplate'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import useIsScannerV4Enabled from 'hooks/useIsScannerV4Enabled'; import usePermissions from 'hooks/usePermissions'; import useURLPagination from 'hooks/useURLPagination'; @@ -46,10 +45,7 @@ import ImagePageVulnerabilities from './ImagePageVulnerabilities'; import ImagePageResources from './ImagePageResources'; import ImagePageSignatureVerification from './ImagePageSignatureVerification'; import { detailsTabValues } from '../../types'; -import ImageDetailBadges, { - imageDetailsFragment, - imageV2DetailsFragment, -} from '../components/ImageDetailBadges'; +import ImageDetailBadges, { imageV2DetailsFragment } from '../components/ImageDetailBadges'; import type { ImageDetails } from '../components/ImageDetailBadges'; import getImageScanMessage from '../utils/getImageScanMessage'; import { DEFAULT_VM_PAGE_SIZE } from '../../constants'; @@ -61,22 +57,6 @@ import { createScheduledReportForImageVulnerabilitiesURL } from '../../Reports/I import CreateReportDropdown from '../components/CreateReportDropdown'; import CreateViewBasedReportModal from '../../components/CreateViewBasedReportModal'; -const imageDetailsQuery = gql` - ${imageDetailsFragment} - query getImageDetails($id: ID!) { - image(id: $id) { - id - name { - registry - remote - tag - fullName - } - ...ImageDetails - } - } -`; - const imageV2DetailsQuery = gql` ${imageV2DetailsFragment} query getImageDetails($id: ID!) { @@ -130,23 +110,13 @@ function ImagePage({ deploymentResourceColumnOverrides, }: ImagePageProps) { const navigate = useNavigate(); - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); const { urlBuilder, pageTitle, baseSearchFilter, viewContext } = useWorkloadCveViewContext(); const { imageId } = useParams() as { imageId: string }; - const v1Query = useQuery<{ image: ImageData }, { id: string }>(imageDetailsQuery, { - variables: { id: imageId }, - skip: isNewImageDataModelEnabled, - }); - - const v2Query = useQuery<{ image: ImageData }, { id: string }>(imageV2DetailsQuery, { + const { data, error } = useQuery<{ image: ImageData }, { id: string }>(imageV2DetailsQuery, { variables: { id: imageId }, - skip: !isNewImageDataModelEnabled, }); - const data = isNewImageDataModelEnabled ? v2Query.data : v1Query.data; - const error = isNewImageDataModelEnabled ? v2Query.error : v1Query.error; const [activeTabKey, setActiveTabKey] = useURLStringUnion('detailsTab', detailsTabValues); const { invalidateAll: refetchAll } = useInvalidateVulnerabilityQueries(); @@ -172,7 +142,7 @@ function ImagePage({ // Create a scoped search filter that includes the image SHA filter plus any applied search filters. const imageScopedSearchFilterForReport = { ...baseSearchFilter, - ...(isNewImageDataModelEnabled ? { 'Image ID': [imageId] } : { 'Image SHA': [imageId] }), + 'Image ID': [imageId], ...querySearchFilter, 'Vulnerability State': [vulnerabilityState], }; diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageResources.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageResources.tsx index 2c095f2d98ab8..ae358efb31c63 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageResources.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageResources.tsx @@ -8,7 +8,6 @@ import { Spinner, } from '@patternfly/react-core'; import { gql, useQuery } from '@apollo/client'; -import type { DocumentNode } from '@apollo/client'; import type { Pagination as PaginationParam } from 'services/types'; import TableErrorComponent from 'Components/PatternFly/TableErrorComponent'; @@ -17,11 +16,9 @@ import type { ColumnConfigOverrides } from 'hooks/useManagedColumns'; import type { UseURLPaginationResult } from 'hooks/useURLPagination'; import useURLSort from 'hooks/useURLSort'; import useSelectToggle from 'hooks/patternfly/useSelectToggle'; -import useFeatureFlags from 'hooks/useFeatureFlags'; import { getPaginationParams, getRequestQueryStringForSearchFilter } from 'utils/searchUtils'; import DeploymentResourceTable, { - deploymentResourcesFragment, deploymentResourcesV2Fragment, deploymentResourcesTableId, defaultColumns as deploymentResourcesDefaultColumns, @@ -37,16 +34,6 @@ export type ImagePageResourcesProps = { >; }; -const imageResourcesQuery = gql` - ${deploymentResourcesFragment} - query getImageResources($id: ID!, $query: String, $pagination: Pagination) { - image(id: $id) { - id - ...DeploymentResources - } - } -`; - const imageV2ResourcesQuery = gql` ${deploymentResourcesV2Fragment} query getImageResources($id: ID!, $query: String, $pagination: Pagination) { @@ -58,16 +45,11 @@ const imageV2ResourcesQuery = gql` } `; -export const getImageResourcesQuery = (isNewImageDataModelEnabled: boolean): DocumentNode => - isNewImageDataModelEnabled ? imageV2ResourcesQuery : imageResourcesQuery; - function ImagePageResources({ imageId, pagination, deploymentResourceColumnOverrides, }: ImagePageResourcesProps) { - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); const { baseSearchFilter } = useWorkloadCveViewContext(); const { page, perPage, setPage, setPerPage } = pagination; const { sortOption, getSortParams } = useURLSort({ @@ -80,11 +62,10 @@ function ImagePageResources({ const { data, previousData, loading, error } = useQuery< { - image: DeploymentResources | null; // Legacy image data model, will be null when ROX_FLATTEN_IMAGE_DATA is enabled - imageV2: DeploymentResources | null; // New image data model, will be null when ROX_FLATTEN_IMAGE_DATA is disabled + imageV2: DeploymentResources | null; }, { id: string; query: string; pagination: PaginationParam } - >(getImageResourcesQuery(isNewImageDataModelEnabled), { + >(imageV2ResourcesQuery, { variables: { id: imageId, query: getRequestQueryStringForSearchFilter(baseSearchFilter), @@ -92,9 +73,7 @@ function ImagePageResources({ }, }); - const imageResourcesData = - (data && (isNewImageDataModelEnabled ? data.imageV2 : data.image)) ?? - (previousData && (isNewImageDataModelEnabled ? previousData.imageV2 : previousData.image)); + const imageResourcesData = data?.imageV2 ?? previousData?.imageV2; const deploymentCount = imageResourcesData?.deploymentCount ?? 0; const deploymentResourceColumnState = useManagedColumns( diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx index 5c797f0017163..6fbed26d3c086 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/Image/ImagePageVulnerabilities.tsx @@ -12,7 +12,6 @@ import { pluralize, } from '@patternfly/react-core'; import { gql, useQuery } from '@apollo/client'; -import type { DocumentNode } from '@apollo/client'; import type { SearchFilter } from 'types/search'; import type { UseURLPaginationResult } from 'hooks/useURLPagination'; import useURLSort from 'hooks/useURLSort'; @@ -51,10 +50,7 @@ import { parseQuerySearchFilter, } from '../../utils/searchUtils'; import BySeveritySummaryCard from '../../components/BySeveritySummaryCard'; -import { - imageMetadataContextFragment, - imageV2MetadataContextFragment, -} from '../Tables/table.utils'; +import { imageV2MetadataContextFragment } from '../Tables/table.utils'; import type { ImageMetadataContext } from '../Tables/table.utils'; import VulnerabilityStateTabs, { vulnStateTabContentId, @@ -72,29 +68,6 @@ import { import BaseImageAssessmentCard from '../components/BaseImageAssessmentCard'; import type { BaseImage } from '../components/ImageDetailBadges'; -export const imageVulnerabilitiesQuery = gql` - ${imageMetadataContextFragment} - ${resourceCountByCveSeverityAndStatusFragment} - ${imageVulnerabilitiesFragment} - query getCVEsForImage( - $id: ID! - $query: String! - $pagination: Pagination! - $statusesForExceptionCount: [String!] - ) { - image(id: $id) { - ...ImageMetadataContext - imageVulnerabilityCount(query: $query) - imageCVECountBySeverity(query: $query) { - ...ResourceCountsByCVESeverityAndStatus - } - imageVulnerabilities(query: $query, pagination: $pagination) { - ...ImageVulnerabilityFields - } - } - } -`; - export const imageV2VulnerabilitiesQuery = gql` ${imageV2MetadataContextFragment} ${resourceCountByCveSeverityAndStatusFragment} @@ -118,9 +91,6 @@ export const imageV2VulnerabilitiesQuery = gql` } `; -export const getImageVulnerabilitiesQuery = (isNewImageDataModelEnabled: boolean): DocumentNode => - isNewImageDataModelEnabled ? imageV2VulnerabilitiesQuery : imageVulnerabilitiesQuery; - const defaultSortFields = ['CVE', 'CVSS', 'Severity']; export type ImagePageVulnerabilitiesProps = { @@ -153,7 +123,6 @@ function ImagePageVulnerabilities({ setSearchFilter, }: ImagePageVulnerabilitiesProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); const { analyticsTrack } = useAnalytics(); const trackAppliedFilter = createFilterTracker(analyticsTrack); @@ -176,20 +145,11 @@ function ImagePageVulnerabilities({ // TODO Split metadata, counts, and vulnerabilities into separate queries const { data, loading, error } = useQuery< { - image: - | (ImageMetadataContext & { - imageCVECountBySeverity: ResourceCountByCveSeverityAndStatus; - imageVulnerabilityCount: number; - imageVulnerabilities: ImageVulnerability[]; - }) - | null; // Legacy image data model, will be null when ROX_FLATTEN_IMAGE_DATA is enabled - imageV2: - | (ImageMetadataContext & { - imageCVECountBySeverity: ResourceCountByCveSeverityAndStatus; - imageVulnerabilityCount: number; - imageVulnerabilities: ImageVulnerability[]; - }) - | null; // New image data model, will be null when ROX_FLATTEN_IMAGE_DATA is disabled + imageV2: ImageMetadataContext & { + imageCVECountBySeverity: ResourceCountByCveSeverityAndStatus; + imageVulnerabilityCount: number; + imageVulnerabilities: ImageVulnerability[]; + }; }, { id: string; @@ -197,7 +157,7 @@ function ImagePageVulnerabilities({ pagination: PaginationParam; statusesForExceptionCount: string[]; } - >(getImageVulnerabilitiesQuery(isNewImageDataModelEnabled), { + >(imageV2VulnerabilitiesQuery, { variables: { id: imageId, query: getVulnStateScopedQueryString( @@ -209,9 +169,6 @@ function ImagePageVulnerabilities({ }, }); - const imageData = - (data && (isNewImageDataModelEnabled ? data.imageV2 : data.image)) || undefined; - const isFiltered = getHasSearchApplied(querySearchFilter); const selectedCves = useMap(); @@ -230,7 +187,7 @@ function ImagePageVulnerabilities({ const tableState = getTableUIState({ isLoading: loading, - data: imageData?.imageVulnerabilities, + data: data?.imageV2.imageVulnerabilities, error, searchFilter, }); @@ -274,7 +231,7 @@ function ImagePageVulnerabilities({ const hiddenSeverities = getHiddenSeverities(querySearchFilter); const hiddenStatuses = getHiddenStatuses(querySearchFilter); - const totalVulnerabilityCount = imageData?.imageVulnerabilityCount ?? 0; + const totalVulnerabilityCount = data?.imageV2?.imageVulnerabilityCount ?? 0; return ( <> @@ -330,9 +287,7 @@ function ImagePageVulnerabilities({ trackAppliedFilter(WORKLOAD_CVE_FILTER_APPLIED, searchPayload); }} additionalContextFilter={{ - ...(isNewImageDataModelEnabled - ? { 'Image ID': imageId } - : { 'Image SHA': imageId }), + 'Image ID': imageId, ...baseSearchFilter, }} > @@ -340,7 +295,7 @@ function ImagePageVulnerabilities({ ( ( , 'variables'>; }) { - const { isFeatureFlagEnabled } = useFeatureFlags(); - const isNewImageDataModelEnabled = isFeatureFlagEnabled('ROX_FLATTEN_IMAGE_DATA'); const { page, perPage } = pagination; return useQuery<{ images: Image[]; - }>(isNewImageDataModelEnabled ? imageV2ListQuery : imageListQuery, { + }>(imageV2ListQuery, { variables: { query, pagination: getPaginationParams({ page, perPage, sortOption }), diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/ImageDetailBadges.tsx b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/ImageDetailBadges.tsx index 3db64197881fa..70314f9d39851 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/ImageDetailBadges.tsx +++ b/ui/apps/platform/src/Containers/Vulnerabilities/WorkloadCves/components/ImageDetailBadges.tsx @@ -31,40 +31,6 @@ export type ImageDetails = { baseImage: BaseImage | null; }; -export const imageDetailsFragment = gql` - fragment ImageDetails on Image { - deploymentCount - operatingSystem - metadata { - v1 { - created - } - } - dataSource { - name - } - scanTime - scanNotes - notes - signatureCount - signatureVerificationData { - results { - description - status - verificationTime - verifiedImageReferences - verifierId - verifierName - } - } - baseImage { - imageSha - names - created - } - } -`; - export const imageV2DetailsFragment = gql` fragment ImageV2Details on ImageV2 { deploymentCount diff --git a/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useInvalidateVulnerabilityQueries.ts b/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useInvalidateVulnerabilityQueries.ts index c36336fc21d36..42685413a3a1c 100644 --- a/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useInvalidateVulnerabilityQueries.ts +++ b/ui/apps/platform/src/Containers/Vulnerabilities/hooks/useInvalidateVulnerabilityQueries.ts @@ -7,7 +7,7 @@ import { imageListQuery } from '../WorkloadCves/Tables/ImageOverviewTable'; import { cveListQuery } from '../WorkloadCves/Tables/WorkloadCVEOverviewTable'; import { deploymentListQuery } from '../WorkloadCves/Tables/DeploymentOverviewTable'; import { deploymentVulnerabilitiesQuery } from '../WorkloadCves/Deployment/DeploymentPageVulnerabilities'; -import { imageVulnerabilitiesQuery } from '../WorkloadCves/Image/ImagePageVulnerabilities'; +import { imageV2VulnerabilitiesQuery } from '../WorkloadCves/Image/ImagePageVulnerabilities'; import { imageCveAffectedDeploymentsQuery, imageCveAffectedImagesQuery, @@ -41,7 +41,7 @@ const queryFieldNames = uniq( imageCveAffectedDeploymentsQuery, imageCveAffectedImagesQuery, // Query for the Workload CVE image detail page - imageVulnerabilitiesQuery, + imageV2VulnerabilitiesQuery, // Query for the Workload CVE deployment detail page deploymentVulnerabilitiesQuery, ].flatMap(extractTopLevelFieldNames) diff --git a/ui/apps/platform/src/types/featureFlag.ts b/ui/apps/platform/src/types/featureFlag.ts index ed63d06aee6b8..127a7bfe3c20a 100644 --- a/ui/apps/platform/src/types/featureFlag.ts +++ b/ui/apps/platform/src/types/featureFlag.ts @@ -5,7 +5,6 @@ export type FeatureFlagEnvVar = | 'ROX_CVE_FIX_TIMESTAMP' | 'ROX_DEPRECATED_COMPLIANCE_DASHBOARD' | 'ROX_EVALUATION_FILTER' - | 'ROX_FLATTEN_IMAGE_DATA' | 'ROX_INIT_CONTAINER_SUPPORT' | 'ROX_LABEL_BASED_POLICY_SCOPING' | 'ROX_LEGACY_SCANNER' From 0a18862cb4ed2614a24ffd8b6791b1226b63c9da Mon Sep 17 00:00:00 2001 From: David Caravello <119438707+dcaravel@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:18:43 -0500 Subject: [PATCH 432/638] ROX-28254: always forward alert-removal notifications (#22948) --- .../alertmanager/alert_manager_impl_test.go | 41 ++++++ sensor/common/deduper/deduper.go | 9 +- sensor/common/deduper/runtime_removal_test.go | 117 ++++++++++++++++++ 3 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 sensor/common/deduper/runtime_removal_test.go diff --git a/central/detection/alertmanager/alert_manager_impl_test.go b/central/detection/alertmanager/alert_manager_impl_test.go index d5aadcd1eb135..d31dc247d79f5 100644 --- a/central/detection/alertmanager/alert_manager_impl_test.go +++ b/central/detection/alertmanager/alert_manager_impl_test.go @@ -730,6 +730,47 @@ func (suite *AlertManagerTestSuite) TestDeploymentMarkedInactiveOnRemoval() { suite.True(modified.Contains("dep-to-remove"), "removed deployment should appear in modified set") } +func (suite *AlertManagerTestSuite) TestRepeatedDeploymentRemovalPreservesRuntimeAlert() { + stored := &storage.Alert{ + Id: "runtime-alert-1", + Policy: fixtures.GetNetworkFlowPolicy(), + Entity: &storage.Alert_Deployment_{Deployment: &storage.Alert_Deployment{Id: "dep-to-remove"}}, + LifecycleStage: storage.LifecycleStage_RUNTIME, + State: storage.ViolationState_ACTIVE, + Time: protocompat.GetProtoTimestampFromSeconds(100), + } + suite.Require().NoError(suite.policySet.UpsertPolicy(stored.GetPolicy())) + suite.runtimeDetectorMock.EXPECT().PolicySet().Return(suite.policySet).AnyTimes() + + // Each removal reads the current persisted state. Only the first should write. + suite.alertsMock.EXPECT().SearchAlertMatchKeys(suite.ctx, gomock.Any(), true). + DoAndReturn(func(context.Context, *v1.Query, bool) ([]*alertviews.AlertMatchKey, error) { + return alertsToMatchKeys([]*storage.Alert{stored}), nil + }).Times(2) + suite.alertsMock.EXPECT().SearchRawAlerts(suite.ctx, gomock.Any(), false). + Return([]*storage.Alert{stored.CloneVT()}, nil) + suite.alertsMock.EXPECT().UpsertAlert(suite.ctx, gomock.Any()). + DoAndReturn(func(_ context.Context, a *storage.Alert) error { + suite.True(a.GetDeployment().GetInactive()) + suite.Equal(storage.ViolationState_ACTIVE, a.GetState()) + stored = a.CloneVT() + return nil + }) + suite.notifierMock.EXPECT().ProcessAlert(gomock.Any(), gomock.Any()).Return() + + modified, err := suite.alertManager.AlertAndNotify(suite.ctx, nil, + WithDeploymentID("dep-to-remove", true)) + suite.Require().NoError(err) + suite.True(modified.Contains("dep-to-remove")) + + modified, err = suite.alertManager.AlertAndNotify(suite.ctx, nil, + WithDeploymentID("dep-to-remove", true)) + suite.Require().NoError(err) + suite.Empty(modified.AsSlice()) + suite.True(stored.GetDeployment().GetInactive()) + suite.Equal(storage.ViolationState_ACTIVE, stored.GetState()) +} + // TestNewRuntimeAlertMarkedInactiveWhenDeploymentGone covers a first runtime // alert arriving after the deployment is already gone. func (suite *AlertManagerTestSuite) TestNewRuntimeAlertMarkedInactiveWhenDeploymentGone() { diff --git a/sensor/common/deduper/deduper.go b/sensor/common/deduper/deduper.go index 3e8734f205ef6..d33c8379be41c 100644 --- a/sensor/common/deduper/deduper.go +++ b/sensor/common/deduper/deduper.go @@ -80,11 +80,12 @@ func (d *deduper) Send(msg *central.MsgFromSensor) error { ResourceType: reflect.TypeOf(event.GetResource()), } if event.GetAction() == central.ResourceAction_REMOVE_RESOURCE { - priorLen := len(d.lastSent) + _, previouslySent := d.lastSent[key] delete(d.lastSent, key) - // Do not send a remove message for something that has not been seen before - // This also effectively dedupes REMOVE actions - if priorLen == len(d.lastSent) { + // Runtime alerts bypass lastSent, so alert removals must reach Central + // even if no deploy-time result has been sent yet. Other resource + // removals can be dropped if the resource was never sent or already removed. + if !previouslySent && event.GetAlertResults() == nil { return nil } if err := d.stream.Send(msg); err != nil { diff --git a/sensor/common/deduper/runtime_removal_test.go b/sensor/common/deduper/runtime_removal_test.go new file mode 100644 index 0000000000000..3525142767491 --- /dev/null +++ b/sensor/common/deduper/runtime_removal_test.go @@ -0,0 +1,117 @@ +package deduper + +import ( + "testing" + + "github.com/stackrox/rox/generated/internalapi/central" + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/require" +) + +func TestRuntimeAlertRemovalIsForwarded(t *testing.T) { + cases := map[string]bool{ + "runtime alert only": false, + "deploy result before runtime alert": true, + } + + for name, sendDeployResult := range cases { + t.Run(name, func(t *testing.T) { + downstream := new(fakeStream) + stream := NewDedupingMessageStream(downstream, nil, false) + + makeMessage := func(action central.ResourceAction, stage storage.LifecycleStage) *central.MsgFromSensor { + return ¢ral.MsgFromSensor{ + Msg: ¢ral.MsgFromSensor_Event{ + Event: ¢ral.SensorEvent{ + Id: "deployment-1", + Action: action, + Resource: ¢ral.SensorEvent_AlertResults{ + AlertResults: ¢ral.AlertResults{ + DeploymentId: "deployment-1", + Stage: stage, + }, + }, + }, + }, + } + } + + if sendDeployResult { + msg := makeMessage(central.ResourceAction_CREATE_RESOURCE, storage.LifecycleStage_DEPLOY) + require.NoError(t, stream.Send(msg)) + require.Len(t, downstream.orderedMessages, 1) + } + + runtime := makeMessage(central.ResourceAction_CREATE_RESOURCE, storage.LifecycleStage_RUNTIME) + runtime.GetEvent().GetAlertResults().Alerts = []*storage.Alert{ + { + Id: "runtime-alert-1", + LifecycleStage: storage.LifecycleStage_RUNTIME, + State: storage.ViolationState_ACTIVE, + }, + } + beforeRuntime := len(downstream.orderedMessages) + require.NoError(t, stream.Send(runtime)) + require.Len(t, downstream.orderedMessages, beforeRuntime+1) + + // Sensor's removal message is empty and has the default lifecycle + // stage, DEPLOY, even when the deployment has runtime alerts. + removal := makeMessage(central.ResourceAction_REMOVE_RESOURCE, storage.LifecycleStage_DEPLOY) + beforeRemoval := len(downstream.orderedMessages) + require.NoError(t, stream.Send(removal)) + require.Len(t, downstream.orderedMessages, beforeRemoval+1, + "alert removal must reach Central even without a prior deploy-time result") + require.Same(t, removal, downstream.orderedMessages[len(downstream.orderedMessages)-1]) + + // Repeated alert removals must also reach Central. + require.NoError(t, stream.Send(removal)) + require.Len(t, downstream.orderedMessages, beforeRemoval+2) + require.Same(t, removal, downstream.orderedMessages[len(downstream.orderedMessages)-1]) + + // Removal must clear any cached deploy-time result. + if sendDeployResult { + msg := makeMessage(central.ResourceAction_CREATE_RESOURCE, storage.LifecycleStage_DEPLOY) + require.NoError(t, stream.Send(msg)) + require.Len(t, downstream.orderedMessages, beforeRemoval+3) + } + }) + } +} + +func TestDeploymentRemovalDeduplication(t *testing.T) { + cases := map[string]bool{ + "previously sent": true, + "never sent": false, + } + for name, previouslySent := range cases { + t.Run(name, func(t *testing.T) { + downstream := new(fakeStream) + stream := NewDedupingMessageStream(downstream, nil, false) + msg := ¢ral.MsgFromSensor{ + Msg: ¢ral.MsgFromSensor_Event{ + Event: ¢ral.SensorEvent{ + Id: "deployment-1", + Action: central.ResourceAction_CREATE_RESOURCE, + Resource: ¢ral.SensorEvent_Deployment{ + Deployment: &storage.Deployment{Id: "deployment-1"}, + }, + }, + }, + } + if previouslySent { + require.NoError(t, stream.Send(msg)) + require.Len(t, downstream.orderedMessages, 1) + } + removal := msg.CloneVT() + removal.GetEvent().Action = central.ResourceAction_REMOVE_RESOURCE + require.NoError(t, stream.Send(removal)) + require.NoError(t, stream.Send(removal)) + if previouslySent { + require.Len(t, downstream.orderedMessages, 2) + require.Same(t, removal, downstream.orderedMessages[1]) + } else { + require.Empty(t, downstream.orderedMessages) + } + }) + } +} From c66b68a6d66bb0db967d1cfa205bdd69e3e13d6e Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 02:49:33 +0000 Subject: [PATCH 433/638] chore(deps): update all dependencies (#23017) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 4 ++-- image/postgres/konflux.Dockerfile | 2 +- image/rhel/konflux.Dockerfile | 4 ++-- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/db/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 572e070cb4e13..d92e856bd45cc 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:8ebe2ad8fdf3cab3e5a53c1edc69194c98209cfadab24b884f4ad9ebcf7bbbfc + image: registry.redhat.io/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 7bed9f7f584a3..18e34cc099091 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:8ebe2ad8fdf3cab3e5a53c1edc69194c98209cfadab24b884f4ad9ebcf7bbbfc + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 imagePullPolicy: IfNotPresent script: | #!/bin/bash @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1789950433@sha256:15c3098dc4639e8a0e6a1bc77b50964513a1d76dccae4b07b9808101c5addd04 + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1790174511@sha256:0a4666f7a4eb0644c97a73cba198eb268691b270d97831822689e7a2088f87be imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index 1e410bbabf9ed..8b0d612e9bb2b 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:905a50c33fb9d80b940727c88bab09b9d1a15f3b1c9bc2411b7aea6142f92922 AS final +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:962fd39f9c86327d685372216b5a055c8f1748abc5c1e7a5103f8cbf8df3a01a AS final USER root diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index e441fdf710356..1c1e55031b641 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -49,7 +49,7 @@ RUN arch=$(uname -m) ; \ /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:bb046ef111dd20f890fe9879a37df8e829ed661177fae2339ba7eeb495f46c60 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:ef4f18a7e736696b1314bdc21bcf57bfd50ba3ea4f80c13833f777316b25c035 as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -73,7 +73,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 38f16e0d511cc..29f7530c67be3 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index 205f6b635414e..c6eb62708c6a2 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index 4295dae3b50f3..af8d11f490e67 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:905a50c33fb9d80b940727c88bab09b9d1a15f3b1c9bc2411b7aea6142f92922 +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:962fd39f9c86327d685372216b5a055c8f1748abc5c1e7a5103f8cbf8df3a01a ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index d09e61ea83aa6..3dbf15bb4879f 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:a4b9ec09b1e790a53ef25b7777c539976abe519248264298e5194dcbceac8c31 AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer COPY --from=ubi-micro-base / /out/ From de27b66840c839b94a77e01f131aabaf768a2541 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 03:01:37 +0000 Subject: [PATCH 434/638] chore(deps): refresh rpm lockfiles (#23112) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 168 ++++++++++++++++++++++++------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index e8926144697aa..eaaf8f5d53047 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2916797 - checksum: sha256:2b5063d95b2c1611a168cdf1dae583a082940b9074c14e85bad921ee132b46fb + size: 2925601 + checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 name: kernel-headers - evr: 5.14.0-687.52.1.el9_8 - sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm + evr: 5.14.0-687.53.1.el9_8 + sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -340,13 +340,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 122400 - checksum: sha256:df004398da989f75bae9267cfb0ad402ada6e6db46ebef0932c869894c013c08 + size: 122957 + checksum: sha256:d13bd77f429835b303d388e24811fb935060be4788c8fe4cf87703640f2337e0 name: expat - evr: 2.5.0-6.el9_8.3 - sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/f/filesystem-3.16-5.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 5003914 @@ -1214,12 +1214,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.5.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 8403095 - checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 + size: 8411609 + checksum: sha256:38d7acfd1130c02c6a00010159a1350ca0f56cd7ed64f3d8394aa5b3d6512ddd name: expat - evr: 2.5.0-6.el9_8.3 + evr: 2.5.0-6.el9_8.5 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 20486 @@ -1286,12 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152521141 - checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 + size: 152530420 + checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a name: kernel - evr: 5.14.0-687.52.1.el9_8 + evr: 5.14.0-687.53.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/52e584b12993891100ca340d6e265dc5402bb7ebdf9454c499a2d73f26886080-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 67183 - checksum: sha256:52e584b12993891100ca340d6e265dc5402bb7ebdf9454c499a2d73f26886080 + size: 68829 + checksum: sha256:80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1711,13 +1711,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2940597 - checksum: sha256:8dff161bb85c25e90d5540b565952a0abedb0cab3b926da46e3ba15806677490 + size: 2949645 + checksum: sha256:7318493a0814cea968050892155214dc61c91549fbea2f66aa4008be389ace7c name: kernel-headers - evr: 5.14.0-687.52.1.el9_8 - sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm + evr: 5.14.0-687.53.1.el9_8 + sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -1977,13 +1977,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 133470 - checksum: sha256:731fb187d6550a4a5fc6227d43370464d67a68e9fb41481c2e86b95e4560345b + size: 133703 + checksum: sha256:f379b66b57d67569fabaa4cecc75d7ed26b1c46733b626f3550fc31e740761af name: expat - evr: 2.5.0-6.el9_8.3 - sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/f/filesystem-3.16-5.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 5003944 @@ -2858,12 +2858,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.5.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 8403095 - checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 + size: 8411609 + checksum: sha256:38d7acfd1130c02c6a00010159a1350ca0f56cd7ed64f3d8394aa5b3d6512ddd name: expat - evr: 2.5.0-6.el9_8.3 + evr: 2.5.0-6.el9_8.5 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 20486 @@ -2930,12 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152521141 - checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 + size: 152530420 + checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a name: kernel - evr: 5.14.0-687.52.1.el9_8 + evr: 5.14.0-687.53.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/b6665d455966c348ea2e41ba6e1a1b9c9a87cf956694754d7198540e5be3d4a1-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 68368 - checksum: sha256:b6665d455966c348ea2e41ba6e1a1b9c9a87cf956694754d7198540e5be3d4a1 + size: 70701 + checksum: sha256:008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3361,13 +3361,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2946745 - checksum: sha256:df7aa11358870f2b9e0e27bd07827ba0cdc54c93128adb11898636a31861da94 + size: 2955557 + checksum: sha256:9b1a97d19e52dfb5387cae5fd1a7888ce3e1f794b17117ef4cc1eeb1daca8aeb name: kernel-headers - evr: 5.14.0-687.52.1.el9_8 - sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm + evr: 5.14.0-687.53.1.el9_8 + sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -3627,13 +3627,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 124579 - checksum: sha256:e6bd000496dc070c1e4cfd17162d512111341820f12c10442b8752c0767c68a0 + size: 125167 + checksum: sha256:f7480b168cba79b9f30bc54faf1c43b9eab1cd3d9701c31f601b40e472659981 name: expat - evr: 2.5.0-6.el9_8.3 - sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/f/filesystem-3.16-5.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 5003897 @@ -4495,12 +4495,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.5.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 8403095 - checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 + size: 8411609 + checksum: sha256:38d7acfd1130c02c6a00010159a1350ca0f56cd7ed64f3d8394aa5b3d6512ddd name: expat - evr: 2.5.0-6.el9_8.3 + evr: 2.5.0-6.el9_8.5 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 20486 @@ -4567,12 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152521141 - checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 + size: 152530420 + checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a name: kernel - evr: 5.14.0-687.52.1.el9_8 + evr: 5.14.0-687.53.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/6932c6640f14bb347e360c92a27479a02b616928e7675123cc01ecb870ae20db-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 64964 - checksum: sha256:6932c6640f14bb347e360c92a27479a02b616928e7675123cc01ecb870ae20db + size: 65924 + checksum: sha256:dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4999,13 +4999,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.52.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2956089 - checksum: sha256:97fdbd1cd14ee0a1f5818edc96e0c3e0f1ebfcaacdd7c63f33d2b791ea177f94 + size: 2965145 + checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 name: kernel-headers - evr: 5.14.0-687.52.1.el9_8 - sourcerpm: kernel-5.14.0-687.52.1.el9_8.src.rpm + evr: 5.14.0-687.53.1.el9_8 + sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5251,13 +5251,13 @@ arches: name: elfutils-libs evr: 0.194-1.el9 sourcerpm: elfutils-0.194-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.3.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/e/expat-2.5.0-6.el9_8.5.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 128577 - checksum: sha256:3c9c96529f94f84fc19c8039d8852269e1085d16b5af2933f44ff286dec66a35 + size: 129088 + checksum: sha256:e6e7edd632fdd1dc4ad94b19b7ab88646f3ef8c1c7956bac58985c3e7118a362 name: expat - evr: 2.5.0-6.el9_8.3 - sourcerpm: expat-2.5.0-6.el9_8.3.src.rpm + evr: 2.5.0-6.el9_8.5 + sourcerpm: expat-2.5.0-6.el9_8.5.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/f/filesystem-3.16-5.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 5003807 @@ -6118,12 +6118,12 @@ arches: checksum: sha256:643279e796ded16c5be5cdc2a91839c787e62e59757008675eb0580a4a42af21 name: elfutils evr: 0.194-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.3.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/e/expat-2.5.0-6.el9_8.5.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 8403095 - checksum: sha256:0cff2a2213384dc43eb1874bf6e77b2b8230be848f50387d8057e0e6cde3d392 + size: 8411609 + checksum: sha256:38d7acfd1130c02c6a00010159a1350ca0f56cd7ed64f3d8394aa5b3d6512ddd name: expat - evr: 2.5.0-6.el9_8.3 + evr: 2.5.0-6.el9_8.5 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/f/filesystem-3.16-5.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 20486 @@ -6190,12 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.52.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152521141 - checksum: sha256:4b1634381a2ca00b3d30051c32db665b5b5ea9440641e06c93f855105846e306 + size: 152530420 + checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a name: kernel - evr: 5.14.0-687.52.1.el9_8 + evr: 5.14.0-687.53.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/d6a984811cecd6e42c3cfecf4be2d1281ed1bf4913f95a424b2bcec27cad24c8-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 70416 - checksum: sha256:d6a984811cecd6e42c3cfecf4be2d1281ed1bf4913f95a424b2bcec27cad24c8 + size: 72089 + checksum: sha256:c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512 From 5759d42b5ad80989efdaa00aa571b219c84483dc Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Tue, 29 Sep 2026 06:38:40 +0200 Subject: [PATCH 435/638] tests(nongroovy-e2e): use roxie (#22470) Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/e2e-nongroovy-tests.yaml | 4 +- tests/e2e/lib-compat.sh | 44 ++++++++++++++++ tests/e2e/lib.sh | 58 +++++++++++++--------- tests/e2e/run.sh | 26 +++++++++- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index 5638d8ed53699..d7a1cead3ca7d 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -153,13 +153,11 @@ jobs: timeout-minutes: 245 run: tests/e2e/run.sh /tmp/e2e-test-logs - - name: Persist env vars for post-test + - name: Ensure central stability before post-test if: always() run: | source tests/e2e/lib.sh wait_for_api - echo "API_ENDPOINT=${API_ENDPOINT}" >> "$GITHUB_ENV" - echo "ROX_ADMIN_PASSWORD=$(cat deploy/k8s/central-deploy/password)" >> "$GITHUB_ENV" - name: Run post-test if: always() diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index 85b686fcbfec2..d5ff29a6dbc76 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -94,6 +94,8 @@ roxie_config_from_environment_compat() { info "Configuring load balancer..." handle_load_balancer_setting "$config_file" + handle_endpoints_for_test "$config_file" # Echoes info internally. + info "Configuring custom central environment..." while read -r var_val; do local name="${var_val%%=*}" @@ -211,6 +213,10 @@ collect_feature_flags() { env_with_default ROX_POLICY_CRITERIA_MODAL "true" env_with_default ROX_VULN_MGMT_LEGACY_SNOOZE "true" env_with_default ROX_NETWORK_GRAPH_AGGREGATE_EXT_IPS "true" + env_with_default ROX_DEPRECATED_COMPLIANCE_DASHBOARD "true" + env_with_default ROX_UI_SECRETS_PAGE_MIGRATION "true" + env_with_default ROX_AI_INTEGRATIONS "true" + env_with_default ROX_LIGHTSPEED_RISK_SUMMARY "true" # Enabled by default in StackRox, but disabled by default for test deployments. env_with_default ROX_NETWORK_GRAPH_EXTERNAL_IPS "false" @@ -366,6 +372,44 @@ handle_load_balancer_setting() { esac } +# Populate a roxie config file with the endpoint settings required by endpoints_test.go: +# plaintext endpoints (8080/8081) and the full central-endpoints ConfigMap (8082, 8444-8448). +# Values are taken from the ROX_PLAINTEXT_ENDPOINTS and ROXDEPLOY_CONFIG_FILE_MAP env vars, +# which are exported by test_preamble() of the jobs that run endpoints_test.go. Jobs that do +# not set these are left untouched, so this is safe to call unconditionally from such a job. +handle_endpoints_for_test() { + local config_file="$1" + + # Expose plaintext endpoints required by endpoints_test.go. + if [[ -n "${ROX_PLAINTEXT_ENDPOINTS:-}" ]]; then + info "Configuring central ROX_PLAINTEXT_ENDPOINTS to ${ROX_PLAINTEXT_ENDPOINTS}..." + set_custom_env "$config_file" "central" "ROX_PLAINTEXT_ENDPOINTS" "${ROX_PLAINTEXT_ENDPOINTS}" + fi + + # Inject the full endpoint config into the central-endpoints ConfigMap so that + # Central also listens on ports 8082 and 8444-8448 (used by endpoints_test.go). + if [[ -n "${ROXDEPLOY_CONFIG_FILE_MAP:-}" && -f "${ROXDEPLOY_CONFIG_FILE_MAP}" ]]; then + info "Configuring central-endpoints ConfigMap from ${ROXDEPLOY_CONFIG_FILE_MAP}..." + local overlay_tmp; overlay_tmp="$(mktemp)" + # \. in the path escapes the dot so the operator treats "endpoints.yaml" as a + # single ConfigMap key rather than a path separator. verbatim preserves newlines. + cat > "$overlay_tmp" <<'OVERLAY' +central: + spec: + overlays: + - apiVersion: v1 + kind: ConfigMap + name: central-endpoints + patches: + - path: data.endpoints\.yaml + verbatim: | +OVERLAY + sed 's/^/ /' "${ROXDEPLOY_CONFIG_FILE_MAP}" >> "$overlay_tmp" + merge_yaml "$config_file" < "$overlay_tmp" + rm -f "$overlay_tmp" + fi +} + handle_declarative_configuration() { local config_file="$1" merge_yaml "$config_file" <_/roxie and put it +# ahead of any pre-installed roxie on PATH. +ensure_roxie_on_path() { + local os arch + case "$(uname -s)" in + Linux*) os="linux" ;; + Darwin*) os="darwin" ;; + *) die "Unsupported operating system: $(uname -s)" ;; + esac + case "$(uname -m)" in + x86_64) arch="amd64" ;; + arm64|aarch64) arch="arm64" ;; + *) die "Unsupported architecture: $(uname -m)" ;; + esac + + # Triggers the download/install of the pinned roxie version if it is not present yet. + "$ROOT/scripts/roxie.sh" version + + # Put ONLY roxie at the front of PATH, via a dedicated directory. We must not prepend + # "$ROOT/bin/${os}_${arch}" directly: it also contains roxctl, which the bats tests move out + # of that directory mid-run, after which bare `roxctl` would resolve to the now-missing path + # (instead of the stable /usr/local/bin/roxctl copy) and later phases (e.g. proxy tests) fail. + local roxie_bindir; roxie_bindir="$(mktemp -d)" + ln -sf "$ROOT/bin/${os}_${arch}/roxie" "${roxie_bindir}/roxie" + export PATH="${roxie_bindir}:$PATH" + + check_for_roxie +} + # Deploy StackRox using roxie. # # This is the preferred way of deploying StackRox for tests as of 2026Q2. @@ -301,36 +333,14 @@ EOF set_custom_env "$config_file" "securedCluster" "ROX_NODE_SCANNING_INTERVAL" "10m" set_custom_env "$config_file" "securedCluster" "ROX_NODE_SCANNING_INTERVAL_DEVIATION" "60s" - # Expose plaintext endpoints required by endpoints_test.go. - set_custom_env "$config_file" "central" "ROX_PLAINTEXT_ENDPOINTS" "8080,grpc@8081" + # Configure the endpoints required by endpoints_test.go. + handle_endpoints_for_test "$config_file" # Speed up baseline generation so TestPod can observe process events within the test window. # The default is 1h; tests time out long before baselines would be generated. set_custom_env "$config_file" "central" "ROX_BASELINE_GENERATION_DURATION" "1m" set_custom_env "$config_file" "central" "ROX_NETWORK_BASELINE_OBSERVATION_PERIOD" "2m" - # Inject the full endpoint config into the central-endpoints ConfigMap so that - # Central also listens on ports 8082 and 8444-8448 (used by endpoints_test.go). - if [[ -n "${ROXDEPLOY_CONFIG_FILE_MAP:-}" && -f "${ROXDEPLOY_CONFIG_FILE_MAP}" ]]; then - local overlay_tmp; overlay_tmp="$(mktemp)" - # \. in the path escapes the dot so the operator treats "endpoints.yaml" as a - # single ConfigMap key rather than a path separator. verbatim preserves newlines. - cat > "$overlay_tmp" <<'OVERLAY' -central: - spec: - overlays: - - apiVersion: v1 - kind: ConfigMap - name: central-endpoints - patches: - - path: data.endpoints\.yaml - verbatim: | -OVERLAY - sed 's/^/ /' "${ROXDEPLOY_CONFIG_FILE_MAP}" >> "$overlay_tmp" - merge_yaml "$config_file" < "$overlay_tmp" - rm -f "$overlay_tmp" - fi - # Add the test CA so Central accepts client-cert auth during endpoints_test.go. if [[ -n "${TRUSTED_CA_FILE:-}" && -f "${TRUSTED_CA_FILE}" ]]; then local trusted_ca_content diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index 7d53d1e42a9d7..17557806f0f44 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -35,6 +35,7 @@ test_e2e() { setup_deployment_env false false remove_existing_stackrox_resources setup_default_TLS_certs + setup_client_TLS_certs info "Creating mocked compliance operator data for compliance v1 tests" "$ROOT/tests/complianceoperator/create.sh" kubectl get compliancecheckresults.compliance.openshift.io -n openshift-compliance @@ -43,7 +44,30 @@ test_e2e() { # If deploy_optional_e2e_components is called after deploy_stackrox it causes an unnecessary Sensor restart deploy_optional_e2e_components - deploy_stackrox + + # Make sure we use the roxie version pinned in ROXIE_VERSION. Under Prow the test image + # ships an older roxie that does not recognize a generic (non-Infra) GKE cluster: it reports + # "cluster type: Unknown" and defaults Central exposure to a localhost port-forward instead of + # the LoadBalancer. That breaks endpoints_test.go, which dials all of Central's ports at the + # API host. The pinned version detects GKE and exposes Central via the LoadBalancer, matching + # the GHA runner (which installs the pinned version explicitly). + ensure_roxie_on_path + + local roxie_config + roxie_config="$(mktemp)" + merge_yaml "$roxie_config" <<'EOF' +central: + namespace: stackrox + pauseReconciliation: true + resourceProfile: ci +securedCluster: + namespace: stackrox + pauseReconciliation: true + resourceProfile: ci +EOF + + deploy_stackrox_with_roxie_compat "$roxie_config" + rm -f "$roxie_config" # Background streamers are not explicitly stopped. They die when the CI # runner terminates, same as the port-forward processes in setup_proxy_tests. From 63e80e0957760afe214c025114457546fc29bcac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:20:20 +0200 Subject: [PATCH 436/638] chore(deps): bump github.com/sigstore/sigstore-go from 1.2.2 to 1.3.0 (#23119) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6f16abd351e89..affe1a8055e15 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( github.com/sigstore/cosign/v3 v3.1.3 github.com/sigstore/rekor v1.5.4 github.com/sigstore/sigstore v1.10.9 - github.com/sigstore/sigstore-go v1.2.2 + github.com/sigstore/sigstore-go v1.3.0 github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 @@ -288,19 +288,19 @@ require ( github.com/go-openapi/jsonpointer v1.0.0 // indirect github.com/go-openapi/jsonreference v1.0.0 // indirect github.com/go-openapi/loads v0.25.1 // indirect - github.com/go-openapi/runtime v0.32.5 // indirect + github.com/go-openapi/runtime v0.33.0 // indirect github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect github.com/go-openapi/spec v0.22.9 // indirect github.com/go-openapi/strfmt v0.27.0 // indirect github.com/go-openapi/swag v0.26.1 // indirect - github.com/go-openapi/swag/cmdutils v0.26.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.0 // indirect github.com/go-openapi/swag/conv v0.28.0 // indirect github.com/go-openapi/swag/fileutils v0.28.0 // indirect github.com/go-openapi/swag/jsonname v0.26.1 // indirect github.com/go-openapi/swag/jsonutils v0.28.0 // indirect github.com/go-openapi/swag/loading v0.28.0 // indirect github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/netutils v0.26.1 // indirect + github.com/go-openapi/swag/netutils v0.27.0 // indirect github.com/go-openapi/swag/pools v0.28.0 // indirect github.com/go-openapi/swag/stringutils v0.28.0 // indirect github.com/go-openapi/swag/typeutils v0.28.0 // indirect @@ -410,7 +410,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect github.com/sigstore/rekor-tiles/v2 v2.3.0 // indirect - github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect + github.com/sigstore/timestamp-authority/v2 v2.1.3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect diff --git a/go.sum b/go.sum index 08d7c0c89b8ff..5dccb1493f47b 100644 --- a/go.sum +++ b/go.sum @@ -539,8 +539,8 @@ github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkr github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= github.com/go-openapi/loads v0.25.1 h1:toKQdIDLxlqfKLLGUUmUsiTd5/X0Chzvde9EGYQP/Ac= github.com/go-openapi/loads v0.25.1/go.mod h1:33Hen4tsKXHL45TyYojvfD5fZUFN4O1y4r/XhsRW2zc= -github.com/go-openapi/runtime v0.32.5 h1:+aDmMj6xB4P8isUwEOERtgHW3JNI/i93KvTWNa4LRik= -github.com/go-openapi/runtime v0.32.5/go.mod h1:eNPUCk8LAYV803GHJP3xDSNllJQfv+pBlXpFfWqpPO8= +github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU= +github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw= github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= @@ -554,8 +554,8 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= -github.com/go-openapi/swag/cmdutils v0.26.1 h1:f2iE1ijYaJ3nuu5PaEMx3zpEhzhZFgivCJObWEObLIQ= -github.com/go-openapi/swag/cmdutils v0.26.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y= +github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= @@ -570,8 +570,8 @@ github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQv github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.26.1 h1:BNctoc39WTAUMxyAs355fExOPzMZtPbZ0ZZ1Am2FR5M= -github.com/go-openapi/swag/netutils v0.26.1/go.mod h1:y02vByhZhQPAVwOX+0KipXFZ/hUbk6G/Enhf5rGaOkQ= +github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8= +github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= @@ -1240,8 +1240,8 @@ github.com/sigstore/rekor-tiles/v2 v2.3.0 h1:HhMgH61UP0t899V8Fjt7pz1YdgOBptbaQdn github.com/sigstore/rekor-tiles/v2 v2.3.0/go.mod h1:DEFiKSyQ4nF75QRVNdOPaIH3cmvMkO2B6xDZjNYngPc= github.com/sigstore/sigstore v1.10.9 h1:7Dcpt+ibnltHQZ8XhaU0dFmhHaf/T491eJfA9WDex4Y= github.com/sigstore/sigstore v1.10.9/go.mod h1:LYW9+qH7bK8wZmLm6lPxIC5lkHtkJDCgkqjChzTAIBs= -github.com/sigstore/sigstore-go v1.2.2 h1:xAJ8hxaoecC0HKBYVbrwUjkeAI+GJYu6vLqbxDlD2Q0= -github.com/sigstore/sigstore-go v1.2.2/go.mod h1:MIFwBxAHJD+/lKgZzt9n/4Zhq/3T2+EuGX8iGrIsZgU= +github.com/sigstore/sigstore-go v1.3.0 h1:hnIMHREyCNTYFtOE1o7ae3Axa9B5W5EjUSBJICP2NBE= +github.com/sigstore/sigstore-go v1.3.0/go.mod h1:AyRQXfpH89py1twjE3kEZxlRersng90GSYqQV9zGJE8= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.9 h1:2NWAU/utJRwzy1OrQurlY3wlDSynyH1rNJS5GgFchrg= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.9/go.mod h1:2shgYiPHTzeb5NNEu6okco+LOojMS5n9p/5Wdp0jhGI= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.9 h1:zcg/EG6JW/8RSx9T2g5j6zqxplaE4dymmhiFflDE6ks= @@ -1250,8 +1250,8 @@ github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.9 h1:DG8R4orWDs3l9trp70 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.9/go.mod h1:d6oIwQ0YiYHMcFO4xqFcAx5njXwIf0dm5KfdytOar8A= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.9 h1:V+spyeSHypvLHSOvPMT+V72dxDlS0LDkDd6lwCsUOXM= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.9/go.mod h1:o7u5bQNLqpDQFaST29dpo6P6ihMV+wicwqfMmMW6H3c= -github.com/sigstore/timestamp-authority/v2 v2.1.2 h1:7DDhnknLL4w8VwomyvW2W8qblOS9LDR8oihna+jc7Ls= -github.com/sigstore/timestamp-authority/v2 v2.1.2/go.mod h1:o6rAVZceFyejClIj/uStRNIemP16bVMZtbMmhk6pr0U= +github.com/sigstore/timestamp-authority/v2 v2.1.3 h1:Fc+LjCTfik1lh3YLkaosENfkXa3R2Y1nswiUKutBdFA= +github.com/sigstore/timestamp-authority/v2 v2.1.3/go.mod h1:myoFOKJB/u5vNTFwvBBJVkG3NnOBeIJevbfjNeasLjo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= From a0d903860460f36db9843d78f72c56f4486c16f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:18:36 +0200 Subject: [PATCH 437/638] chore(deps): bump github.com/adhocore/gronx from 1.20.3 to 1.20.4 (#23120) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index affe1a8055e15..64a1442afa28f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/RoaringBitmap/roaring/v2 v2.29.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 - github.com/adhocore/gronx v1.20.3 + github.com/adhocore/gronx v1.20.4 github.com/andygrunwald/go-jira v1.17.0 github.com/aws/aws-sdk-go-v2 v1.47.1 github.com/aws/aws-sdk-go-v2/config v1.33.6 diff --git a/go.sum b/go.sum index 5dccb1493f47b..4e672370c1f3c 100644 --- a/go.sum +++ b/go.sum @@ -182,8 +182,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= -github.com/adhocore/gronx v1.20.3 h1:DjvHxCqyz9WDzpodceOU814Z4GNpTvSpZs7oeX4ToKU= -github.com/adhocore/gronx v1.20.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.20.4 h1:vE2UKHC3mRH4DF+tKiq/kOnENTuKaEois2aD0McyHiU= +github.com/adhocore/gronx v1.20.4/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= From d23c2cf9f57544f668186eec7bdf0c7799c94d90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:18:41 +0200 Subject: [PATCH 438/638] chore(deps): bump cloud.google.com/go/containeranalysis from 0.19.0 to 0.20.0 (#23118) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 64a1442afa28f..2d44ebafcd07c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.7 require ( cloud.google.com/go/artifactregistry v1.26.0 cloud.google.com/go/compute/metadata v0.9.1 - cloud.google.com/go/containeranalysis v0.19.0 + cloud.google.com/go/containeranalysis v0.20.0 cloud.google.com/go/securitycenter v1.46.0 cloud.google.com/go/storage v1.64.0 dario.cat/mergo v1.0.2 diff --git a/go.sum b/go.sum index 4e672370c1f3c..d3e62963698ca 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= -cloud.google.com/go/containeranalysis v0.19.0 h1:89ZhFvJHWzX9/jUJy/IEVbtSa2hv4+NtsvwWMEsUYnY= -cloud.google.com/go/containeranalysis v0.19.0/go.mod h1:Zq0XHzUIa0oTa7H6aSR8HWqeJnoRI9syUcYJzfozjZQ= +cloud.google.com/go/containeranalysis v0.20.0 h1:5tko84zYirVH+FEU6JPuT/xGBvSagsllYxH7m8ytjyI= +cloud.google.com/go/containeranalysis v0.20.0/go.mod h1:cJzgbuNqFTWswsVcT0eemqdqtOEq4dJEFvAH3cgzkyc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= From c8878dec5b5266f57f75b83631289df819a52cb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:18:57 +0200 Subject: [PATCH 439/638] chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.23.0 to 1.23.1 (#23121) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2d44ebafcd07c..fe5566cbc612d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( cloud.google.com/go/storage v1.64.0 dario.cat/mergo v1.0.2 github.com/Azure/azure-sdk-for-go-extensions v0.7.0 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 github.com/Azure/azure-sdk-for-go/sdk/monitor/ingestion/azlogs v1.1.0 diff --git a/go.sum b/go.sum index d3e62963698ca..8a867deccd72e 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go-extensions v0.7.0 h1:ko7jxarxFmfm3OAtIkKcnC2yK1H7a7wB7As38huaa4Y= github.com/Azure/azure-sdk-for-go-extensions v0.7.0/go.mod h1:f/wRrqvvh197V5r4jGADV7528UdO/zfL+/Ud92BMSag= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 h1:4gRPBpN1f6xt88yi4WR26m7XaD9OlWtVT6bWPdGUIok= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0/go.mod h1:G7QVLxw1j1JVyrO1MA95S8m8HStaaleDZYTcfGgjB2o= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= From 182c3a1a4be90ff7fcae113a888fd33cbb6ad685 Mon Sep 17 00:00:00 2001 From: Kirk Bater Date: Tue, 29 Sep 2026 07:55:53 -0400 Subject: [PATCH 440/638] fix(ci): Removes scannerV4 memory overrides (#23101) Removes scannerv4 CI memory overrides allowing them to fall back to installer defaults. --- deploy/common/scanner-v4-matcher-patch.yaml | 3 --- tests/e2e/lib-compat.sh | 15 --------------- .../yaml/central-cr-with-scanner-v4.envsubst.yaml | 2 -- tests/e2e/yaml/central-cr.envsubst.yaml | 3 --- 4 files changed, 23 deletions(-) diff --git a/deploy/common/scanner-v4-matcher-patch.yaml b/deploy/common/scanner-v4-matcher-patch.yaml index 3532ff36797b3..557949164ff6b 100644 --- a/deploy/common/scanner-v4-matcher-patch.yaml +++ b/deploy/common/scanner-v4-matcher-patch.yaml @@ -6,6 +6,3 @@ spec: resources: requests: cpu: "400m" - memory: "6Gi" - limits: - memory: "6Gi" diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index d5ff29a6dbc76..2b85aa4392e4a 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -186,7 +186,6 @@ roxie_config_from_environment_compat() { handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" "Enabled" handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" "AutoSense" handle_scanner_v4_vuln_readiness "$config_file" - handle_scanner_v4_matcher_resources "$config_file" info "Configuring declarative configuration..." handle_declarative_configuration "$config_file" @@ -287,20 +286,6 @@ handle_scanner_v4_vuln_readiness() { fi } -# handle_scanner_v4_matcher_resources raises the scanner-v4-matcher memory limit -# for roxie deployments. -handle_scanner_v4_matcher_resources() { - local config_file="$1" - - # Only meaningful when Scanner V4 is enabled. - if [[ "${ROX_SCANNER_V4:-true}" == "false" ]]; then - return - fi - - info " setting scanner-v4-matcher memory limit to 6Gi" - patch_yaml "$config_file" '.central.spec.scannerV4.matcher.resources.limits.memory = "6Gi"' -} - handle_trusted_ca_file() { local config_file="$1" local trusted_ca_file="${TRUSTED_CA_FILE:-}" diff --git a/tests/e2e/yaml/central-cr-with-scanner-v4.envsubst.yaml b/tests/e2e/yaml/central-cr-with-scanner-v4.envsubst.yaml index ea584b30f0dd5..2dd550ae2f319 100644 --- a/tests/e2e/yaml/central-cr-with-scanner-v4.envsubst.yaml +++ b/tests/e2e/yaml/central-cr-with-scanner-v4.envsubst.yaml @@ -59,10 +59,8 @@ $centralAdditionalCAIndented resources: requests: cpu: "400m" - memory: "6Gi" limits: cpu: "6000m" - memory: "6Gi" db: resources: requests: diff --git a/tests/e2e/yaml/central-cr.envsubst.yaml b/tests/e2e/yaml/central-cr.envsubst.yaml index 6170b0dc9bad5..ea69d2e7a5b33 100644 --- a/tests/e2e/yaml/central-cr.envsubst.yaml +++ b/tests/e2e/yaml/central-cr.envsubst.yaml @@ -55,9 +55,6 @@ $centralAdditionalCAIndented resources: requests: cpu: "400m" - memory: "6Gi" - limits: - memory: "6Gi" db: $scannerV4DbPersistenceYaml resources: From ae8c906c446e67127612e489599f1086b0fd6df2 Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Tue, 29 Sep 2026 13:41:28 +0000 Subject: [PATCH 441/638] ROX-35665: avoid race condition in Cluster deletion (#23047) Co-authored-by: Tomasz Janiszewski --- central/cluster/datastore/datastore_impl.go | 8 +- .../cluster/datastore/datastore_impl_test.go | 2 +- central/cluster/lifecycle/gate.go | 93 +++++++++++++++++++ central/cluster/lifecycle/gate_test.go | 47 ++++++++++ .../service/pipeline/nodeindex/pipeline.go | 17 +++- .../pipeline/nodeindex/pipeline_test.go | 28 ++++++ .../nodeindex/three_pipelines_test.go | 1 + .../pipeline/nodeinventory/pipeline.go | 17 +++- .../pipeline/nodeinventory/pipeline_test.go | 3 + .../nodeinventory/two_pipelines_test.go | 1 + .../sensor/service/pipeline/nodes/pipeline.go | 34 ++++++- .../service/pipeline/nodes/pipeline_test.go | 1 + tests/cluster_delete_test.go | 10 +- 13 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 central/cluster/lifecycle/gate.go create mode 100644 central/cluster/lifecycle/gate_test.go diff --git a/central/cluster/datastore/datastore_impl.go b/central/cluster/datastore/datastore_impl.go index 62ed1dbc6dc4f..fea57dfb7bf47 100644 --- a/central/cluster/datastore/datastore_impl.go +++ b/central/cluster/datastore/datastore_impl.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" alertDataStore "github.com/stackrox/rox/central/alert/datastore" + "github.com/stackrox/rox/central/cluster/lifecycle" clusterStore "github.com/stackrox/rox/central/cluster/store/cluster" clusterHealthStore "github.com/stackrox/rox/central/cluster/store/clusterhealth" clusterInitStore "github.com/stackrox/rox/central/clusterinit/store" @@ -658,7 +659,9 @@ func (ds *datastoreImpl) RemoveCluster(ctx context.Context, id string, done *con return err } + release := lifecycle.Singleton().BeginDeletion(id) if err := ds.clusterStorage.Delete(ctx, id); err != nil { + release() return errors.Wrapf(err, "failed to remove cluster %q", id) } ds.idToNameCache.Remove(id) @@ -666,11 +669,12 @@ func (ds *datastoreImpl) RemoveCluster(ctx context.Context, id string, done *con ds.nameToIDCache.Remove(cluster.GetName()) deleteRelatedCtx := sac.WithAllAccess(context.Background()) - go ds.postRemoveCluster(deleteRelatedCtx, cluster, done) + go ds.postRemoveCluster(deleteRelatedCtx, cluster, done, release) return nil } -func (ds *datastoreImpl) postRemoveCluster(ctx context.Context, cluster *storage.Cluster, done *concurrency.Signal) { +func (ds *datastoreImpl) postRemoveCluster(ctx context.Context, cluster *storage.Cluster, done *concurrency.Signal, release func()) { + defer release() // Terminate the cluster connection to prevent new data from being stored. if ds.cm != nil { ds.cm.CloseConnection(cluster.GetId()) diff --git a/central/cluster/datastore/datastore_impl_test.go b/central/cluster/datastore/datastore_impl_test.go index 757824b7fa839..0ba81c3c61ccf 100644 --- a/central/cluster/datastore/datastore_impl_test.go +++ b/central/cluster/datastore/datastore_impl_test.go @@ -434,7 +434,7 @@ func (s *clusterDataStoreTestSuite) TestPostRemoveCluster() { ctx := sac.WithAllAccess(s.T().Context()) doneSignal := concurrency.NewSignal() - s.datastore.postRemoveCluster(ctx, removedCluster, &doneSignal) + s.datastore.postRemoveCluster(ctx, removedCluster, &doneSignal, func() {}) doneSignal.Wait() }) diff --git a/central/cluster/lifecycle/gate.go b/central/cluster/lifecycle/gate.go new file mode 100644 index 0000000000000..7eab76a27beb7 --- /dev/null +++ b/central/cluster/lifecycle/gate.go @@ -0,0 +1,93 @@ +package lifecycle + +import ( + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/sync" +) + +// Gate serializes cluster deletion with writes that must not outlive the cluster. +type Gate struct { + mu sync.Mutex + entries map[string]*entry +} + +type entry struct { + mu sync.RWMutex + stateMu sync.Mutex + deleting bool + refs int +} + +var singleton = New() + +// Singleton returns the process-wide cluster lifecycle gate. +func Singleton() *Gate { + return singleton +} + +// New creates a cluster lifecycle gate. +func New() *Gate { + return &Gate{entries: make(map[string]*entry)} +} + +// Enter acquires a shared lifecycle lease unless deletion has started. The +// returned release function must be called when active is true. +func (g *Gate) Enter(clusterID string) (active bool, release func()) { + e := g.retain(clusterID) + + active = concurrency.WithLock1(&e.stateMu, func() bool { + if e.deleting { + return false + } + e.mu.RLock() + return true + }) + if !active { + g.release(clusterID, e) + return false, nil + } + + return true, func() { + concurrency.UnsafeRUnlock(&e.mu) + g.release(clusterID, e) + } +} + +// BeginDeletion prevents new active writes and waits for existing writes to finish. +// The returned function must be called once cluster cleanup completes. +func (g *Gate) BeginDeletion(clusterID string) func() { + e := g.retain(clusterID) + concurrency.WithLock(&e.stateMu, func() { + e.deleting = true + }) + e.mu.Lock() + + return func() { + concurrency.UnsafeUnlock(&e.mu) + concurrency.WithLock(&e.stateMu, func() { + e.deleting = false + }) + g.release(clusterID, e) + } +} + +func (g *Gate) retain(clusterID string) *entry { + g.mu.Lock() + defer g.mu.Unlock() + e := g.entries[clusterID] + if e == nil { + e = &entry{} + g.entries[clusterID] = e + } + e.refs++ + return e +} + +func (g *Gate) release(clusterID string, e *entry) { + g.mu.Lock() + defer g.mu.Unlock() + e.refs-- + if e.refs == 0 && !e.deleting { + delete(g.entries, clusterID) + } +} diff --git a/central/cluster/lifecycle/gate_test.go b/central/cluster/lifecycle/gate_test.go new file mode 100644 index 0000000000000..7cd8e67424006 --- /dev/null +++ b/central/cluster/lifecycle/gate_test.go @@ -0,0 +1,47 @@ +package lifecycle + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestGateBlocksDeletionUntilActiveWriteCompletes(t *testing.T) { + gate := New() + writeStarted := make(chan struct{}) + releaseWrite := make(chan struct{}) + writeFinished := make(chan struct{}) + go func() { + active, release := gate.Enter("cluster-id") + if !active { + t.Error("active write was rejected") + return + } + defer release() + close(writeStarted) + <-releaseWrite + close(writeFinished) + }() + <-writeStarted + + deletionStarted := make(chan func()) + go func() { deletionStarted <- gate.BeginDeletion("cluster-id") }() + require.Never(t, func() bool { + select { + case <-deletionStarted: + return true + default: + return false + } + }, 20*time.Millisecond, time.Millisecond) + + close(releaseWrite) + <-writeFinished + releaseDeletion := <-deletionStarted + releaseDeletion() + + active, release := gate.Enter("cluster-id") + require.True(t, active) + release() +} diff --git a/central/sensor/service/pipeline/nodeindex/pipeline.go b/central/sensor/service/pipeline/nodeindex/pipeline.go index 18eeea9cf16ab..dc032fe4cbf36 100644 --- a/central/sensor/service/pipeline/nodeindex/pipeline.go +++ b/central/sensor/service/pipeline/nodeindex/pipeline.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" clusterDataStore "github.com/stackrox/rox/central/cluster/datastore" + "github.com/stackrox/rox/central/cluster/lifecycle" "github.com/stackrox/rox/central/enrichment" countMetrics "github.com/stackrox/rox/central/metrics" nodeDatastore "github.com/stackrox/rox/central/node/datastore" @@ -120,7 +121,7 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe incomingScanTime := node.GetScan().GetScanTime() // Update the whole node in the database with the new and previous information. - err = p.riskManager.CalculateRiskAndUpsertNode(node) + err = p.upsertNodeIfClusterActive(ctx, node) if err != nil { return errors.Wrapf(err, "failed calculating risk and upserting node %s", nodeDatastore.NodeString(node)) } @@ -156,6 +157,20 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe return nil } +func (p *pipelineImpl) upsertNodeIfClusterActive(ctx context.Context, node *storage.Node) error { + active, release := lifecycle.Singleton().Enter(node.GetClusterId()) + if !active { + return nil + } + defer release() + + _, exists, err := p.clusterStore.GetClusterName(ctx, node.GetClusterId()) + if err != nil || !exists { + return err + } + return p.riskManager.CalculateRiskAndUpsertNode(node) +} + func sendComplianceAck(ctx context.Context, node *storage.Node, injector common.MessageInjector) { if injector == nil { return diff --git a/central/sensor/service/pipeline/nodeindex/pipeline_test.go b/central/sensor/service/pipeline/nodeindex/pipeline_test.go index 539199eadcb6f..7d98420ce86bc 100644 --- a/central/sensor/service/pipeline/nodeindex/pipeline_test.go +++ b/central/sensor/service/pipeline/nodeindex/pipeline_test.go @@ -5,6 +5,7 @@ import ( "time" clusterDatastoreMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" + "github.com/stackrox/rox/central/cluster/lifecycle" nodeDatastoreMocks "github.com/stackrox/rox/central/node/datastore/mocks" riskManagerMocks "github.com/stackrox/rox/central/risk/manager/mocks" "github.com/stackrox/rox/generated/internalapi/central" @@ -75,6 +76,7 @@ func TestPipelineWarnsWhenPersistedScanTimeRegresses(t *testing.T) { node := &storage.Node{Id: "1", Name: "node-name", ClusterId: "cluster-id"} ctrl := gomock.NewController(t) + clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) riskManager := riskManagerMocks.NewMockManager(ctrl) enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) @@ -94,8 +96,10 @@ func TestPipelineWarnsWhenPersistedScanTimeRegresses(t *testing.T) { return nil }), ) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Eq(node.GetClusterId())).Return("", true, nil) p := &pipelineImpl{ + clusterStore: clusterStore, nodeDatastore: nodeDatastore, riskManager: riskManager, enricher: enricher, @@ -157,6 +161,7 @@ func TestPipelineEnrichesAndUpserts(t *testing.T) { } ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq("1")).Times(2).Return(&node, true, nil) riskManager := riskManagerMocks.NewMockManager(ctrl) @@ -183,12 +188,34 @@ func TestPipelineEnrichesAndUpserts(t *testing.T) { } } +func TestPipelineSkipsUpsertAfterClusterDeletionBegins(t *testing.T) { + t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") + t.Setenv(features.ScannerV4.EnvVar(), "true") + + ctrl := gomock.NewController(t) + node := &storage.Node{Id: "1", Name: "node-name", ClusterId: "cluster-id"} + nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) + nodeDatastore.EXPECT().GetNode(gomock.Any(), gomock.Eq(node.GetId())).Return(node, true, nil) + enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) + enricher.EXPECT().EnrichNodeWithVulnerabilities(gomock.Any(), nil, gomock.Any()).Return(nil) + + releaseDeletion := lifecycle.Singleton().BeginDeletion(node.GetClusterId()) + defer releaseDeletion() + + p := &pipelineImpl{ + nodeDatastore: nodeDatastore, + enricher: enricher, + } + require.NoError(t, p.Run(t.Context(), node.GetClusterId(), createMsg(mockIndexReport), nil)) +} + func TestPipelineSendsSensorAndLegacyACKs(t *testing.T) { t.Setenv(features.NodeIndexEnabled.EnvVar(), "true") t.Setenv(features.ScannerV4.EnvVar(), "true") ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) riskManager := riskManagerMocks.NewMockManager(ctrl) enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) @@ -245,6 +272,7 @@ func TestPipelineSkipsSensorACKWhenCapabilityMissing(t *testing.T) { ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) riskManager := riskManagerMocks.NewMockManager(ctrl) enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) diff --git a/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go b/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go index 32f96a4cdf149..069ed968a6fbf 100644 --- a/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go +++ b/central/sensor/service/pipeline/nodeindex/three_pipelines_test.go @@ -360,6 +360,7 @@ func Test_ThreePipelines_Run(t *testing.T) { if tt.setUpMocksAndEnv != nil { tt.setUpMocksAndEnv(t, tt.mocks) } + tt.mocks.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) pNode := nodes.NewPipeline(tt.mocks.clusterStore, tt.mocks.nodeDatastore, tt.enricher, tt.riskManager) pNodeInv := nodeinventory.NewPipeline(tt.mocks.clusterStore, tt.mocks.nodeDatastore, tt.enricher, tt.riskManager) pNodeIdx := newPipeline(tt.mocks.clusterStore, tt.mocks.nodeDatastore, tt.enricher, tt.riskManager) diff --git a/central/sensor/service/pipeline/nodeinventory/pipeline.go b/central/sensor/service/pipeline/nodeinventory/pipeline.go index cc03d75aaa2d4..f9cfff63913f0 100644 --- a/central/sensor/service/pipeline/nodeinventory/pipeline.go +++ b/central/sensor/service/pipeline/nodeinventory/pipeline.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" clusterDataStore "github.com/stackrox/rox/central/cluster/datastore" + "github.com/stackrox/rox/central/cluster/lifecycle" "github.com/stackrox/rox/central/enrichment" countMetrics "github.com/stackrox/rox/central/metrics" nodeDatastore "github.com/stackrox/rox/central/node/datastore" @@ -115,7 +116,7 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe len(node.GetScan().GetComponents())) // Update the whole node in the database with the new and previous information. - err = p.riskManager.CalculateRiskAndUpsertNode(node) + err = p.calculateRiskAndUpsertNodeIfClusterActive(ctx, node) if err != nil { log.Error(err) return err @@ -125,6 +126,20 @@ func (p *pipelineImpl) Run(ctx context.Context, _ string, msg *central.MsgFromSe return nil } +func (p *pipelineImpl) calculateRiskAndUpsertNodeIfClusterActive(ctx context.Context, node *storage.Node) error { + active, release := lifecycle.Singleton().Enter(node.GetClusterId()) + if !active { + return nil + } + defer release() + + _, exists, err := p.clusterStore.GetClusterName(ctx, node.GetClusterId()) + if err != nil || !exists { + return err + } + return p.riskManager.CalculateRiskAndUpsertNode(node) +} + // shouldDiscardMsg returns true if the pipeline should discard an incoming message or false if it should keep processing it. func shouldDiscardMsg(node *storage.Node) bool { // In a mixed environment, there might be v2-only clusters, so there is no Scanner v4 scan available for that cluster. diff --git a/central/sensor/service/pipeline/nodeinventory/pipeline_test.go b/central/sensor/service/pipeline/nodeinventory/pipeline_test.go index b2ed240fef7cd..a6e70ea76acfc 100644 --- a/central/sensor/service/pipeline/nodeinventory/pipeline_test.go +++ b/central/sensor/service/pipeline/nodeinventory/pipeline_test.go @@ -171,6 +171,7 @@ func Test_pipelineImpl_Run(t *testing.T) { if tt.setUp != nil { tt.setUp(t, &tt.args, &tt.mocks) } + tt.mocks.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) p := &pipelineImpl{ clusterStore: tt.mocks.clusterStore, nodeDatastore: tt.mocks.nodeDatastore, @@ -196,6 +197,7 @@ func Test_pipelineImpl_Run_SendsSensorAndLegacyACKs(t *testing.T) { testutils.MustUpdateFeature(t, features.LegacyScanner, true) ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) riskManager := riskManagerMocks.NewMockManager(ctrl) enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) @@ -263,6 +265,7 @@ func Test_pipelineImpl_Run_SkipsSensorACKWhenCapabilityMissing(t *testing.T) { testutils.MustUpdateFeature(t, features.LegacyScanner, true) ctrl := gomock.NewController(t) clusterStore := clusterDatastoreMocks.NewMockDataStore(ctrl) + clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) nodeDatastore := nodeDatastoreMocks.NewMockDataStore(ctrl) riskManager := riskManagerMocks.NewMockManager(ctrl) enricher := nodesEnricherMocks.NewMockNodeEnricher(ctrl) diff --git a/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go b/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go index ce04f4ed06ff5..9f8b72efd5fa8 100644 --- a/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go +++ b/central/sensor/service/pipeline/nodeinventory/two_pipelines_test.go @@ -230,6 +230,7 @@ func Test_TwoPipelines_Run(t *testing.T) { if tt.setUpMocks != nil { tt.setUpMocks(t, tt.mocks) } + tt.mocks.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("", true, nil) pNode := nodes.NewPipeline(tt.mocks.clusterStore, tt.mocks.nodeDatastore, tt.enricher, tt.riskManager) pNodeInv := NewPipeline(tt.mocks.clusterStore, tt.mocks.nodeDatastore, tt.enricher, tt.riskManager) diff --git a/central/sensor/service/pipeline/nodes/pipeline.go b/central/sensor/service/pipeline/nodes/pipeline.go index a59efc0c8cb7f..9bf5a7e053ab4 100644 --- a/central/sensor/service/pipeline/nodes/pipeline.go +++ b/central/sensor/service/pipeline/nodes/pipeline.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" clusterDataStore "github.com/stackrox/rox/central/cluster/datastore" + "github.com/stackrox/rox/central/cluster/lifecycle" "github.com/stackrox/rox/central/enrichment" countMetrics "github.com/stackrox/rox/central/metrics" nodeDatastore "github.com/stackrox/rox/central/node/datastore" @@ -103,14 +104,13 @@ func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.M // ROX-22002: Remove invalid null characters in annotations stringutils.SanitizeMapValues(node.GetAnnotations()) - if enricher.SupportsNodeScanning(node) { // If supports node scanning, this pipeline should only update the node's // metadata. We call upsert without scan. Upsert will read scan information from // the database before writing. This is safe because NodeInventory and Node // pipelines never run concurrently by the Sensor Event worker queues. node.Scan = nil - if err := p.nodeDatastore.UpsertNode(ctx, node); err != nil { + if err := p.upsertNodeIfClusterActive(ctx, clusterID, node); err != nil { err = errors.Wrapf(err, "upserting node %s", nodeDatastore.NodeString(node)) log.Error(err) return err @@ -125,7 +125,7 @@ func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.M ScanTime: protocompat.TimestampNow(), Notes: []storage.NodeScan_Note{storage.NodeScan_UNSUPPORTED}, } - if err := p.nodeDatastore.UpsertNode(ctx, node); err != nil { + if err := p.upsertNodeIfClusterActive(ctx, clusterID, node); err != nil { err = errors.Wrapf(err, "upserting node %s", nodeDatastore.NodeString(node)) log.Error(err) return err @@ -139,7 +139,7 @@ func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.M "risk information will not be updated): %v", nodeDatastore.NodeString(node), err) } - if err := p.riskManager.CalculateRiskAndUpsertNode(node); err != nil { + if err := p.calculateRiskAndUpsertNodeIfClusterActive(ctx, clusterID, node); err != nil { log.Error(err) return err } @@ -147,4 +147,30 @@ func (p *pipelineImpl) Run(ctx context.Context, clusterID string, msg *central.M return nil } +func (p *pipelineImpl) upsertNodeIfClusterActive(ctx context.Context, clusterID string, node *storage.Node) error { + return p.withActiveCluster(ctx, clusterID, func() error { + return p.nodeDatastore.UpsertNode(ctx, node) + }) +} + +func (p *pipelineImpl) calculateRiskAndUpsertNodeIfClusterActive(ctx context.Context, clusterID string, node *storage.Node) error { + return p.withActiveCluster(ctx, clusterID, func() error { + return p.riskManager.CalculateRiskAndUpsertNode(node) + }) +} + +func (p *pipelineImpl) withActiveCluster(ctx context.Context, clusterID string, write func() error) error { + active, release := lifecycle.Singleton().Enter(clusterID) + if !active { + return nil + } + defer release() + + _, exists, err := p.clusterStore.GetClusterName(ctx, clusterID) + if err != nil || !exists { + return err + } + return write() +} + func (p *pipelineImpl) OnFinish(_ string) {} diff --git a/central/sensor/service/pipeline/nodes/pipeline_test.go b/central/sensor/service/pipeline/nodes/pipeline_test.go index c6ba35109fdae..c673578f9288b 100644 --- a/central/sensor/service/pipeline/nodes/pipeline_test.go +++ b/central/sensor/service/pipeline/nodes/pipeline_test.go @@ -133,6 +133,7 @@ func Test_pipelineImpl_Run(t *testing.T) { if tt.setUp != nil { tt.setUp(t, &tt.args, &tt.mocks) } + tt.mocks.clusterStore.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).AnyTimes().Return("test cluster name", true, nil) p := &pipelineImpl{ clusterStore: tt.mocks.clusterStore, nodeDatastore: tt.mocks.nodeDatastore, diff --git a/tests/cluster_delete_test.go b/tests/cluster_delete_test.go index 10ea65ad63a29..149e04acce00c 100644 --- a/tests/cluster_delete_test.go +++ b/tests/cluster_delete_test.go @@ -32,6 +32,14 @@ func (a *allCounts) AllZero() bool { return allIntsZero(a.PodCount, a.ClusterCount, a.NodeCount, a.DeploymentCount, a.SecretCount) } +func (a *allCounts) Less(other *allCounts) bool { + return a.PodCount < other.PodCount || + a.ClusterCount < other.ClusterCount || + a.NodeCount < other.NodeCount || + a.DeploymentCount < other.DeploymentCount || + a.SecretCount < other.SecretCount +} + type summaryCountsResp struct { ClusterCount, NodeCount, ViolationCount, DeploymentCount, SecretCount int } @@ -105,7 +113,7 @@ func TestClusterDeletion(t *testing.T) { t.Logf("deployment count is still not zero: %d", counts.DeploymentCount) } - if previous.DeploymentCount > 0 && previous.DeploymentCount > counts.DeploymentCount { + if counts.Less(&previous) { noChangeCount = 0 } else { noChangeCount++ From 5ef3d48a8d089837c1c1302b96a7e83a857d9763 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:21:41 +0200 Subject: [PATCH 442/638] chore(deps): bump cloud.google.com/go/artifactregistry from 1.26.0 to 1.27.0 (#23117) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fe5566cbc612d..1d8f786ac5d42 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/stackrox/rox go 1.26.7 require ( - cloud.google.com/go/artifactregistry v1.26.0 + cloud.google.com/go/artifactregistry v1.27.0 cloud.google.com/go/compute/metadata v0.9.1 cloud.google.com/go/containeranalysis v0.20.0 cloud.google.com/go/securitycenter v1.46.0 diff --git a/go.sum b/go.sum index 8a867deccd72e..afacb4c1f634e 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0c cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/artifactregistry v1.26.0 h1:iq5kkdY2FJY8RkkNghUelb3EOcqyy7dVhfDGVk/Xw9g= -cloud.google.com/go/artifactregistry v1.26.0/go.mod h1:c5FPi5GtDBP+OAr5kKhCBNQDT9ZgAyobXQjekx93VWs= +cloud.google.com/go/artifactregistry v1.27.0 h1:EBgdk9sXNAVyWJ3hnTmWRdlmKhGDKCjkJbLoF3EsyWE= +cloud.google.com/go/artifactregistry v1.27.0/go.mod h1:r0rZgbNmn5ppdqwUMF182yc7EFH6vabJMbXM+PlfW9A= cloud.google.com/go/auth v0.23.3 h1:UMK+oBtuNGMCR/6i6mmySUItqjOazpJrbmZyhGbGBWo= cloud.google.com/go/auth v0.23.3/go.mod h1:fClbry28fo7XkxhSeT6AQtAVAp6Jy0fW9N99PoPNPFM= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= From 067c642e255db1724c1a4ff47d1112ec6e37b142 Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:37:34 +0200 Subject: [PATCH 443/638] chore: Remove `scripts/offline-bundle` (#22826) --- Makefile | 8 --- scripts/offline-bundle/.gitignore | 3 - scripts/offline-bundle/create.sh | 63 ------------------- .../offline-bundle/image-bundle/README.txt | 18 ------ scripts/offline-bundle/image-bundle/import.sh | 61 ------------------ .../image-collector-bundle/README.txt | 7 --- .../image-collector-bundle/import.sh | 37 ----------- 7 files changed, 197 deletions(-) delete mode 100644 scripts/offline-bundle/.gitignore delete mode 100755 scripts/offline-bundle/create.sh delete mode 100644 scripts/offline-bundle/image-bundle/README.txt delete mode 100755 scripts/offline-bundle/image-bundle/import.sh delete mode 100644 scripts/offline-bundle/image-collector-bundle/README.txt delete mode 100755 scripts/offline-bundle/image-collector-bundle/import.sh diff --git a/Makefile b/Makefile index 11bc6d500c9d6..7ab67e2cd9c67 100644 --- a/Makefile +++ b/Makefile @@ -871,14 +871,6 @@ roxvet: $(ROXVET_BIN) ########## ## Misc ## ########## -.PHONY: clean-offline-bundle -clean-offline-bundle: - $(SILENT)find scripts/offline-bundle -name '*.img' -delete -o -name '*.tgz' -delete -o -name 'bin' -type d -exec rm -r "{}" \; - -.PHONY: offline-bundle -offline-bundle: clean-offline-bundle - $(SILENT)./scripts/offline-bundle/create.sh - .PHONY: check-debugger check-debugger: /usr/bin/env DEBUG_BUILD="$(DEBUG_BUILD)" BUILD_TAG="$(BUILD_TAG)" TAG="$(TAG)" ./scripts/check-debugger.sh diff --git a/scripts/offline-bundle/.gitignore b/scripts/offline-bundle/.gitignore deleted file mode 100644 index 8bc06143b8a41..0000000000000 --- a/scripts/offline-bundle/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -**/*.img -**/bin -*.tgz diff --git a/scripts/offline-bundle/create.sh b/scripts/offline-bundle/create.sh deleted file mode 100755 index 0bf4e1b614dce..0000000000000 --- a/scripts/offline-bundle/create.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" - -export_image() { - local name=$1 - local image=$2 - local last_dir=$3 - - docker pull "$image" | cat - - mkdir -p "${DIR}/${last_dir}" - echo "Saving $image to ${DIR}/${last_dir}/${name}.img" - docker save "$image" -o "${DIR}/${last_dir}/${name}.img" -} - -save() { - local registry=$1 - local name=$2 - local tag=$3 - local last_dir=$4 - - export_image "$name" "${registry}/${name}:${tag}" "${last_dir}" -} - -bundle() { - local name=$1 - pushd "${DIR}" - tar -czvf "${name}.tgz" "${name}" - popd -} - -store_roxctl() { - output_path=$1 - mkdir -p "${DIR}/${output_path}/bin" - gsutil -m cp -r "gs://sr-roxc/${main_tag}/bin/darwin" "${DIR}/${output_path}/bin" - gsutil -m cp -r "gs://sr-roxc/${main_tag}/bin/linux" "${DIR}/${output_path}/bin" - gsutil -m cp -r "gs://sr-roxc/${main_tag}/bin/windows" "${DIR}/${output_path}/bin" - chmod +x "${DIR}/${output_path}/bin/darwin/roxctl" "${DIR}/${output_path}/bin/linux/roxctl" -} - -main() { - # Main uses the version reported by make tag. - local main_tag - main_tag="$(make --quiet --no-print-directory tag)" - save "stackrox.io" "main" "${main_tag}" "image-bundle" - - # Scanner uses the same version as Main. - save "stackrox.io" "scanner" "${main_tag}" "image-bundle" - save "stackrox.io" "scanner-db" "${main_tag}" "image-bundle" - - # Collector uses the same version as Main. - save "collector.stackrox.io" "collector" "${main_tag}" "image-collector-bundle" - - store_roxctl "image-bundle" - - bundle "image-bundle" - bundle "image-collector-bundle" -} - -main "$@" diff --git a/scripts/offline-bundle/image-bundle/README.txt b/scripts/offline-bundle/image-bundle/README.txt deleted file mode 100644 index 5375d77249f1e..0000000000000 --- a/scripts/offline-bundle/image-bundle/README.txt +++ /dev/null @@ -1,18 +0,0 @@ -This bundle contains archives of the images necessary to run the -StackRox Kubernetes Security Platform. - -To secure a cluster, your private registry must be able to pull the images. -The import script loads images onto your local Docker engine, but can also -push the images to a private registry. - -To install, first reimport the images: - ./import.sh - -See product documentation for complete setup steps. To view documentation: - - Go to https://help.stackrox.com, or - - Run import.sh, then follow the instructions displayed. - -When you generate your installation configuration and follow the -instructions, use your private registry image names. - -See product documentation for additional details. diff --git a/scripts/offline-bundle/image-bundle/import.sh b/scripts/offline-bundle/image-bundle/import.sh deleted file mode 100755 index 625de26a13d37..0000000000000 --- a/scripts/offline-bundle/image-bundle/import.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -print_docs_usage() { - echo - echo "To view complete product documentation go to https://help.stackrox.com" -} - -main() { - cd "$(dirname "${BASH_SOURCE[0]}")" - - echo "This script can optionally push imported images into a private registry." - echo "Most users add a path segment like \"/stackrox\"." - echo "For example, you might input: my-registry.example.com:5000/stackrox" - echo "To skip pushing, simply do not enter a prefix." - echo -n "Enter your private registry prefix: " - read registry_prefix - echo - - echo "Loading main image..." - main_tag="$(docker load -i main.img | tag)" - main_image_local="stackrox.io/main:${main_tag}" - main_image_remote="${registry_prefix}/main:${main_tag}" - - echo "Loading scanner images..." - scanner_tag="$(docker load -i scanner.img | tag)" - scanner_image_local="stackrox.io/scanner:${scanner_tag}" - scanner_image_remote="${registry_prefix}/scanner:${scanner_tag}" - - scanner_db_tag="$(docker load -i scanner-db.img | tag)" - scanner_db_image_local="stackrox.io/scanner-db:${scanner_db_tag}" - scanner_db_image_remote="${registry_prefix}/scanner-db:${scanner_db_tag}" - - if [[ -z "$registry_prefix" ]]; then - echo "No registry prefix given, all done!" - print_docs_usage - return - fi - - echo "Pushing image: ${main_image_local} as ${main_image_remote}" - docker tag "${main_image_local}" "${main_image_remote}" - docker push "${main_image_remote}" | cat - - echo "Pushing image: ${scanner_image_local} as ${scanner_image_remote}" - docker tag "${scanner_image_local}" "${scanner_image_remote}" - docker push "${scanner_image_remote}" | cat - - echo "Pushing image: ${scanner_db_image_local} as ${scanner_db_image_remote}" - docker tag "${scanner_db_image_local}" "${scanner_db_image_remote}" - docker push "${scanner_db_image_remote}" | cat - - echo "All done!" - print_docs_usage -} - -tag() { - sed 's/.*:\(.*$\)/\1/' -} - -main diff --git a/scripts/offline-bundle/image-collector-bundle/README.txt b/scripts/offline-bundle/image-collector-bundle/README.txt deleted file mode 100644 index 6c5b4ab778c6b..0000000000000 --- a/scripts/offline-bundle/image-collector-bundle/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -This bundle contains archives of the Collector image which integrates with -the StackRox Kubernetes Security Platform. - -To install, first reimport the image: - ./import.sh - -See product documentation for additional details. diff --git a/scripts/offline-bundle/image-collector-bundle/import.sh b/scripts/offline-bundle/image-collector-bundle/import.sh deleted file mode 100755 index 024f0edd5a629..0000000000000 --- a/scripts/offline-bundle/image-collector-bundle/import.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -main() { - cd "$(dirname "${BASH_SOURCE[0]}")" - - echo "This script can optionally push imported images into a private registry." - echo "Most users add a path segment like \"/stackrox\"." - echo "For example, you might input: my-registry.example.com:5000/stackrox" - echo "To skip pushing, simply do not enter a prefix." - echo -n "Enter your private registry prefix: " - read registry_prefix - echo - - echo "Loading collector image..." - collector_tag="$(docker load -i collector.img | tag)" - collector_image_local="collector.stackrox.io/collector:${collector_tag}" - collector_image_remote="${registry_prefix}/collector:${collector_tag}" - - if [[ -z "$registry_prefix" ]]; then - echo "No registry prefix given, all done!" - return - fi - - echo "Pushing image: ${collector_image_local} as ${collector_image_remote}" - docker tag "${collector_image_local}" "${collector_image_remote}" - docker push "${collector_image_remote}" | cat - - echo "All done!" -} - -tag() { - sed 's/.*:\(.*$\)/\1/' -} - -main From 90006fc01cd7e285e7bfd6e98b132a2091140a6d Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 03:22:43 +0000 Subject: [PATCH 444/638] chore(deps): update quay.io/rhacs-eng/konflux-tasks:latest docker digest to 53b3c5a (#23142) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 14c58b159f06f..0a282bd9ec0fc 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 0f30a4cc32a21..35f053f753c11 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index d92e856bd45cc..a1c56638ff702 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 62f2e080d7f0a..17a06bb9d5b13 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index a86d6052e7593..066ed9687bd63 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 18e34cc099091..05dfcf048d415 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index fda4a68727954..a2c1f4286bf90 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index c6a3c4d137c68..098264b41384f 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:243b1db3f2d0da8dbf6f716516f46bb5c43918e74331942db820e3a32b378940 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 - name: kind value: task resolver: bundles From 963139bd8305b087dc66095cbb57841f0aeb91af Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 03:53:21 +0000 Subject: [PATCH 445/638] chore(deps): update all dependencies (#23141) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- image/postgres/konflux.Dockerfile | 2 +- image/rhel/konflux.Dockerfile | 2 +- image/roxctl/konflux.Dockerfile | 2 +- operator/konflux.Dockerfile | 2 +- scanner/image/db/konflux.Dockerfile | 2 +- scanner/image/scanner/konflux.Dockerfile | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index 8b0d612e9bb2b..e68bb68a7eb91 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:962fd39f9c86327d685372216b5a055c8f1748abc5c1e7a5103f8cbf8df3a01a AS final +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:a3a5c31afcd91ef5d96f20bcf7aa7b47b10c034f86b549b04985ebed6d14567e AS final USER root diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 1c1e55031b641..7361053a6a0f8 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -73,7 +73,7 @@ RUN make -C ui build FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 29f7530c67be3..948c16d3964d7 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -27,7 +27,7 @@ RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index c6eb62708c6a2..835b924e191d7 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -20,7 +20,7 @@ RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/mai FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index af8d11f490e67..6888180df6b18 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:962fd39f9c86327d685372216b5a055c8f1748abc5c1e7a5103f8cbf8df3a01a +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:a3a5c31afcd91ef5d96f20bcf7aa7b47b10c034f86b549b04985ebed6d14567e ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index 3dbf15bb4879f..b21eb84546a32 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -18,7 +18,7 @@ RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-script FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:7a38d75d376f7989033e75fcaaf9fcbb090fafc58e05c2c99de76ae0ba083fdb AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer COPY --from=ubi-micro-base / /out/ From e1b6549c14318cf36ee1793ba58a9db7619defee Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 06:35:11 +0000 Subject: [PATCH 446/638] chore(deps): update quay.io/rhacs-eng/konflux-tasks:latest docker digest to a33d6ea (#23144) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 0a282bd9ec0fc..412e2460acc16 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 35f053f753c11..0a32b36258dc3 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index a1c56638ff702..d82ad2a8b5bbb 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 17a06bb9d5b13..787b6c7506c9e 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 066ed9687bd63..08cf42b987678 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 05dfcf048d415..a3c1aa9ceff8f 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index a2c1f4286bf90..63b3467b416a7 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index 098264b41384f..bc8c0f64addec 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:53b3c5ae9fd5d632d03d16a11b3f85842f86123b52d5a72488204649386f9df8 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 - name: kind value: task resolver: bundles From 4b60d6d4773b0584a08e5e367b31bf567deb575d Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 30 Sep 2026 09:16:38 +0200 Subject: [PATCH 447/638] ROX-35079: drop Application from the central chart (#21585) --- CHANGELOG.md | 2 + .../templates/00-stackrox-application.yaml | 122 ------------------ .../tests/centralservices/base_suite_test.go | 1 - .../securedclusterservices/base_suite_test.go | 1 - pkg/helm/config/roundtrip_test.go | 1 - pkg/helm/util/render.go | 6 - 6 files changed, 2 insertions(+), 131 deletions(-) delete mode 100644 image/templates/helm/stackrox-central/templates/00-stackrox-application.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbe071da4de4..5d2a12a8afb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc ### Removed Features +- ROX-35079: The `app.k8s.io/v1beta1/Application` CR is no longer shipped. + ### Deprecated Features ### Technical Changes diff --git a/image/templates/helm/stackrox-central/templates/00-stackrox-application.yaml b/image/templates/helm/stackrox-central/templates/00-stackrox-application.yaml deleted file mode 100644 index 3be046fa21d0c..0000000000000 --- a/image/templates/helm/stackrox-central/templates/00-stackrox-application.yaml +++ /dev/null @@ -1,122 +0,0 @@ -{{- include "srox.init" . -}} - -{{- if has "app.k8s.io/v1beta1/Application" ._rox._apiServer.apiResources -}} -apiVersion: app.k8s.io/v1beta1 -kind: Application -metadata: - name: stackrox - namespace: {{ .Release.Namespace }} - labels: - {{- include "srox.labels" (list . "application" "stackrox") | nindent 4 }} - annotations: - {{- include "srox.annotations" (list . "application" "stackrox") | nindent 4 }} - {{ if eq ._rox.image.registry "quay.io/stackrox-io" }} - kubernetes-engine.cloud.google.com/icon: "data:image/png;base64,{{ .Files.Get "assets/StackRox_icon.png" | b64enc }}" - {{ else }} - kubernetes-engine.cloud.google.com/icon: "data:image/png;base64,{{ .Files.Get "assets/Red_Hat-Hat_icon.png" | b64enc }}" - {{ end }} -spec: - descriptor: - type: StackRox - version: {{ .Chart.AppVersion | quote }} - description: |- - StackRox Kubernetes Security Platform - - Version {{ .Chart.AppVersion }} - - ## Thank you for installing StackRox! - -
    - - #### Support - - [Email support@stackrox.com](mailto:support@stackrox.com?cc=sales@stackrox.com&Subject=StackRox%20Support%20Question&Body=Dear%20StackRox%20support,) - - ## Connecting to StackRox - -
    - - #### Directly using a Load Balancer - - When deploying StackRox with the `Load Balancer` network configuration, the service can be accessed directly. - - $CONNECT - - #### Tunneling via Port Forward - - When deploying StackRox with the `Node Port` or `None` network configuration, the service must be accessed using a port forward tunnel. - - - Step 1 - Start the port forward tunnel to the StackRox Central service. - - ``` - $ kubectl -n stackrox port-forward svc/central 8443:443 - ``` - - - Step 2 - In a browser, [visit https://localhost:8443](https://localhost:8443) to access StackRox. - - keywords: - - "stackrox" - - "kube" - - "security" - maintainers: - - name: StackRox, Inc. - url: https://stackrox.com - owners: - - name: StackRox, Inc. - url: https://stackrox.com - links: - - description: StackRox Help Documentation - url: "https://help.stackrox.com" - - info: - - name: StackRox namespace - value: stackrox - - name: StackRox admin username - value: "admin" - - selector: - matchLabels: - app.kubernetes.io/name: stackrox - - componentKinds: - - group: '' - kind: ConfigMap - - group: '' - kind: Secret - - group: '' - kind: PersistentVolumeClaim - - group: '' - kind: PersistentVolume - - group: '' - kind: Service - - group: '' - kind: ServiceAccount - - group: rbac.authorization.k8s.io - kind: ClusterRole - - group: rbac.authorization.k8s.io - kind: ClusterRoleBinding - - group: apps - kind: Deployment -{{- if ._rox.network.enableNetworkPolicies }} - - group: networking.k8s.io - kind: NetworkPolicy -{{- end }} - - group: rbac.authorization.k8s.io - kind: Role - - group: rbac.authorization.k8s.io - kind: RoleBinding - - group: route.openshift.io - kind: Route - - group: security.openshift.io - kind: SecurityContextConstraints - - group: admissionregistration.k8s.io - kind: ValidatingWebhookConfiguration - - group: autoscaling - kind: HorizontalPodAutoscaler - - group: storage.k8s.io - kind: StorageClass -{{- if ._rox.system.enablePodSecurityPolicies }} - - group: policy - kind: PodSecurityPolicy -{{- end }} -{{- end }} diff --git a/pkg/helm/charts/tests/centralservices/base_suite_test.go b/pkg/helm/charts/tests/centralservices/base_suite_test.go index e1da27eab219c..91a163712ac25 100644 --- a/pkg/helm/charts/tests/centralservices/base_suite_test.go +++ b/pkg/helm/charts/tests/centralservices/base_suite_test.go @@ -26,7 +26,6 @@ var ( Revision: 1, IsInstall: true, }, - APIVersions: append(chartutil.DefaultVersionSet, "app.k8s.io/v1beta1/Application"), } // A values YAML that sets all generatable values explicitly, and causes all diff --git a/pkg/helm/charts/tests/securedclusterservices/base_suite_test.go b/pkg/helm/charts/tests/securedclusterservices/base_suite_test.go index c2ebfdb4461a5..98cb4bf010421 100644 --- a/pkg/helm/charts/tests/securedclusterservices/base_suite_test.go +++ b/pkg/helm/charts/tests/securedclusterservices/base_suite_test.go @@ -25,7 +25,6 @@ var ( Revision: 1, IsInstall: true, }, - APIVersions: append(chartutil.DefaultVersionSet, "app.k8s.io/v1beta1/Application"), } // A values YAML that sets all generatable values explicitly, and causes all diff --git a/pkg/helm/config/roundtrip_test.go b/pkg/helm/config/roundtrip_test.go index 03206e561c8b3..597e0114bd355 100644 --- a/pkg/helm/config/roundtrip_test.go +++ b/pkg/helm/config/roundtrip_test.go @@ -30,7 +30,6 @@ var ( Revision: 1, IsInstall: true, }, - APIVersions: chartutil.DefaultVersionSet, } ) diff --git a/pkg/helm/util/render.go b/pkg/helm/util/render.go index 78e9f953d88cf..a12ed1668ae82 100644 --- a/pkg/helm/util/render.go +++ b/pkg/helm/util/render.go @@ -17,8 +17,6 @@ type Options struct { ReleaseOptions chartutil.ReleaseOptions KubeVersion string HelmVersion string - - APIVersions chartutil.VersionSet } // Render renders a chart locally, like renderutil.Render, but its options struct allows specifying @@ -38,10 +36,6 @@ func Render(c *chart.Chart, values chartutil.Values, opts Options) (map[string]s HelmVersion: chartutil.DefaultCapabilities.HelmVersion, } - if opts.APIVersions != nil { - caps.APIVersions = opts.APIVersions - } - if opts.KubeVersion != "" { kv, verErr := semver.NewVersion(opts.KubeVersion) if verErr != nil { From fad7abfa0e1089cb13c757157cb738548d679e05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:53:20 +0200 Subject: [PATCH 448/638] chore(deps): bump k8s.io/client-go from 0.37.0 to 0.37.1 in /tests/performance/scale (#23149) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/scale/go.mod | 6 +++--- tests/performance/scale/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/performance/scale/go.mod b/tests/performance/scale/go.mod index f8200e8e75790..00f91094ef10f 100644 --- a/tests/performance/scale/go.mod +++ b/tests/performance/scale/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/spf13/cobra v1.10.2 go.yaml.in/yaml/v3 v3.0.5 - k8s.io/client-go v0.37.0 + k8s.io/client-go v0.37.1 ) require ( @@ -69,8 +69,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.37.0 // indirect - k8s.io/apimachinery v0.37.0 // indirect + k8s.io/api v0.37.1 // indirect + k8s.io/apimachinery v0.37.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect diff --git a/tests/performance/scale/go.sum b/tests/performance/scale/go.sum index 62fc40ae1c7c9..a865a78a72587 100644 --- a/tests/performance/scale/go.sum +++ b/tests/performance/scale/go.sum @@ -194,12 +194,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= -k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= -k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= -k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= -k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= -k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= +k8s.io/api v0.37.1 h1:l6N77U7tjwB5L056bgrBTJIEdevac/naBZ3iSvDNfpM= +k8s.io/api v0.37.1/go.mod h1:zSlbB1YpJ1YQlFVQy20UYll81UJSJJUMLhkhvg6Z78M= +k8s.io/apimachinery v0.37.1 h1:hGCYyvKHCwtwMitj2vU4vYx0Z16N9GyZk9BBnz0wDAE= +k8s.io/apimachinery v0.37.1/go.mod h1:jF84AyUi/IRIXRot5f+lm6MpxoWI+F1XgjaMmwCdTFw= +k8s.io/client-go v0.37.1 h1:QTv/5ha4jAHtW9qxxVBkQVFBRDb4jHfFopQqqMdc+wM= +k8s.io/client-go v0.37.1/go.mod h1:dnAPtTnCNY38Ho04D2KdY1F4IKausa9UbqaAZKl60SY= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= From 66fd4ac9641bde481472cb6b01edf4d9294d5785 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Wed, 30 Sep 2026 11:05:50 +0200 Subject: [PATCH 449/638] docs: Document CI image location in AGENTS.md (#21700) Co-authored-by: Claude Opus 4.6 (1M context) --- AGENTS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b3d59803da998..52856e8ddb89a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,6 +147,11 @@ StackRox is a Kubernetes-native security platform with a distributed microservic - **Secured Cluster Services**: Deployed per monitored cluster (Sensor, Admission Controller) - **Multi-cluster support**: One Central instance monitors multiple Kubernetes clusters +### CI Image (Prow / OpenShift CI) +- The CI image is built from the **[stackrox/rox-ci-image](https://github.com/stackrox/rox-ci-image)** repo, not this repo. +- Tool versions bundled in the CI image (e.g. roxie, helm, docker) are pinned in Dockerfiles in that repo. +- `.openshift-ci/Dockerfile.build_root` in this repo only references the resulting image for validation purposes. + ### Key Directories - `/central/` - Central management service code - `/sensor/` - Sensor agent code for cluster monitoring From 1b4be85ef65b8339fc7a68bfa6fba08b1564eb96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:21:44 +0200 Subject: [PATCH 450/638] chore(deps): bump github.com/openshift-online/ocm-sdk-go from 0.1.509 to 0.1.515 (#23154) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 1d8f786ac5d42..a1f9499ed93d7 100644 --- a/go.mod +++ b/go.mod @@ -95,7 +95,7 @@ require ( github.com/onsi/gomega v1.43.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/openshift-online/ocm-sdk-go v0.1.509 + github.com/openshift-online/ocm-sdk-go v0.1.515 github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb github.com/openshift/client-go v0.0.0-20260317180604-743f664b82d1 github.com/openshift/controller-runtime-common v0.0.0-20260318085703-1812aed6dbd2 @@ -384,8 +384,8 @@ require ( github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect - github.com/openshift-online/ocm-api-model/clientapi v0.0.464 // indirect - github.com/openshift-online/ocm-api-model/model v0.0.464 // indirect + github.com/openshift-online/ocm-api-model/clientapi v0.0.469 // indirect + github.com/openshift-online/ocm-api-model/model v0.0.469 // indirect github.com/openshift/custom-resource-status v1.1.2 // indirect github.com/operator-framework/operator-lib v0.17.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect diff --git a/go.sum b/go.sum index afacb4c1f634e..4fb8ab445cbcd 100644 --- a/go.sum +++ b/go.sum @@ -1083,12 +1083,12 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openshift-online/ocm-api-model/clientapi v0.0.464 h1:MrAdeU7E0ElWu4Ztv7hMRqlzY6cdcQeqWiNaO00n2n4= -github.com/openshift-online/ocm-api-model/clientapi v0.0.464/go.mod h1:fZwy5HY2URG9nrExvQeXrDU/08TGqZ16f8oymVEN5lo= -github.com/openshift-online/ocm-api-model/model v0.0.464 h1:aZCIx0+EPZxVSbct4QQ9cSCaI1deoeAhfgwx5CfNl54= -github.com/openshift-online/ocm-api-model/model v0.0.464/go.mod h1:PQIoq6P8Vlb7goOdRMLK8nJY+B7HH0RTqYAa4kyidTE= -github.com/openshift-online/ocm-sdk-go v0.1.509 h1:JcmyaaJQslBqe4vIFugV0rQElGLojJSP/lO11moI/Ho= -github.com/openshift-online/ocm-sdk-go v0.1.509/go.mod h1:h1owsjOKlqLFRgzos5WWa0K0lSr8xRU6P1lF9EG94do= +github.com/openshift-online/ocm-api-model/clientapi v0.0.469 h1:SbIUwZrIsWu6WXSHRrU6kGMs+9MVv7lxuP53uoD3UbE= +github.com/openshift-online/ocm-api-model/clientapi v0.0.469/go.mod h1:fZwy5HY2URG9nrExvQeXrDU/08TGqZ16f8oymVEN5lo= +github.com/openshift-online/ocm-api-model/model v0.0.469 h1:Zfz1o01V5hurqP7CVEjPW5L0/3rRZgMbyRzMfHin8AY= +github.com/openshift-online/ocm-api-model/model v0.0.469/go.mod h1:PQIoq6P8Vlb7goOdRMLK8nJY+B7HH0RTqYAa4kyidTE= +github.com/openshift-online/ocm-sdk-go v0.1.515 h1:cioRrDcF5f0t4in+H1Qrk6u2L+A3PAJV8W5RpwNbYsE= +github.com/openshift-online/ocm-sdk-go v0.1.515/go.mod h1:1rP6JgFdlW8OJF9k8luQEb7mEZOPOTTCOEAKiPTg1uY= github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb h1:iwBR3mzmyE3EMFx7R3CQ9lOccTS0dNht8TW82aGITg0= github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/openshift/client-go v0.0.0-20260317180604-743f664b82d1 h1:Hr/R38eg5ZJXfbiaHumjJIN1buDZwhsm4ys4npVCXH0= From 43c8093ece3bf7c56a5c64ce3a323158b1ce5625 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 30 Sep 2026 11:45:31 +0200 Subject: [PATCH 451/638] ROX-36859: Properly clean up files (#23131) --- pkg/registrymirror/store_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/registrymirror/store_test.go b/pkg/registrymirror/store_test.go index 1d31e682108ff..705603c959772 100644 --- a/pkg/registrymirror/store_test.go +++ b/pkg/registrymirror/store_test.go @@ -172,6 +172,14 @@ func TestDataRaceAtCleanup(t *testing.T) { s.Cleanup() doneSignal.Signal() wg.Wait() + + // The stopped writers may still have a delayed updateConfigNow scheduled. + // One more update cancels those pending writes and lets us wait for the pipeline to flush, + // so no background write recreates the file after Cleanup(). + source := icspA.Spec.RepositoryDigestMirrors[0].Source + require.NoError(t, s.UpsertImageContentSourcePolicy(icspA)) + require.Eventually(t, func() bool { return fileContains(path, source) }, 5*time.Second, 10*time.Millisecond) + s.Cleanup() } func TestCleanup(t *testing.T) { From b6ed23d075afd711be3d984aaaa5a6ec6040e94d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:22:01 +0200 Subject: [PATCH 452/638] chore(deps): bump github.com/grpc-ecosystem/go-grpc-middleware/v2 from 2.3.3 to 2.3.4 (#23151) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a1f9499ed93d7..9ec24e12bb8df 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grafana/pyroscope-go v1.4.2 github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index 4fb8ab445cbcd..a1e3af545ffcc 100644 --- a/go.sum +++ b/go.sum @@ -763,8 +763,8 @@ github.com/grafana/pyroscope-go/godeltaprof v0.1.11 h1:el5LYpXissAiCKZ5/6yjlr6mh github.com/grafana/pyroscope-go/godeltaprof v0.1.11/go.mod h1:jl1V8M4cWsXciROCPIDDG7CtjSjT/ECbp6eLVuMxYRI= github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 h1:nz9OX9q1iUJZwrPA2Ge8xNlB3inmq+pafyf6qmBLBew= github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 h1:9ZJYjPEJpcleIKcqysXOo14NLQYwu23fzmKRFpIjPjc= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4/go.mod h1:0xydIeg2omQ9DH6zYMP24Kk57793rnLUvP8fwdbFvz8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= From a0070cab84775552370990ba5d4b064e01897879 Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Wed, 30 Sep 2026 15:42:26 +0200 Subject: [PATCH 453/638] fix(tests): clean up created policies first in AdmissionControllerTest (#23103) Co-authored-by: Claude Sonnet 4.5 --- .../groovy/AdmissionControllerTest.groovy | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy index c031ef3011efd..766e3b4e3482a 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy @@ -1,5 +1,7 @@ import static util.Helpers.withRetry +import io.grpc.Status +import io.grpc.StatusRuntimeException import io.stackrox.proto.storage.Cve.VulnerabilitySeverity import io.stackrox.proto.storage.ImageOuterClass import io.stackrox.proto.storage.PolicyOuterClass @@ -132,8 +134,12 @@ class AdmissionControllerTest extends BaseSpecification { def cleanupSpec() { orchestrator.deleteNamespace(TEST_NAMESPACE) - for (policyID in createdPolicyIds) { - PolicyService.deletePolicy(policyID) + try { + for (policyID in createdPolicyIds) { + PolicyService.deletePolicy(policyID) + } + } catch (Exception e) { + log.warn "Failed to delete one or more policies during cleanup: ${e.message}" } } @@ -249,6 +255,32 @@ class AdmissionControllerTest extends BaseSpecification { log.warn "Failed to confirm deletion of deployment ${deployment.name}. Subsequent tests may be affected ..." } + private Exception deletePolicyWithCaution(String policyId) { + try { + if (policyId) { + PolicyService.deletePolicy(policyId) + // Wait for policy deletion to propagate to admission controller + withRetry(10, 1) { + def policyExists = true + try { + Services.getPolicy(policyId) + } catch (StatusRuntimeException e) { + if (e.status.code == Status.Code.NOT_FOUND) { + policyExists = false + } else { + throw e + } + } + assert !policyExists : "Policy ${policyId} still exists after deletion" + } + } + } catch (Exception e) { + log.warn "Failed to delete policy ${policyId}: ${e.message}" + return e + } + return null + } + // Retry to allow time for the admission controller to fetch scan data from // Central. Policies that require image enrichment (e.g. severity) may not // evaluate on the first attempt if the AC pod handling this request hasn't @@ -313,6 +345,9 @@ class AdmissionControllerTest extends BaseSpecification { assert created == !blocked cleanup: + // Delete policy first to avoid enforcement blocking cleanup, + // and to prevent leftover policy from impacting later tests. + def policyDeletionFailure = deletePolicyWithCaution(policyId) if (created) { deleteDeploymentWithCaution(deployment) } @@ -320,12 +355,12 @@ class AdmissionControllerTest extends BaseSpecification { // Wait for full namespace deletion; a namespace left in Terminating state // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). orchestrator.deleteNamespace(testNs) - } finally { - // Delete the per-test policy even if the namespace deletion above times out - // and throws, otherwise the leftover policy affects later tests. - if (policyId) { - PolicyService.deletePolicy(policyId) - } + } catch (Exception e) { + log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" + throw e + } + if (policyDeletionFailure) { + throw policyDeletionFailure } where: @@ -417,6 +452,9 @@ class AdmissionControllerTest extends BaseSpecification { assert created2 cleanup: + // Delete policy first to avoid enforcement blocking cleanup, + // and to prevent leftover policy from impacting later tests. + def policyDeletionFailure = deletePolicyWithCaution(policyId) if (created2) { deleteDeploymentWithCaution(deployment2) } @@ -424,12 +462,12 @@ class AdmissionControllerTest extends BaseSpecification { // Wait for full namespace deletion; a namespace left in Terminating state // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). orchestrator.deleteNamespace(testNs) - } finally { - // Delete the per-test policy even if the namespace deletion above times out - // and throws, otherwise the leftover policy affects later tests. - if (policyId) { - PolicyService.deletePolicy(policyId) - } + } catch (Exception e) { + log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" + throw e + } + if (policyDeletionFailure) { + throw policyDeletionFailure } where: From 87e01a1fbf80fb4e197aa4b9519201e255f0d606 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 15:48:27 +0200 Subject: [PATCH 454/638] chore(deps-dev): bump brace-expansion from 1.1.16 to 1.1.21 in /tests/performance/load (#23158) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/load/package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 638304c522bd7..1731398081606 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -258,10 +258,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2715,9 +2716,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "requires": { "balanced-match": "^1.0.0", From fe5c61e23dbae8c58d95eda69e2728fe3142f8dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 15:49:16 +0200 Subject: [PATCH 455/638] chore(deps-dev): bump moment from 2.29.4 to 2.31.0 in /tests/performance/load (#23159) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/performance/load/package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 1731398081606..2dd22a073539b 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -1698,10 +1698,11 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -3845,9 +3846,9 @@ } }, "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "dev": true }, "ms": { From 2a358b3f41ac53fe7f82eef92efc16f7bab65d0a Mon Sep 17 00:00:00 2001 From: David House <105243888+davdhacs@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:17:45 -0600 Subject: [PATCH 456/638] chore(ci): pin gha to ubuntu24.04 (#23078) --- .../workflows/add-new-pr-to-oss-triaging.yml | 2 +- .github/workflows/add-vms-to-cluster.yml | 2 +- .github/workflows/audit-backport-prs.yml | 4 +-- .github/workflows/auto-merge.yml | 2 +- .github/workflows/backport-pr.yaml | 2 +- .github/workflows/batch-load-test-metrics.yml | 2 +- .github/workflows/build.yaml | 32 +++++++++---------- .../workflows/check-crd-compatibility.yaml | 2 +- .../check-crd-frozen-on-release.yaml | 2 +- .github/workflows/check-pr-title.yaml | 2 +- .github/workflows/ci-failures-report.yml | 2 +- .github/workflows/create-clusters.yml | 2 +- .github/workflows/cut-rc.yml | 12 +++---- .github/workflows/detect-changes.yml | 2 +- .github/workflows/e2e-byodb-tests.yaml | 2 +- .../e2e-db-backup-restore-tests.yaml | 2 +- .github/workflows/e2e-dispatch.yaml | 4 +-- .github/workflows/e2e-gke-upgrade-tests.yaml | 6 ++-- .github/workflows/e2e-nongroovy-tests.yaml | 2 +- .github/workflows/e2e-qa-tests.yaml | 2 +- .../emailsender-central-compatibility.yaml | 2 +- .github/workflows/finish-release.yml | 10 +++--- .github/workflows/fixxxer.yaml | 2 +- .github/workflows/labeler.yaml | 2 +- .github/workflows/nightly-tag.yml | 4 +-- .github/workflows/notify-milestone-change.yml | 4 +-- .github/workflows/performance-tests.yml | 4 +-- .github/workflows/release-ci.yaml | 14 ++++---- .github/workflows/retest_periodic.yml | 2 +- .../workflows/scan-image-vulnerabilities.yaml | 6 ++-- .../scanner-db-integration-tests.yaml | 2 +- .github/workflows/scanner-e2e-test.yaml | 4 +-- .github/workflows/scanner-mapping-update.yaml | 4 +-- .github/workflows/scanner-nvd-update.yaml | 12 +++---- .../scanner-offline-bundle-update.yaml | 8 ++--- .github/workflows/scanner-updater-bundle.yaml | 6 ++-- .github/workflows/scanner-updater-export.yaml | 6 ++-- .../workflows/scanner-updater-schedule.yaml | 6 ++-- .../scanner-versioned-definitions-update.yaml | 12 +++---- .../workflows/signing-key-bundle-update.yaml | 4 +-- .github/workflows/start-release.yml | 20 ++++++------ .github/workflows/style.yaml | 18 +++++------ .github/workflows/tag-more-reviewers.yaml | 2 +- .github/workflows/tagged-ci.yaml | 2 +- .github/workflows/triage-report_periodic.yml | 2 +- .github/workflows/unit-tests.yaml | 20 ++++++------ .../workflows/update_collector_periodic.yaml | 2 +- .github/workflows/update_fact_periodic.yaml | 2 +- .github/workflows/upgrade-test.yml | 10 +++--- .github/workflows/variables.yml | 2 +- .github/workflows/verify-release.yml | 6 ++-- 51 files changed, 144 insertions(+), 144 deletions(-) diff --git a/.github/workflows/add-new-pr-to-oss-triaging.yml b/.github/workflows/add-new-pr-to-oss-triaging.yml index dfb5b52a95100..9d7a10996440f 100644 --- a/.github/workflows/add-new-pr-to-oss-triaging.yml +++ b/.github/workflows/add-new-pr-to-oss-triaging.yml @@ -9,7 +9,7 @@ env: jobs: check-pr-if-external: name: Add external label to pull request if outside StackRox - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: GH_TOKEN: ${{ github.token }} BASE_REPO: ${{ github.repository }} diff --git a/.github/workflows/add-vms-to-cluster.yml b/.github/workflows/add-vms-to-cluster.yml index 58eb968bd3a87..b790abaf6be2a 100644 --- a/.github/workflows/add-vms-to-cluster.yml +++ b/.github/workflows/add-vms-to-cluster.yml @@ -29,7 +29,7 @@ on: jobs: add-vms: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 env: INFRA_TOKEN: ${{ secrets.INFRA_TOKEN }} diff --git a/.github/workflows/audit-backport-prs.yml b/.github/workflows/audit-backport-prs.yml index 08ceefc68c375..b220cb7c4e725 100644 --- a/.github/workflows/audit-backport-prs.yml +++ b/.github/workflows/audit-backport-prs.yml @@ -29,7 +29,7 @@ env: jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.slack-channel, steps.properties.outputs.dry-slack-channel))[(github.event_name == 'workflow_dispatch' && inputs.dry_run) || github.event_name == 'pull_request'] }} steps: @@ -40,7 +40,7 @@ jobs: run: gh api -H "$ACCEPT_RAW" "$PROPERTIES_URL" >> "$GITHUB_OUTPUT" audit: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [properties] permissions: contents: read diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 5ae4329f1151d..1a18592aebca3 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -16,7 +16,7 @@ concurrency: jobs: auto-merge: name: Enable auto-merge for eligible PRs - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.repository_owner == 'stackrox' steps: diff --git a/.github/workflows/backport-pr.yaml b/.github/workflows/backport-pr.yaml index 817043cc41ea0..dc54e31818fce 100644 --- a/.github/workflows/backport-pr.yaml +++ b/.github/workflows/backport-pr.yaml @@ -16,7 +16,7 @@ defaults: jobs: backport: name: Backport - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > diff --git a/.github/workflows/batch-load-test-metrics.yml b/.github/workflows/batch-load-test-metrics.yml index 8168c7cfd63fd..f798db49af93d 100644 --- a/.github/workflows/batch-load-test-metrics.yml +++ b/.github/workflows/batch-load-test-metrics.yml @@ -10,7 +10,7 @@ concurrency: Batch load test metrics jobs: batch-load-test-metrics: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6776ef11d814c..d2932976e7487 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -164,7 +164,7 @@ jobs: ROX_PRODUCT_BRANDING: ${{ matrix.branding }} # For consistency with `image/rhel/konflux.Dockerfile`, see comment there. UI_PKG_INSTALL_EXTRA_ARGS: --ignore-scripts - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -205,7 +205,7 @@ jobs: pre-build-cli: needs: define-job-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: BUILD_TAG: ${{ needs.define-job-matrix.outputs.build-tag }} SHORTCOMMIT: ${{ needs.define-job-matrix.outputs.short-commit }} @@ -283,7 +283,7 @@ jobs: # race-condition-debug - built with -race matrix: ${{ fromJson(needs.define-job-matrix.outputs.matrix).pre_build_go_binaries }} needs: define-job-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: BUILD_TAG: ${{ needs.define-job-matrix.outputs.build-tag }} SHORTCOMMIT: ${{ needs.define-job-matrix.outputs.short-commit }} @@ -363,7 +363,7 @@ jobs: compression-level: 1 pre-build-docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -394,7 +394,7 @@ jobs: image/rhel/docs pre-build-oss-notice: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -435,7 +435,7 @@ jobs: path: image/rhel/THIRD_PARTY_NOTICES build-and-push-db: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix if: ${{ !cancelled() }} @@ -541,7 +541,7 @@ jobs: "docker://quay.io/rhacs-eng/scanner-v4-db:${{ env.BUILD_TAG }}" build-operator-bundle: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: QUAY_ORG: rhacs-eng QUAY_RHACS_ENG_RW_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }} @@ -579,7 +579,7 @@ jobs: build-and-push-main: # Use native arm64 runner to avoid QEMU emulation overhead (~2x slower). - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - pre-build-cli @@ -755,7 +755,7 @@ jobs: push_main_image_set "$push_context" "${{ env.ROX_PRODUCT_BRANDING }}" "${{ matrix.arch }}" push-matching-collector-scanner: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix if: ${{ !cancelled() }} @@ -788,7 +788,7 @@ jobs: push_matching_collector_scanner_images "${{ env.ROX_PRODUCT_BRANDING }}" push-main-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-main @@ -911,7 +911,7 @@ jobs: message-path: build-comment.md build-and-push-operator: - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - pre-build-go-binaries @@ -1027,7 +1027,7 @@ jobs: echo "Operator image push completed successfully" build-and-push-scanner: - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - build-and-push-db @@ -1111,7 +1111,7 @@ jobs: push_scanner_image_set "${{ matrix.registry }}" "${{ matrix.arch }}" push-scanner-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-scanner @@ -1171,7 +1171,7 @@ jobs: push_scanner_image_manifest_lists "${{ matrix.registry }}" "$architectures" push-operator-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-operator @@ -1228,7 +1228,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'scan-go-binaries') env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - pre-build-cli - pre-build-go-binaries @@ -1289,7 +1289,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - build-and-push-db - build-and-push-main diff --git a/.github/workflows/check-crd-compatibility.yaml b/.github/workflows/check-crd-compatibility.yaml index 95ba18adc4a75..5aa9f878ea3ab 100644 --- a/.github/workflows/check-crd-compatibility.yaml +++ b/.github/workflows/check-crd-compatibility.yaml @@ -23,7 +23,7 @@ jobs: check-crd-compatibility: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 outputs: diff --git a/.github/workflows/check-crd-frozen-on-release.yaml b/.github/workflows/check-crd-frozen-on-release.yaml index fa64eb4493aec..981253e8cfb15 100644 --- a/.github/workflows/check-crd-frozen-on-release.yaml +++ b/.github/workflows/check-crd-frozen-on-release.yaml @@ -35,7 +35,7 @@ permissions: jobs: check-crds-frozen: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout stackrox/stackrox uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/check-pr-title.yaml b/.github/workflows/check-pr-title.yaml index 5212dfd3ac09b..49afd75bf89a4 100644 --- a/.github/workflows/check-pr-title.yaml +++ b/.github/workflows/check-pr-title.yaml @@ -6,7 +6,7 @@ on: jobs: check-title: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: PR_TITLE: ${{ github.event.pull_request.title }} steps: diff --git a/.github/workflows/ci-failures-report.yml b/.github/workflows/ci-failures-report.yml index 9be7d94707cf8..a2e0e03d9a7bf 100644 --- a/.github/workflows/ci-failures-report.yml +++ b/.github/workflows/ci-failures-report.yml @@ -18,7 +18,7 @@ on: jobs: report-e2e-failures-to-slack: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/create-clusters.yml b/.github/workflows/create-clusters.yml index 5249b320bacbf..cda2e693e84e4 100644 --- a/.github/workflows/create-clusters.yml +++ b/.github/workflows/create-clusters.yml @@ -42,7 +42,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: trim-cluster-names: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cluster-with-fake-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-fake-load-name-cleaned }} cluster-with-real-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-real-load-name-cleaned }} diff --git a/.github/workflows/cut-rc.yml b/.github/workflows/cut-rc.yml index 402dda5b0e6a2..6baf9ba5875a9 100644 --- a/.github/workflows/cut-rc.yml +++ b/.github/workflows/cut-rc.yml @@ -60,7 +60,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: properties: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.fetch.outputs.dry-slack-channel, steps.fetch.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} jira-project: ${{ steps.fetch.outputs.jira-project }} @@ -73,7 +73,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -96,7 +96,7 @@ jobs: check-jira: name: Check Jira tickets for release needs: [variables, properties] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Query JIRA # Checking unequal with "false" because closed milestones have unset input values. @@ -117,7 +117,7 @@ jobs: postpone-prs: name: Postpone open PRs needs: variables - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check open PRs id: check @@ -173,7 +173,7 @@ jobs: cut-rc: name: Tag RC for milestone ${{needs.variables.outputs.milestone}} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties, postpone-prs, check-jira] steps: - name: Check out code @@ -308,7 +308,7 @@ jobs: :arrow_right: Once all checks pass and you're ready for release, run the <${{ github.server_url }}/${{ github.repository }}/actions/workflows/finish-release.yml|Finish Release> workflow and delete the `${{ needs.variables.outputs.next-milestone }}` milestone to avoid confusion. trim-cluster-names: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cluster-with-fake-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-fake-load-name-cleaned }} cluster-with-real-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-real-load-name-cleaned }} diff --git a/.github/workflows/detect-changes.yml b/.github/workflows/detect-changes.yml index b36dab630fc9f..8cced67cfd949 100644 --- a/.github/workflows/detect-changes.yml +++ b/.github/workflows/detect-changes.yml @@ -39,7 +39,7 @@ on: jobs: detect: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: files: ${{ steps.list.outputs.files }} ui_only: ${{ steps.list.outputs.ui_only }} diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index 7b698cd05a5a1..ea298e07a8dba 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -29,7 +29,7 @@ defaults: jobs: gke-byodb-qa-e2e-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} diff --git a/.github/workflows/e2e-db-backup-restore-tests.yaml b/.github/workflows/e2e-db-backup-restore-tests.yaml index eff0bfcb79fda..d2a890a0daad5 100644 --- a/.github/workflows/e2e-db-backup-restore-tests.yaml +++ b/.github/workflows/e2e-db-backup-restore-tests.yaml @@ -29,7 +29,7 @@ defaults: jobs: gke-db-backup-restore-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index 178a967d166e7..b0e1870ba2007 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -135,7 +135,7 @@ jobs: wait-for-images: needs: [ should-dispatch ] if: needs.should-dispatch.outputs.wait-for-gha-images == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG_DIR: "." IMAGE_PREFIX: "" @@ -198,7 +198,7 @@ jobs: wait-for-konflux-images: needs: [ should-dispatch ] if: needs.should-dispatch.outputs.wait-for-konflux-images == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG_DIR: "operator" IMAGE_PREFIX: "release-" diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index 40f394eb5c724..d4f5977da436a 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -31,7 +31,7 @@ jobs: # Verifies Central DB upgrades/rollbacks. gke-upgrade-tests-central: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -201,7 +201,7 @@ jobs: gke-upgrade-tests-postgres: needs: check-postgres-upgrade if: needs.check-postgres-upgrade.outputs.required == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -352,7 +352,7 @@ jobs: # Verifies Sensor/Secured Cluster `upgrader` (applicable to manifest installations only). gke-upgrade-tests-sensor: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index d7a1cead3ca7d..e3df36cf7a676 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -29,7 +29,7 @@ defaults: jobs: gke-nongroovy-e2e-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index fe63f141908e0..9427cfc7cb047 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -72,7 +72,7 @@ defaults: jobs: qa-e2e-tests: if: inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} diff --git a/.github/workflows/emailsender-central-compatibility.yaml b/.github/workflows/emailsender-central-compatibility.yaml index 9600d3cf78c94..ddbb8a009c46c 100644 --- a/.github/workflows/emailsender-central-compatibility.yaml +++ b/.github/workflows/emailsender-central-compatibility.yaml @@ -27,7 +27,7 @@ jobs: e2e-test-on-kind: timeout-minutes: 60 name: "Test on kind cluster" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ !github.event.pull_request.head.repo.fork }} # do not run for PRs from forks permissions: id-token: write diff --git a/.github/workflows/finish-release.yml b/.github/workflows/finish-release.yml index 480a4b67d6bfc..3e7a22ecaeed7 100644 --- a/.github/workflows/finish-release.yml +++ b/.github/workflows/finish-release.yml @@ -34,7 +34,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: properties: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.fetch.outputs.dry-slack-channel, steps.fetch.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} steps: @@ -47,7 +47,7 @@ jobs: run-parameters: if: github.event_name == 'workflow_dispatch' name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -70,7 +70,7 @@ jobs: preflight-checks: needs: [variables] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Verify that this release is listed in scanner’s RELEASE_VERSION file on master run: | @@ -81,7 +81,7 @@ jobs: publish-release: name: Tag Release ${{ needs.variables.outputs.named-release-patch }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties, preflight-checks] steps: - name: Check out code @@ -183,7 +183,7 @@ jobs: update-infra-demo-version: name: Update infra demo default versions - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [publish-release] env: BRANCH: "bump-demo-versions-${{ inputs.version }}" diff --git a/.github/workflows/fixxxer.yaml b/.github/workflows/fixxxer.yaml index d581a1ae15229..f2706f6355ef5 100644 --- a/.github/workflows/fixxxer.yaml +++ b/.github/workflows/fixxxer.yaml @@ -15,7 +15,7 @@ jobs: name: Run PR Fixxxer # TODO: support leading and trailing whitespace too if: ${{ github.event.issue.pull_request && github.event.comment.body == '/fixxx' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml index 3a48fb21bfee2..53e135c2eebf5 100644 --- a/.github/workflows/labeler.yaml +++ b/.github/workflows/labeler.yaml @@ -8,7 +8,7 @@ on: jobs: label-pr: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork }} steps: - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # ratchet:actions/labeler@v6 diff --git a/.github/workflows/nightly-tag.yml b/.github/workflows/nightly-tag.yml index f40edc3724b0e..6ef44bae5fd8f 100644 --- a/.github/workflows/nightly-tag.yml +++ b/.github/workflows/nightly-tag.yml @@ -6,7 +6,7 @@ on: jobs: create-nightly-tag: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -20,7 +20,7 @@ jobs: slack-on-failure: needs: create-nightly-tag if: failure() - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Slack on failure uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # ratchet:slackapi/slack-github-action@v3.0.3 diff --git a/.github/workflows/notify-milestone-change.yml b/.github/workflows/notify-milestone-change.yml index a6a648b50dec0..dc327ead0b241 100644 --- a/.github/workflows/notify-milestone-change.yml +++ b/.github/workflows/notify-milestone-change.yml @@ -18,7 +18,7 @@ env: jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: docs-repository: ${{ steps.properties.outputs.docs-repository }} slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[github.repository == 'stackrox/stackrox'] }} @@ -31,7 +31,7 @@ jobs: run: gh api -H "$ACCEPT_RAW" "$PROPERTIES_URL" >> "$GITHUB_OUTPUT" notify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [properties] steps: - name: Determine Jira context for PR diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml index c1561db61871e..13ced2205fb65 100644 --- a/.github/workflows/performance-tests.yml +++ b/.github/workflows/performance-tests.yml @@ -10,7 +10,7 @@ concurrency: performance-tests-${{ github.ref }} jobs: wait-for-images: name: Wait for images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ contains(github.event.pull_request.labels.*.name, 'ci-performance-tests') }} steps: - name: Checkout repo @@ -33,7 +33,7 @@ jobs: run-k6-performance-tests: needs: [wait-for-images] name: Run k6 performance tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ contains(github.event.pull_request.labels.*.name, 'ci-performance-tests') }} env: USE_GKE_GCLOUD_AUTH_PLUGIN: "True" diff --git a/.github/workflows/release-ci.yaml b/.github/workflows/release-ci.yaml index ea7cc7238d2b6..32c8cd7fb54c5 100644 --- a/.github/workflows/release-ci.yaml +++ b/.github/workflows/release-ci.yaml @@ -9,7 +9,7 @@ on: jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | echo "Event: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" @@ -21,7 +21,7 @@ jobs: secrets: inherit check-is-release: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: is_release: ${{ steps.is_release_check.outputs.is_release }} steps: @@ -40,7 +40,7 @@ jobs: fi check-collector-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -53,7 +53,7 @@ jobs: check_collector_version check-fact-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -68,7 +68,7 @@ jobs: publish-helm-charts: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -97,7 +97,7 @@ jobs: publish-cli: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -123,7 +123,7 @@ jobs: publish-openapispec: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/retest_periodic.yml b/.github/workflows/retest_periodic.yml index d30e223e91813..3c371a6102804 100644 --- a/.github/workflows/retest_periodic.yml +++ b/.github/workflows/retest_periodic.yml @@ -10,7 +10,7 @@ jobs: retest: if: ${{ github.repository_owner == 'stackrox' }} timeout-minutes: 10 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/scan-image-vulnerabilities.yaml b/.github/workflows/scan-image-vulnerabilities.yaml index 2ee393ab6b219..bb4befe0852d1 100644 --- a/.github/workflows/scan-image-vulnerabilities.yaml +++ b/.github/workflows/scan-image-vulnerabilities.yaml @@ -21,7 +21,7 @@ run-name: ${{ format('Scan images for vulnerabilities ({0})', inputs.version || jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: wait-for-images-limit: ${{ steps.run-parameters.outputs.wait-for-images-limit }} version: ${{ steps.run-parameters.outputs.version }} @@ -47,7 +47,7 @@ jobs: scan-image-vulnerabilities-upstream: name: "Upstream: ${{ matrix.image }}:${{ needs.run-parameters.outputs.version }}" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [run-parameters] permissions: # Needed for stackrox/central-login to create the JWT token. @@ -99,7 +99,7 @@ jobs: scan-image-vulnerabilities-konflux: name: "Konflux: ${{ matrix.image }}:${{ needs.run-parameters.outputs.version }}" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [run-parameters] permissions: # Needed for stackrox/central-login to create the JWT token. diff --git a/.github/workflows/scanner-db-integration-tests.yaml b/.github/workflows/scanner-db-integration-tests.yaml index 785f47924b715..effbe1190271e 100644 --- a/.github/workflows/scanner-db-integration-tests.yaml +++ b/.github/workflows/scanner-db-integration-tests.yaml @@ -20,7 +20,7 @@ jobs: db-integration-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 steps: diff --git a/.github/workflows/scanner-e2e-test.yaml b/.github/workflows/scanner-e2e-test.yaml index e4ee1333e3964..b2d0e9670e165 100644 --- a/.github/workflows/scanner-e2e-test.yaml +++ b/.github/workflows/scanner-e2e-test.yaml @@ -14,7 +14,7 @@ jobs: if: > github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'scanner-functional-tests') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -36,7 +36,7 @@ jobs: name: Deploy and run needs: - scanner-ft-images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: SCANNER_E2E_QUAY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} SCANNER_E2E_QUAY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} diff --git a/.github/workflows/scanner-mapping-update.yaml b/.github/workflows/scanner-mapping-update.yaml index 9883bf11b4c04..236c427937429 100644 --- a/.github/workflows/scanner-mapping-update.yaml +++ b/.github/workflows/scanner-mapping-update.yaml @@ -7,7 +7,7 @@ on: jobs: update-scanner-mappings: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: REPOMAPPING_DIR: repomapping steps: @@ -37,7 +37,7 @@ jobs: send-notification: needs: update-scanner-mappings if: failure() - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-nvd-update.yaml b/.github/workflows/scanner-nvd-update.yaml index 0a29f375363c7..8294ffb27ab71 100644 --- a/.github/workflows/scanner-nvd-update.yaml +++ b/.github/workflows/scanner-nvd-update.yaml @@ -12,7 +12,7 @@ on: jobs: fetch-nvd-feeds: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.repository_owner == 'stackrox' && github.event_name == 'schedule' || @@ -51,7 +51,7 @@ jobs: if-no-files-found: error fetch-nvd-api: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || @@ -94,7 +94,7 @@ jobs: upload-pr-nvd-feeds: needs: - fetch-nvd-feeds - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-nvd') steps: # Checkout master to get the latest local actions @@ -153,7 +153,7 @@ jobs: upload-nvd-feeds: needs: - fetch-nvd-feeds - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name != 'pull_request' steps: - name: Checkout repository @@ -182,7 +182,7 @@ jobs: upload-nvd-api: needs: - fetch-nvd-api - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name != 'pull_request' steps: - name: Checkout repository @@ -212,7 +212,7 @@ jobs: needs: - upload-nvd-feeds - upload-nvd-api - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ failure() && github.ref_name == 'master' }} permissions: contents: read diff --git a/.github/workflows/scanner-offline-bundle-update.yaml b/.github/workflows/scanner-offline-bundle-update.yaml index 657246451eb02..10213e553e435 100644 --- a/.github/workflows/scanner-offline-bundle-update.yaml +++ b/.github/workflows/scanner-offline-bundle-update.yaml @@ -11,7 +11,7 @@ on: jobs: offline-bundle-matrix: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.repository_owner == 'stackrox' && github.event_name == 'schedule' || @@ -36,7 +36,7 @@ jobs: offline-bundle-generate: needs: offline-bundle-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -69,7 +69,7 @@ jobs: if-no-files-found: error offline-bundle-upload: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - offline-bundle-generate steps: @@ -125,7 +125,7 @@ jobs: - offline-bundle-matrix - offline-bundle-generate - offline-bundle-upload - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'schedule' && failure() permissions: contents: read diff --git a/.github/workflows/scanner-updater-bundle.yaml b/.github/workflows/scanner-updater-bundle.yaml index 8f26a5e8e6ba4..de4e98dc7c40a 100644 --- a/.github/workflows/scanner-updater-bundle.yaml +++ b/.github/workflows/scanner-updater-bundle.yaml @@ -21,7 +21,7 @@ permissions: jobs: build-matrix: if: github.repository_owner == 'stackrox' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: streams: ${{ steps.emit.outputs.streams }} steps: @@ -40,7 +40,7 @@ jobs: bundle: needs: build-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 volumes: @@ -216,7 +216,7 @@ jobs: notify: needs: [bundle] if: always() && needs.bundle.result == 'failure' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-updater-export.yaml b/.github/workflows/scanner-updater-export.yaml index 91e3862ab8ba4..cbe0b3a3fa41c 100644 --- a/.github/workflows/scanner-updater-export.yaml +++ b/.github/workflows/scanner-updater-export.yaml @@ -40,7 +40,7 @@ permissions: jobs: recheck: if: ${{ !inputs.skip_freshness_check }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: result: ${{ steps.check.outputs.result }} concurrency: @@ -89,7 +89,7 @@ jobs: update: needs: recheck if: always() && (needs.recheck.result == 'skipped' || needs.recheck.outputs.result == 'run') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 concurrency: group: scanner-updater-export-${{ inputs.object }} cancel-in-progress: false @@ -195,7 +195,7 @@ jobs: !inputs.skip_freshness_check && always() && (needs.recheck.result == 'failure' || needs.update.result == 'failure') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-updater-schedule.yaml b/.github/workflows/scanner-updater-schedule.yaml index 7999ec6045cbc..0fb2cc1079316 100644 --- a/.github/workflows/scanner-updater-schedule.yaml +++ b/.github/workflows/scanner-updater-schedule.yaml @@ -21,7 +21,7 @@ jobs: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') ) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: needs-notification: ${{ steps.matrix-scheduled.outputs.has_errors }} steps: @@ -98,7 +98,7 @@ jobs: plan: needs: build-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: has_due: ${{ steps.emit.outputs.has_due }} matrix: ${{ steps.emit.outputs.matrix }} @@ -152,7 +152,7 @@ jobs: if: >- github.event_name != 'pull_request' && (failure() || needs.build-matrix.outputs.needs-notification == 'true') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-versioned-definitions-update.yaml b/.github/workflows/scanner-versioned-definitions-update.yaml index 3216f74a5bcf5..c9ba26d32df8f 100644 --- a/.github/workflows/scanner-versioned-definitions-update.yaml +++ b/.github/workflows/scanner-versioned-definitions-update.yaml @@ -43,7 +43,7 @@ on: jobs: parse-versions: if: github.repository_owner == 'stackrox' && github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: versions: ${{ steps.set-versions.outputs.versions }} steps: @@ -102,7 +102,7 @@ jobs: prepare-environment: if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: manual_url: ${{ steps.set-manual-url.outputs.manual_url }} steps: @@ -187,7 +187,7 @@ jobs: needs: - parse-versions - prepare-environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 volumes: @@ -340,7 +340,7 @@ jobs: if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') needs: - prepare-environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 volumes: @@ -430,7 +430,7 @@ jobs: needs: - build-and-run if: ${{ (failure() || success()) && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')) }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: # Checkout to run ./.github/actions/download-artifact-with-retry - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -510,7 +510,7 @@ jobs: needs: - build-and-run - upload-definitions - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ failure() && github.ref_name == 'master' && (github.event_name == 'schedule' || github.event.inputs.dry_run == 'false') }} permissions: checks: read diff --git a/.github/workflows/signing-key-bundle-update.yaml b/.github/workflows/signing-key-bundle-update.yaml index d456185299106..fda57fde6bd44 100644 --- a/.github/workflows/signing-key-bundle-update.yaml +++ b/.github/workflows/signing-key-bundle-update.yaml @@ -18,7 +18,7 @@ concurrency: jobs: validate-and-upload: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -48,7 +48,7 @@ jobs: send-notification: needs: - validate-and-upload - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: failure() permissions: contents: read diff --git a/.github/workflows/start-release.yml b/.github/workflows/start-release.yml index 34116d543db82..d23caf47e4a8b 100644 --- a/.github/workflows/start-release.yml +++ b/.github/workflows/start-release.yml @@ -42,7 +42,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: start-sha: name: Find commit for release branch - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: start-sha: ${{ fromJSON(format('["{0}","{1}"]', env.USER_REF, steps.find.outputs.merge_base))[env.USER_REF == ''] }} steps: @@ -67,7 +67,7 @@ jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: docs-repository: ${{ steps.properties.outputs.docs-repository }} slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} @@ -81,7 +81,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -102,7 +102,7 @@ jobs: check-jira: name: Check Jira release needs: [variables, properties] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: release-date: ${{steps.check-jira-release.outputs.date}} steps: @@ -122,7 +122,7 @@ jobs: branch: name: Prepare release branch needs: [variables, start-sha, check-jira] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code (normal release) if: needs.variables.outputs.patch == 0 @@ -193,7 +193,7 @@ jobs: name: Configure OpenShift CI jobs needs: [variables, branch] if: needs.variables.outputs.patch == 0 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: RELEASE: "${{needs.variables.outputs.release}}" BRANCH: "stackrox-release-${{needs.variables.outputs.release}}" @@ -262,7 +262,7 @@ jobs: name: Patch CHANGELOG.md on the default branch needs: [variables, branch, start-sha] if: needs.variables.outputs.patch == 0 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -288,7 +288,7 @@ jobs: patch-scanner-versions: name: Patch Scanner updater configuration needs: [variables, branch] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -310,7 +310,7 @@ jobs: milestone: name: Create milestone needs: [variables] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Create ${{needs.variables.outputs.next-milestone}} milestone if: env.DRY_RUN == 'false' @@ -336,7 +336,7 @@ jobs: notify: name: Notify everybody needs: [variables, properties, branch, milestone] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Post to Slack (normal release) if: needs.variables.outputs.patch == 0 diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml index 0cfe9f54c7978..3989115a09bab 100644 --- a/.github/workflows/style.yaml +++ b/.github/workflows/style.yaml @@ -27,7 +27,7 @@ jobs: if: ${{ !needs.detect-changes.outputs.ui_only }} env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -54,7 +54,7 @@ jobs: misc-checks: env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -97,7 +97,7 @@ jobs: run: if [ -s scripts/ci/jobs/check-dependabot-gomod.sh ]; then scripts/ci/jobs/check-dependabot-gomod.sh; fi style-check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -159,7 +159,7 @@ jobs: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} timeout-minutes: 240 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -208,7 +208,7 @@ jobs: # This job ensures that COLLECTOR_VERSION or FACT_VERSION files cannot be updated to a version # for which the image was not successfully built on Konflux (suffix "-fast"). It also verifies that GHA-built image # is there (no suffix) so that the failure also happens in this job. - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -253,7 +253,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.github/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - uses: ./.github/actions/job-preamble @@ -267,7 +267,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.github/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - uses: ./.github/actions/job-preamble @@ -281,7 +281,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.openshift-ci/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -313,7 +313,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - check-generated-files - misc-checks diff --git a/.github/workflows/tag-more-reviewers.yaml b/.github/workflows/tag-more-reviewers.yaml index f6ce74aac7c50..4b94157c38a6d 100644 --- a/.github/workflows/tag-more-reviewers.yaml +++ b/.github/workflows/tag-more-reviewers.yaml @@ -18,7 +18,7 @@ jobs: github.event.pull_request.user.login != 'red-hat-konflux[bot]' env: GH_TOKEN: ${{ secrets.RHACS_BOT_GITHUB_TOKEN }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Tag Konflux Maintainers for review run: | diff --git a/.github/workflows/tagged-ci.yaml b/.github/workflows/tagged-ci.yaml index 028412d071aa8..e936f4f4ea0ca 100644 --- a/.github/workflows/tagged-ci.yaml +++ b/.github/workflows/tagged-ci.yaml @@ -8,7 +8,7 @@ on: jobs: slack-tagged-notice: name: Slack Tagged Notice - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/triage-report_periodic.yml b/.github/workflows/triage-report_periodic.yml index 4843660c8ab0b..2df7169befde0 100644 --- a/.github/workflows/triage-report_periodic.yml +++ b/.github/workflows/triage-report_periodic.yml @@ -6,7 +6,7 @@ on: jobs: triage-report: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index d5ca5b28812be..51b67cd25c7f2 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -41,7 +41,7 @@ jobs: gotags: [ 'GOTAGS=""', 'GOTAGS=release' ] exclude: - gotags: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-release-build') && 'GOTAGS=release' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -116,7 +116,7 @@ jobs: exclude: - gotags: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-release-build') && 'GOTAGS=release' }} - pg: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-postgres-tests') && 'default' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} env: @@ -205,7 +205,7 @@ jobs: go-bench: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -264,7 +264,7 @@ jobs: !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^ui/') || contains(needs.detect-changes.outputs.files, '^.github/actions/cache-ui-dependencies/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -318,7 +318,7 @@ jobs: !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^ui/') || contains(needs.detect-changes.outputs.files, '^.github/actions/cache-ui-dependencies/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -380,7 +380,7 @@ jobs: local-roxctl-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} container: @@ -426,7 +426,7 @@ jobs: shell-unit-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} container: @@ -464,7 +464,7 @@ jobs: openshift-ci-unit-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: @@ -486,7 +486,7 @@ jobs: if: ${{ !needs.detect-changes.outputs.ui_only }} env: KUBECONFIG: "/tmp/kubeconfig" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -565,7 +565,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - go - go-bench diff --git a/.github/workflows/update_collector_periodic.yaml b/.github/workflows/update_collector_periodic.yaml index aca8fd0cf0f3e..6eec999095b3d 100644 --- a/.github/workflows/update_collector_periodic.yaml +++ b/.github/workflows/update_collector_periodic.yaml @@ -6,7 +6,7 @@ on: jobs: update-collector: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: diff --git a/.github/workflows/update_fact_periodic.yaml b/.github/workflows/update_fact_periodic.yaml index 12ec44dd22b65..1ba1644308290 100644 --- a/.github/workflows/update_fact_periodic.yaml +++ b/.github/workflows/update_fact_periodic.yaml @@ -6,7 +6,7 @@ on: jobs: update-fact: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index 78ddcbeb0c98d..0c8387864b17d 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -40,7 +40,7 @@ concurrency: Release automation ${{ inputs.milestone }} jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[inputs.dry-run != 'true'] }} steps: @@ -52,7 +52,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -66,7 +66,7 @@ jobs: create-clusters: needs: [properties] if: github.event.inputs.dry-run != 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: name: [test1, test2] @@ -83,7 +83,7 @@ jobs: prepare-clusters: needs: [properties, create-clusters] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -136,7 +136,7 @@ jobs: name: Notify about failed cluster creation needs: [properties, prepare-clusters] if: always() && needs.prepare-clusters.result == 'failure' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Post to Slack uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # ratchet:slackapi/slack-github-action@v3.0.3 diff --git a/.github/workflows/variables.yml b/.github/workflows/variables.yml index f574eec48faa4..6fedd4d7f5546 100644 --- a/.github/workflows/variables.yml +++ b/.github/workflows/variables.yml @@ -56,7 +56,7 @@ on: jobs: parse: name: Parse ${{inputs.version}} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: release: ${{steps.parse.outputs.release}} major: ${{steps.parse.outputs.major}} diff --git a/.github/workflows/verify-release.yml b/.github/workflows/verify-release.yml index 5751a5121b968..3792e9c80958b 100644 --- a/.github/workflows/verify-release.yml +++ b/.github/workflows/verify-release.yml @@ -32,7 +32,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | { @@ -52,7 +52,7 @@ jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: jira-project: ${{ steps.properties.outputs.jira-project }} steps: @@ -64,7 +64,7 @@ jobs: verify-release: name: Verify all artifacts for release ${{ needs.variables.outputs.named-release-patch }} are published - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties] env: JIRA_USER: ${{ vars.RHACS_BOT_GITHUB_EMAIL }} From 7af1f6f2aeb331773d446b920a649669eceb8d57 Mon Sep 17 00:00:00 2001 From: Jouko Virtanen Date: Wed, 30 Sep 2026 08:57:25 -0700 Subject: [PATCH 457/638] ROX-37017: Perf and scale tests should save collector metrics (#22830) --- scripts/ci/collect-collector-metrics.sh | 11 +++++++++-- scripts/ci/jobs/ocp-perf-scale-tests-post.sh | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/ci/collect-collector-metrics.sh b/scripts/ci/collect-collector-metrics.sh index 777e922197db9..6d9ef92fd4af7 100755 --- a/scripts/ci/collect-collector-metrics.sh +++ b/scripts/ci/collect-collector-metrics.sh @@ -53,6 +53,7 @@ main() { exit 0 fi + exit_code=0 for pod in ${pods}; do remote="${pod}:${pod_port}" metrics_file="${pod}.txt" @@ -73,11 +74,17 @@ main() { done echo echo "set up port-forwarding from $remote to $local" - curl --silent --fail -k "${local}/${metrics_path}" > "${metrics_dir}/${metrics_file}" - echo "finished download ${metrics_file}" + if curl --silent --fail -k "${local}/${metrics_path}" > "${metrics_dir}/${metrics_file}"; then + echo "finished download ${metrics_file}" + else + echo >&2 "failed to download metrics from $pod" + exit_code=1 + fi kill ${PID} echo "finished tear down of port-forwarding from $remote to $local" done + + return "${exit_code}" } main "$@" diff --git a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh index ca83582ff7713..cb0d75324c3fe 100755 --- a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh +++ b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh @@ -89,12 +89,24 @@ else info "Warning: Failed to get central diagnostics" fi +# Collect collector metrics +COLLECTOR_METRICS_OUTPUT="collector-metrics" +info "Collecting collector metrics to ${COLLECTOR_METRICS_OUTPUT}" +if "${ROOT}/scripts/ci/collect-collector-metrics.sh" stackrox "${COLLECTOR_METRICS_OUTPUT}"; then + info "Collected collector metrics to ${COLLECTOR_METRICS_OUTPUT}" +else + info "Warning: Failed to collect collector metrics" +fi + # Store artifacts to OpenShift CI artifact directory if [[ -n "${ARTIFACT_DIR:-}" ]]; then info "Copying diagnostics to ${ARTIFACT_DIR}" if [[ -d "${DIAGNOSTIC_OUTPUT}" ]]; then cp -r "${DIAGNOSTIC_OUTPUT}" "${ARTIFACT_DIR}/" || info "Warning: Failed to copy ${DIAGNOSTIC_OUTPUT}" fi + if [[ -d "${COLLECTOR_METRICS_OUTPUT}" ]]; then + cp -r "${COLLECTOR_METRICS_OUTPUT}" "${ARTIFACT_DIR}/" || info "Warning: Failed to copy ${COLLECTOR_METRICS_OUTPUT}" + fi else info "Warning: ARTIFACT_DIR not set, diagnostics not copied to artifacts" fi From 6e01785189bef42535acadfe299cc2c6c81c684e Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:44:15 +0200 Subject: [PATCH 458/638] chore(fixxxers): Ensure config controller CRDs are re-generated (#23102) --- scripts/fixxxers/config-controller/10-generate.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 scripts/fixxxers/config-controller/10-generate.sh diff --git a/scripts/fixxxers/config-controller/10-generate.sh b/scripts/fixxxers/config-controller/10-generate.sh new file mode 100755 index 0000000000000..22fcfa5a44923 --- /dev/null +++ b/scripts/fixxxers/config-controller/10-generate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +make config-controller-gen From bcc43573769aabc3d27552c80775f832865cc778 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:51:56 +0200 Subject: [PATCH 459/638] chore(deps): bump github.com/cockroachdb/pebble/v2 from 2.1.6 to 2.1.7 (#23155) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9ec24e12bb8df..c7e6f3cb34b02 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 github.com/cloudflare/cfssl v1.6.5 - github.com/cockroachdb/pebble/v2 v2.1.6 + github.com/cockroachdb/pebble/v2 v2.1.7 github.com/containers/image/v5 v5.36.2 github.com/coreos/go-oidc/v3 v3.20.0 github.com/coreos/go-systemd/v22 v22.7.0 @@ -243,7 +243,7 @@ require ( github.com/cockroachdb/errors v1.12.0 // indirect github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect github.com/cockroachdb/redact v1.1.6 // indirect - github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect + github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect github.com/coder/websocket v1.8.14 // indirect github.com/containers/storage v1.59.1 // indirect diff --git a/go.sum b/go.sum index a1e3af545ffcc..74babc197c6a6 100644 --- a/go.sum +++ b/go.sum @@ -330,12 +330,12 @@ github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILM github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA= -github.com/cockroachdb/pebble/v2 v2.1.6 h1:GDo7Z2+LgFZ7LJLdLmBXhDeTVIwgSPGxIT15hE7vGqM= -github.com/cockroachdb/pebble/v2 v2.1.6/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= +github.com/cockroachdb/pebble/v2 v2.1.7 h1:hFQnbsniSWg9BVcNKMuaUufYPiVXY6uJvaY9grbQ9+U= +github.com/cockroachdb/pebble/v2 v2.1.7/go.mod h1:JhU5cqqYkr2BdsBHbZhRZOryAtfhcV3eNI/oBcbrxWc= github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI= -github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg= +github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258 h1:IJ+uNItEm0qx9FE2AgIc1PMsCUtk8nbSIzhQE1t5GWw= +github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= From 1c597a3d790b817655530a3b06229e96a91cdeb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:25:18 +0200 Subject: [PATCH 460/638] chore(deps): bump go.opentelemetry.io/otel/sdk from 1.44.0 to 1.45.0 in /operator/tools/operator-sdk (#22893) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/operator-sdk/go.mod | 10 +++++----- operator/tools/operator-sdk/go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index 017715960a436..e68af3cf7313c 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-git/go-git/v5 v5.19.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/jsonreference v0.21.4 // indirect @@ -194,12 +194,12 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.podman.io/common v0.66.1 // indirect go.podman.io/image/v5 v5.38.0 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index c16a85d690c9e..6a377f0ac4059 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -161,8 +161,8 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -527,8 +527,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -553,16 +553,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.podman.io/common v0.66.1 h1:zDyd4HhVgQAN8LupBHCnhtM3FEOJ9DwmThjulXZq2qA= From d7b64b7eb4b13d513830d943952d495e772a3463 Mon Sep 17 00:00:00 2001 From: Saif Chaudhry Date: Wed, 30 Sep 2026 15:20:27 -0700 Subject: [PATCH 461/638] chore(ui): remove unused Access Control Redux writers (#22889) Signed-off-by: Saif Chaudhry --- ui/apps/platform/src/reducers/groups.js | 5 -- ui/apps/platform/src/reducers/roles.js | 39 ---------- ui/apps/platform/src/reducers/roles.test.js | 1 - ui/apps/platform/src/sagas/groupSagas.js | 24 +----- ui/apps/platform/src/sagas/roleSagas.js | 76 +------------------ .../platform/src/services/GroupsService.ts | 11 --- 6 files changed, 5 insertions(+), 151 deletions(-) diff --git a/ui/apps/platform/src/reducers/groups.js b/ui/apps/platform/src/reducers/groups.js index 763d38f677872..5416f27ed3f7d 100644 --- a/ui/apps/platform/src/reducers/groups.js +++ b/ui/apps/platform/src/reducers/groups.js @@ -6,7 +6,6 @@ import { createFetchingActionTypes, createFetchingActions } from 'utils/fetching export const types = { FETCH_RULE_GROUPS: createFetchingActionTypes('groups/FETCH_RULE_GROUPS'), SAVE_RULE_GROUP: 'groups/SAVE_RULE_GROUP', - DELETE_RULE_GROUP: 'groups/DELETE_RULE_GROUP', }; export const actions = { @@ -17,10 +16,6 @@ export const actions = { defaultRole, id, }), - deleteRuleGroup: (group) => ({ - type: types.DELETE_RULE_GROUP, - group, - }), }; const groups = (state = [], action) => { diff --git a/ui/apps/platform/src/reducers/roles.js b/ui/apps/platform/src/reducers/roles.js index 4e9b2d87c3741..13fe27efba559 100644 --- a/ui/apps/platform/src/reducers/roles.js +++ b/ui/apps/platform/src/reducers/roles.js @@ -13,26 +13,11 @@ export const ACCESS_LEVEL = Object.freeze({ export const types = { FETCH_USER_ROLE_PERMISSIONS: createFetchingActionTypes('roles/FETCH_USER_ROLE_PERMISSIONS'), FETCH_ROLES: createFetchingActionTypes('roles/FETCH_ROLES'), - SELECTED_ROLE: 'roles/SELECTED_ROLE', - SAVE_ROLE: 'roles/SAVE_ROLE', - DELETE_ROLE: 'roles/DELETE_ROLE', }; export const actions = { fetchUserRolePermissions: createFetchingActions(types.FETCH_USER_ROLE_PERMISSIONS), fetchRoles: createFetchingActions(types.FETCH_ROLES), - selectRole: (role) => ({ - type: types.SELECTED_ROLE, - role, - }), - saveRole: (role) => ({ - type: types.SAVE_ROLE, - role, - }), - deleteRole: (id) => ({ - type: types.DELETE_ROLE, - id, - }), }; const roles = (state = [], action) => { @@ -42,19 +27,6 @@ const roles = (state = [], action) => { return state; }; -const selectedRole = (state = null, action) => { - if (action.type === types.FETCH_ROLES.SUCCESS && !state) { - if (action.response.roles.length) { - return action.response.roles[0]; - } - return state; - } - if (action.type === types.SELECTED_ROLE && action.role) { - return isEqual(action.role, state) ? state : action.role; - } - return state; -}; - const userRolePermissions = (state = null, action) => { if (action.type === types.FETCH_USER_ROLE_PERMISSIONS.SUCCESS) { return isEqual(action.response, state) ? state : action.response; @@ -93,22 +65,16 @@ const isLoading = (state = true, action) => { const reducer = combineReducers({ roles, - selectedRole, userRolePermissions, error, isLoading, }); const getRoles = (state) => state.roles; -const getSelectedRole = (state) => state.selectedRole; const getUserRolePermissions = (state) => state.userRolePermissions; const getUserRolePermissionsError = (state) => state.error; const getIsLoadingUserRolePermissions = (state) => state.isLoading; -/* - * Given resource string (for example, "Integration") and role or permissionSet object, - * return access level (for example, "READ_ACCESS"). - */ const getAccessForPermission = (resource, userRolePermissionsArg) => { return userRolePermissionsArg?.resourceToAccess?.[resource] ?? ACCESS_LEVEL.NO_ACCESS; }; @@ -118,7 +84,6 @@ export const getHasReadPermission = (resource, userRolePermissionsArg) => { if (access === ACCESS_LEVEL.READ_WRITE_ACCESS || access === ACCESS_LEVEL.READ_ACCESS) { return true; } - // If the given resource doesn't yield the required access, try with the replacing resource (if there is any). if (replacedResourceMapping.has(resource)) { const replacingResourceAccess = getAccessForPermission( replacedResourceMapping.get(resource), @@ -129,7 +94,6 @@ export const getHasReadPermission = (resource, userRolePermissionsArg) => { replacingResourceAccess === ACCESS_LEVEL.READ_ACCESS ); } - // Return false if neither the resource nor the replacing resource have the correct access. return false; }; @@ -138,7 +102,6 @@ export const getHasReadWritePermission = (resource, userRolePermissionsArg) => { if (access === ACCESS_LEVEL.READ_WRITE_ACCESS) { return true; } - // If the given resource doesn't yield the required access, try with the replacing resource (if there is any). if (replacedResourceMapping.has(resource)) { const replacingResourceAccess = getAccessForPermission( replacedResourceMapping.get(resource), @@ -146,13 +109,11 @@ export const getHasReadWritePermission = (resource, userRolePermissionsArg) => { ); return replacingResourceAccess === ACCESS_LEVEL.READ_WRITE_ACCESS; } - // Return false if neither the resource nor the replacing resource have the correct access. return false; }; export const selectors = { getRoles, - getSelectedRole, getUserRolePermissions, getUserRolePermissionsError, getIsLoadingUserRolePermissions, diff --git a/ui/apps/platform/src/reducers/roles.test.js b/ui/apps/platform/src/reducers/roles.test.js index d63f8d79418ae..73dfef5bd619b 100644 --- a/ui/apps/platform/src/reducers/roles.test.js +++ b/ui/apps/platform/src/reducers/roles.test.js @@ -4,7 +4,6 @@ describe('roles reducer', () => { it('should return the initial state', () => { const expected = { roles: [], - selectedRole: null, userRolePermissions: null, error: null, isLoading: true, diff --git a/ui/apps/platform/src/sagas/groupSagas.js b/ui/apps/platform/src/sagas/groupSagas.js index a30ad168c3be8..bf49401d17e1d 100644 --- a/ui/apps/platform/src/sagas/groupSagas.js +++ b/ui/apps/platform/src/sagas/groupSagas.js @@ -1,6 +1,5 @@ -import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects'; +import { all, call, put, select, takeLatest } from 'redux-saga/effects'; import { - deleteRuleGroup as serviceDeleteRuleGroup, fetchGroups as serviceFetchGroups, getDefaultGroup as serviceGetDefaultGroup, updateOrAddGroup as serviceUpdateOrAddGroup, @@ -66,28 +65,9 @@ function* saveRuleGroup(action) { } } -function* deleteRuleGroup(action) { - const { group } = action; - try { - yield call(serviceDeleteRuleGroup, group); - yield call(getRuleGroups); - } catch (error) { - Raven.captureException(error); - } -} - -function* watchSaveRuleGroup() { - yield takeLatest(types.SAVE_RULE_GROUP, saveRuleGroup); -} - -function* watchDeleteRuleGroup() { - yield takeLatest(types.DELETE_RULE_GROUP, deleteRuleGroup); -} - export default function* groups() { yield all([ takeLatest(types.FETCH_RULE_GROUPS.REQUEST, getRuleGroups), - fork(watchSaveRuleGroup), - fork(watchDeleteRuleGroup), + takeLatest(types.SAVE_RULE_GROUP, saveRuleGroup), ]); } diff --git a/ui/apps/platform/src/sagas/roleSagas.js b/ui/apps/platform/src/sagas/roleSagas.js index 862de22e0dfb7..e3be95248d78e 100644 --- a/ui/apps/platform/src/sagas/roleSagas.js +++ b/ui/apps/platform/src/sagas/roleSagas.js @@ -1,15 +1,6 @@ -import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects'; -import { - createRole as serviceCreateRole, - deleteRole as serviceDeleteRole, - fetchRoles as serviceFetchRoles, - updateRole as serviceUpdateRole, -} from 'services/RolesService'; +import { all, call, put, takeLatest } from 'redux-saga/effects'; +import { fetchRoles as serviceFetchRoles } from 'services/RolesService'; import { actions, types } from 'reducers/roles'; -import { selectors } from 'reducers'; - -import Raven from 'raven-js'; -import { actions as notificationActions } from 'reducers/notifications'; function* getRoles() { try { @@ -20,67 +11,6 @@ function* getRoles() { } } -function* saveRole(action) { - try { - const { role } = action; - const roles = yield select(selectors.getRoles); - const isNewRole = !roles.filter((currRole) => currRole.name === role.name).length; - if (isNewRole) { - yield call(serviceCreateRole, role); - yield put(actions.selectRole(role)); - } else { - yield call(serviceUpdateRole, role); - yield put(actions.selectRole(role)); - } - yield call(getRoles); - } catch (error) { - yield put(notificationActions.addNotification(error.response.data.error)); - yield put(notificationActions.removeOldestNotification()); - Raven.captureException(error); - } -} - -function* deleteRole(action) { - const { id } = action; - try { - yield call(serviceDeleteRole, id); - yield put(actions.fetchRoles.request()); - } catch (error) { - yield put(notificationActions.addNotification(error.response.data.error)); - yield put(notificationActions.removeOldestNotification()); - Raven.captureException(error); - } -} - -function* selectRole(action) { - const { role } = action; - try { - if (!role) { - const roles = yield select(selectors.getRoles); - yield put(actions.selectRole(roles[0])); - } - } catch (error) { - Raven.captureException(error); - } -} - -function* watchSaveRole() { - yield takeLatest(types.SAVE_ROLE, saveRole); -} - -function* watchDeleteRole() { - yield takeLatest(types.DELETE_ROLE, deleteRole); -} - -function* watchSelectRole() { - yield takeLatest(types.SELECTED_ROLE, selectRole); -} - export default function* integrations() { - yield all([ - takeLatest(types.FETCH_ROLES.REQUEST, getRoles), - fork(watchSaveRole), - fork(watchDeleteRole), - fork(watchSelectRole), - ]); + yield all([takeLatest(types.FETCH_ROLES.REQUEST, getRoles)]); } diff --git a/ui/apps/platform/src/services/GroupsService.ts b/ui/apps/platform/src/services/GroupsService.ts index 83e0bffeae953..16b5cfd43fc20 100644 --- a/ui/apps/platform/src/services/GroupsService.ts +++ b/ui/apps/platform/src/services/GroupsService.ts @@ -48,14 +48,3 @@ export function getDefaultGroup({ response: response.data?.groups[0], })); } - -/** - * Deletes a group rule. - */ -export function deleteRuleGroup(data: Group) { - const { key, authProviderId, value, id } = data.props; - return axios.delete( - `${url}?authProviderId=${authProviderId}&key=${key}&value=${value}&id=${id}`, - {} - ); -} From e7c8a3ddf45d4521777efc36573ea18e1184d32e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:17:29 +0200 Subject: [PATCH 462/638] chore(deps): bump oras.land/oras-go/v2 from 2.6.0 to 2.6.2 in /operator/tools/operator-sdk (#22884) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/operator-sdk/go.mod | 2 +- operator/tools/operator-sdk/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index e68af3cf7313c..57a33d97aa1e4 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -239,7 +239,7 @@ require ( k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect k8s.io/kubectl v0.35.1 // indirect k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect - oras.land/oras-go/v2 v2.6.0 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/controller-runtime v0.23.1 // indirect sigs.k8s.io/controller-tools v0.20.0 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index 6a377f0ac4059..62851de86910d 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -780,8 +780,8 @@ k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= From 029556e02923042e8e961fd3d45fb589ee2d9a36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:18:02 +0200 Subject: [PATCH 463/638] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.42.0 to 1.45.0 in /operator/tools/operator-sdk (#22894) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/operator-sdk/go.mod | 10 +++++----- operator/tools/operator-sdk/go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index 57a33d97aa1e4..8f42d084d33bb 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -195,12 +195,12 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/otel/sdk v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.podman.io/common v0.66.1 // indirect go.podman.io/image/v5 v5.38.0 // indirect go.podman.io/storage v1.61.0 // indirect @@ -218,8 +218,8 @@ require ( golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index 62851de86910d..7e97323d86fd2 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -537,10 +537,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdK go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= @@ -563,8 +563,8 @@ go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJj go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.podman.io/common v0.66.1 h1:zDyd4HhVgQAN8LupBHCnhtM3FEOJ9DwmThjulXZq2qA= go.podman.io/common v0.66.1/go.mod h1:aNd2a0S7pY+fx1X5kpQYuF4hbwLU8ZOccuVrhu7h1Xc= go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4= @@ -713,10 +713,10 @@ 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= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -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/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= From 163842726244e315b68aa904aac92fc54ea57103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:50:30 +0200 Subject: [PATCH 464/638] chore(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.29.0 to 2.31.0 (#22209) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: StackRox PR Fixxxer --- .../v1/administration_events_service.pb.gw.go | 18 +- .../v1/administration_usage_service.pb.gw.go | 6 +- generated/api/v1/alert_service.pb.gw.go | 48 +++--- generated/api/v1/alert_service.swagger.json | 7 +- generated/api/v1/api_token_service.pb.gw.go | 18 +- .../api/v1/api_token_service.swagger.json | 3 +- generated/api/v1/auth_service.pb.gw.go | 18 +- .../api/v1/authprovider_service.pb.gw.go | 30 ++-- generated/api/v1/backup_service.pb.gw.go | 30 ++-- .../api/v1/cloud_source_service.pb.gw.go | 30 ++-- .../api/v1/cluster_init_service.swagger.json | 3 +- generated/api/v1/cluster_service.pb.gw.go | 24 +-- generated/api/v1/cluster_service.swagger.json | 2 +- .../v1/compliance_management_service.pb.gw.go | 12 +- ...compliance_management_service.swagger.json | 3 +- generated/api/v1/compliance_service.pb.gw.go | 24 +-- generated/api/v1/config_service.swagger.json | 3 +- .../api/v1/credential_expiry_service.pb.gw.go | 6 +- .../v1/credential_expiry_service.swagger.json | 3 +- generated/api/v1/cve_service.swagger.json | 6 +- generated/api/v1/db_service.pb.gw.go | 12 +- generated/api/v1/db_service.swagger.json | 2 +- generated/api/v1/debug_service.pb.gw.go | 6 +- generated/api/v1/debug_service.swagger.json | 3 +- ...gated_registry_config_service.swagger.json | 5 +- generated/api/v1/deployment_service.pb.gw.go | 42 ++--- .../api/v1/deployment_service.swagger.json | 9 +- .../api/v1/detection_service.swagger.json | 9 +- .../v1/discovered_cluster_service.pb.gw.go | 18 +- .../api/v1/feature_flag_service.swagger.json | 3 +- generated/api/v1/group_service.pb.gw.go | 24 +-- .../api/v1/image_integration_service.pb.gw.go | 30 ++-- .../v1/image_integration_service.swagger.json | 3 +- generated/api/v1/image_service.pb.gw.go | 36 ++-- generated/api/v1/image_service.swagger.json | 3 +- .../v1/integration_health_service.pb.gw.go | 6 +- generated/api/v1/metadata_service.pb.gw.go | 6 +- generated/api/v1/mitre_service.pb.gw.go | 6 +- generated/api/v1/mitre_service.swagger.json | 3 +- generated/api/v1/namespace_service.pb.gw.go | 12 +- .../api/v1/network_baseline_service.pb.gw.go | 36 ++-- .../v1/network_baseline_service.swagger.json | 4 +- .../api/v1/network_graph_service.pb.gw.go | 42 ++--- .../api/v1/network_graph_service.swagger.json | 2 +- .../api/v1/network_policy_service.pb.gw.go | 90 +++++----- .../v1/network_policy_service.swagger.json | 2 +- generated/api/v1/node_service.pb.gw.go | 18 +- generated/api/v1/node_service.swagger.json | 3 +- generated/api/v1/notifier_service.pb.gw.go | 24 +-- generated/api/v1/pod_service.pb.gw.go | 12 +- generated/api/v1/pod_service.swagger.json | 3 +- .../api/v1/policy_category_service.pb.gw.go | 18 +- .../v1/policy_category_service.swagger.json | 3 +- generated/api/v1/policy_service.pb.gw.go | 60 +++---- generated/api/v1/policy_service.swagger.json | 3 +- .../api/v1/probe_upload_service.pb.gw.go | 6 +- .../api/v1/process_baseline_service.pb.gw.go | 12 +- .../v1/process_baseline_service.swagger.json | 3 +- ...process_listening_on_port_service.pb.gw.go | 6 +- ...ess_listening_on_port_service.swagger.json | 3 +- generated/api/v1/process_service.pb.gw.go | 24 +-- generated/api/v1/process_service.swagger.json | 3 +- generated/api/v1/rbac_service.pb.gw.go | 36 ++-- .../v1/resource_collection_service.pb.gw.go | 30 ++-- generated/api/v1/role_service.pb.gw.go | 78 ++++----- generated/api/v1/search_service.pb.gw.go | 18 +- generated/api/v1/search_service.swagger.json | 2 +- generated/api/v1/secret_service.pb.gw.go | 24 +-- .../api/v1/sensor_upgrade_service.pb.gw.go | 12 +- .../v1/sensor_upgrade_service.swagger.json | 3 +- .../api/v1/service_account_service.pb.gw.go | 12 +- .../v1/signature_integration_service.pb.gw.go | 18 +- generated/api/v1/user_service.pb.gw.go | 6 +- generated/api/v1/vuln_mgmt_service.pb.gw.go | 6 +- .../api/v1/vuln_mgmt_service.swagger.json | 7 +- generated/api/v2/base_image_service.pb.gw.go | 12 +- .../api/v2/base_image_service.swagger.json | 3 +- .../compliance_integration_service.pb.gw.go | 6 +- ...ompliance_integration_service.swagger.json | 2 +- .../v2/compliance_profile_service.pb.gw.go | 18 +- .../v2/compliance_results_service.pb.gw.go | 42 ++--- .../compliance_results_stats_service.pb.gw.go | 42 ++--- .../api/v2/compliance_rule_service.pb.gw.go | 6 +- ...liance_scan_configuration_service.pb.gw.go | 60 +++---- generated/api/v2/node_report_service.pb.gw.go | 72 ++++---- .../api/v2/node_report_service.swagger.json | 6 +- generated/api/v2/report_service.pb.gw.go | 72 ++++---- generated/api/v2/report_service.swagger.json | 6 +- .../api/v2/virtual_machine_service.pb.gw.go | 12 +- .../v2/virtual_machine_v2_service.pb.gw.go | 60 +++---- .../virtual_machine_v2_service.swagger.json | 3 +- .../api/v2/vuln_exception_service.pb.gw.go | 42 ++--- .../v2/vuln_exception_service.swagger.json | 3 +- .../central/development_service.pb.gw.go | 12 +- .../central/v1/token_service.pb.gw.go | 6 +- go.mod | 67 ++++---- go.sum | 154 +++++++++--------- 97 files changed, 946 insertions(+), 921 deletions(-) diff --git a/generated/api/v1/administration_events_service.pb.gw.go b/generated/api/v1/administration_events_service.pb.gw.go index 57855747ef4fb..fabc7317f3e53 100644 --- a/generated/api/v1/administration_events_service.pb.gw.go +++ b/generated/api/v1/administration_events_service.pb.gw.go @@ -42,15 +42,15 @@ func request_AdministrationEventService_CountAdministrationEvents_0(ctx context. protoReq CountAdministrationEventsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdministrationEventService_CountAdministrationEvents_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountAdministrationEvents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_AdministrationEventService_GetAdministrationEvent_0(ctx context.Con metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -87,6 +84,9 @@ func request_AdministrationEventService_GetAdministrationEvent_0(ctx context.Con if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAdministrationEvent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_AdministrationEventService_ListAdministrationEvents_0(ctx context.C protoReq ListAdministrationEventsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdministrationEventService_ListAdministrationEvents_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListAdministrationEvents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/administration_usage_service.pb.gw.go b/generated/api/v1/administration_usage_service.pb.gw.go index 0889ec45c387c..b9dec12d1b966 100644 --- a/generated/api/v1/administration_usage_service.pb.gw.go +++ b/generated/api/v1/administration_usage_service.pb.gw.go @@ -63,15 +63,15 @@ func request_AdministrationUsageService_GetMaxSecuredUnitsUsage_0(ctx context.Co protoReq TimeRange metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdministrationUsageService_GetMaxSecuredUnitsUsage_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetMaxSecuredUnitsUsage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/alert_service.pb.gw.go b/generated/api/v1/alert_service.pb.gw.go index 07c4e44e552ba..1f46e3201bf5a 100644 --- a/generated/api/v1/alert_service.pb.gw.go +++ b/generated/api/v1/alert_service.pb.gw.go @@ -41,9 +41,6 @@ func request_AlertService_GetAlert_0(ctx context.Context, marshaler runtime.Mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_AlertService_GetAlert_0(ctx context.Context, marshaler runtime.Mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAlert(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_AlertService_CountAlerts_0(ctx context.Context, marshaler runtime.M protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_CountAlerts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountAlerts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_AlertService_ListAlerts_0(ctx context.Context, marshaler runtime.Ma protoReq ListAlertsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_ListAlerts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListAlerts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -151,15 +151,15 @@ func request_AlertService_GetAlertsGroup_0(ctx context.Context, marshaler runtim protoReq ListAlertsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_GetAlertsGroup_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAlertsGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -186,15 +186,15 @@ func request_AlertService_GetAlertsCounts_0(ctx context.Context, marshaler runti protoReq GetAlertsCountsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_GetAlertsCounts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAlertsCounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -221,15 +221,15 @@ func request_AlertService_GetAlertTimeseries_0(ctx context.Context, marshaler ru protoReq ListAlertsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_GetAlertTimeseries_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAlertTimeseries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -258,9 +258,6 @@ func request_AlertService_ResolveAlert_0(ctx context.Context, marshaler runtime. if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -269,6 +266,9 @@ func request_AlertService_ResolveAlert_0(ctx context.Context, marshaler runtime. if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ResolveAlert(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -328,15 +328,15 @@ func request_AlertService_DeleteAlerts_0(ctx context.Context, marshaler runtime. protoReq DeleteAlertsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AlertService_DeleteAlerts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteAlerts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/alert_service.swagger.json b/generated/api/v1/alert_service.swagger.json index 2134c2e45324b..307efecd9bdcd 100644 --- a/generated/api/v1/alert_service.swagger.json +++ b/generated/api/v1/alert_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "AlertService" + "name": "AlertService", + "description": "AlertService APIs can be used to manage alerts." } ], "consumes": [ @@ -1518,7 +1519,7 @@ "NODE" ], "default": "DEPLOYMENT", - "title": "A special ListAlert-only enumeration of all resource types. Unlike Alert.Resource.ResourceType this also includes deployment and node as types\nThis must be kept in sync with Alert.Resource.ResourceType (excluding the deployment and node values)" + "description": "A special ListAlert-only enumeration of all resource types. Unlike Alert.Resource.ResourceType this also includes deployment and node as types\nThis must be kept in sync with Alert.Resource.ResourceType (excluding the deployment and node values)" }, "storageNetworkEntityInfoType": { "type": "string", @@ -1531,7 +1532,7 @@ "INTERNAL_ENTITIES" ], "default": "UNKNOWN_TYPE", - "title": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" + "description": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" }, "storagePolicy": { "type": "object", diff --git a/generated/api/v1/api_token_service.pb.gw.go b/generated/api/v1/api_token_service.pb.gw.go index ad7c42e56b2e6..dc9007767ffa3 100644 --- a/generated/api/v1/api_token_service.pb.gw.go +++ b/generated/api/v1/api_token_service.pb.gw.go @@ -41,9 +41,6 @@ func request_APITokenService_GetAPIToken_0(ctx context.Context, marshaler runtim metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_APITokenService_GetAPIToken_0(ctx context.Context, marshaler runtim if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAPIToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_APITokenService_GetAPITokens_0(ctx context.Context, marshaler runti protoReq GetAPITokensRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_APITokenService_GetAPITokens_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAPITokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -142,9 +142,6 @@ func request_APITokenService_RevokeToken_0(ctx context.Context, marshaler runtim metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -153,6 +150,9 @@ func request_APITokenService_RevokeToken_0(ctx context.Context, marshaler runtim if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.RevokeToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/api_token_service.swagger.json b/generated/api/v1/api_token_service.swagger.json index 96a411c1f51bb..35fe471045723 100644 --- a/generated/api/v1/api_token_service.swagger.json +++ b/generated/api/v1/api_token_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "APITokenService" + "name": "APITokenService", + "description": "APITokenService APIs can be used to manage API tokens." } ], "consumes": [ diff --git a/generated/api/v1/auth_service.pb.gw.go b/generated/api/v1/auth_service.pb.gw.go index e8c4600adf4ec..fea8409f5290c 100644 --- a/generated/api/v1/auth_service.pb.gw.go +++ b/generated/api/v1/auth_service.pb.gw.go @@ -83,9 +83,6 @@ func request_AuthService_GetAuthMachineToMachineConfig_0(ctx context.Context, ma metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -94,6 +91,9 @@ func request_AuthService_GetAuthMachineToMachineConfig_0(ctx context.Context, ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAuthMachineToMachineConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -152,9 +152,6 @@ func request_AuthService_UpdateAuthMachineToMachineConfig_0(ctx context.Context, if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["config.id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "config.id") @@ -163,6 +160,9 @@ func request_AuthService_UpdateAuthMachineToMachineConfig_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "config.id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateAuthMachineToMachineConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -194,9 +194,6 @@ func request_AuthService_DeleteAuthMachineToMachineConfig_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -205,6 +202,9 @@ func request_AuthService_DeleteAuthMachineToMachineConfig_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteAuthMachineToMachineConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/authprovider_service.pb.gw.go b/generated/api/v1/authprovider_service.pb.gw.go index 6f928439c544c..2de8e8e0edd2c 100644 --- a/generated/api/v1/authprovider_service.pb.gw.go +++ b/generated/api/v1/authprovider_service.pb.gw.go @@ -63,9 +63,6 @@ func request_AuthProviderService_GetAuthProvider_0(ctx context.Context, marshale metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -74,6 +71,9 @@ func request_AuthProviderService_GetAuthProvider_0(ctx context.Context, marshale if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAuthProvider(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -124,15 +124,15 @@ func request_AuthProviderService_GetAuthProviders_0(ctx context.Context, marshal protoReq GetAuthProvidersRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthProviderService_GetAuthProviders_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAuthProviders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -188,9 +188,6 @@ func request_AuthProviderService_UpdateAuthProvider_0(ctx context.Context, marsh if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -199,6 +196,9 @@ func request_AuthProviderService_UpdateAuthProvider_0(ctx context.Context, marsh if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateAuthProvider(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -233,9 +233,6 @@ func request_AuthProviderService_PutAuthProvider_0(ctx context.Context, marshale if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -244,6 +241,9 @@ func request_AuthProviderService_PutAuthProvider_0(ctx context.Context, marshale if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutAuthProvider(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -277,9 +277,6 @@ func request_AuthProviderService_DeleteAuthProvider_0(ctx context.Context, marsh metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -294,6 +291,9 @@ func request_AuthProviderService_DeleteAuthProvider_0(ctx context.Context, marsh if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthProviderService_DeleteAuthProvider_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteAuthProvider(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/backup_service.pb.gw.go b/generated/api/v1/backup_service.pb.gw.go index 114272eb9be8f..e2d80b0f41032 100644 --- a/generated/api/v1/backup_service.pb.gw.go +++ b/generated/api/v1/backup_service.pb.gw.go @@ -42,9 +42,6 @@ func request_ExternalBackupService_GetExternalBackup_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -53,6 +50,9 @@ func request_ExternalBackupService_GetExternalBackup_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetExternalBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -132,9 +132,6 @@ func request_ExternalBackupService_PutExternalBackup_0(ctx context.Context, mars if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -143,6 +140,9 @@ func request_ExternalBackupService_PutExternalBackup_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutExternalBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -201,9 +201,6 @@ func request_ExternalBackupService_DeleteExternalBackup_0(ctx context.Context, m metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -212,6 +209,9 @@ func request_ExternalBackupService_DeleteExternalBackup_0(ctx context.Context, m if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteExternalBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -240,9 +240,6 @@ func request_ExternalBackupService_TriggerExternalBackup_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -251,6 +248,9 @@ func request_ExternalBackupService_TriggerExternalBackup_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.TriggerExternalBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -282,9 +282,6 @@ func request_ExternalBackupService_UpdateExternalBackup_0(ctx context.Context, m if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["external_backup.id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "external_backup.id") @@ -293,6 +290,9 @@ func request_ExternalBackupService_UpdateExternalBackup_0(ctx context.Context, m if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "external_backup.id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateExternalBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/cloud_source_service.pb.gw.go b/generated/api/v1/cloud_source_service.pb.gw.go index 510f5f44218c0..2334cf4b1faa6 100644 --- a/generated/api/v1/cloud_source_service.pb.gw.go +++ b/generated/api/v1/cloud_source_service.pb.gw.go @@ -42,15 +42,15 @@ func request_CloudSourcesService_CountCloudSources_0(ctx context.Context, marsha protoReq CountCloudSourcesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CloudSourcesService_CountCloudSources_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountCloudSources(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_CloudSourcesService_GetCloudSource_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -87,6 +84,9 @@ func request_CloudSourcesService_GetCloudSource_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetCloudSource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_CloudSourcesService_ListCloudSources_0(ctx context.Context, marshal protoReq ListCloudSourcesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CloudSourcesService_ListCloudSources_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListCloudSources(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -180,9 +180,6 @@ func request_CloudSourcesService_UpdateCloudSource_0(ctx context.Context, marsha if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cloud_source.id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cloud_source.id") @@ -191,6 +188,9 @@ func request_CloudSourcesService_UpdateCloudSource_0(ctx context.Context, marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cloud_source.id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateCloudSource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -222,9 +222,6 @@ func request_CloudSourcesService_DeleteCloudSource_0(ctx context.Context, marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -233,6 +230,9 @@ func request_CloudSourcesService_DeleteCloudSource_0(ctx context.Context, marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteCloudSource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/cluster_init_service.swagger.json b/generated/api/v1/cluster_init_service.swagger.json index 9e4773f0410d0..ec48899b0e1c2 100644 --- a/generated/api/v1/cluster_init_service.swagger.json +++ b/generated/api/v1/cluster_init_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ClusterInitService" + "name": "ClusterInitService", + "description": "ClusterInitService manages cluster init bundles and CRSs." } ], "consumes": [ diff --git a/generated/api/v1/cluster_service.pb.gw.go b/generated/api/v1/cluster_service.pb.gw.go index d0ed73dd50c9c..55f9e25111dc7 100644 --- a/generated/api/v1/cluster_service.pb.gw.go +++ b/generated/api/v1/cluster_service.pb.gw.go @@ -43,15 +43,15 @@ func request_ClustersService_GetClusters_0(ctx context.Context, marshaler runtim protoReq GetClustersRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ClustersService_GetClusters_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetClusters(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -77,9 +77,6 @@ func request_ClustersService_GetCluster_0(ctx context.Context, marshaler runtime metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -88,6 +85,9 @@ func request_ClustersService_GetCluster_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetCluster(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -146,9 +146,6 @@ func request_ClustersService_PutCluster_0(ctx context.Context, marshaler runtime if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -157,6 +154,9 @@ func request_ClustersService_PutCluster_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutCluster(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -188,9 +188,6 @@ func request_ClustersService_DeleteCluster_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -199,6 +196,9 @@ func request_ClustersService_DeleteCluster_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteCluster(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/cluster_service.swagger.json b/generated/api/v1/cluster_service.swagger.json index e7cb5f337dafc..9401492401ef4 100644 --- a/generated/api/v1/cluster_service.swagger.json +++ b/generated/api/v1/cluster_service.swagger.json @@ -229,7 +229,7 @@ "HEALTHY" ], "default": "UNINITIALIZED", - "title": "- UNAVAILABLE: Only collector can have unavailable status" + "description": "- UNAVAILABLE: Only collector can have unavailable status" }, "ClusterUpgradeStatusUpgradability": { "type": "string", diff --git a/generated/api/v1/compliance_management_service.pb.gw.go b/generated/api/v1/compliance_management_service.pb.gw.go index d07c28da3c7c6..85beb2bcc2ed7 100644 --- a/generated/api/v1/compliance_management_service.pb.gw.go +++ b/generated/api/v1/compliance_management_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ComplianceManagementService_GetRecentRuns_0(ctx context.Context, ma protoReq GetRecentComplianceRunsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceManagementService_GetRecentRuns_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRecentRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -104,15 +104,15 @@ func request_ComplianceManagementService_GetRunStatuses_0(ctx context.Context, m protoReq GetComplianceRunStatusesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceManagementService_GetRunStatuses_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRunStatuses(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/compliance_management_service.swagger.json b/generated/api/v1/compliance_management_service.swagger.json index e243bbe6ff9f5..6896bba6a615b 100644 --- a/generated/api/v1/compliance_management_service.swagger.json +++ b/generated/api/v1/compliance_management_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ComplianceManagementService" + "name": "ComplianceManagementService", + "description": "BEGIN EXPERIMENTAL/UNSTABLE API" } ], "consumes": [ diff --git a/generated/api/v1/compliance_service.pb.gw.go b/generated/api/v1/compliance_service.pb.gw.go index 519f5634ac267..a4f8692fd62f2 100644 --- a/generated/api/v1/compliance_service.pb.gw.go +++ b/generated/api/v1/compliance_service.pb.gw.go @@ -62,9 +62,6 @@ func request_ComplianceService_GetStandard_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -73,6 +70,9 @@ func request_ComplianceService_GetStandard_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetStandard(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -102,15 +102,15 @@ func request_ComplianceService_GetRunResults_0(ctx context.Context, marshaler ru protoReq GetComplianceRunResultsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceService_GetRunResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRunResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -137,15 +137,15 @@ func request_ComplianceService_GetAggregatedResults_0(ctx context.Context, marsh protoReq ComplianceAggregationRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceService_GetAggregatedResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAggregatedResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -174,9 +174,6 @@ func request_ComplianceService_UpdateComplianceStandardConfig_0(ctx context.Cont if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -185,6 +182,9 @@ func request_ComplianceService_UpdateComplianceStandardConfig_0(ctx context.Cont if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateComplianceStandardConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/config_service.swagger.json b/generated/api/v1/config_service.swagger.json index ceb25909aa01f..0cf414400003e 100644 --- a/generated/api/v1/config_service.swagger.json +++ b/generated/api/v1/config_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ConfigService" + "name": "ConfigService", + "description": "ConfigService APIs can be used to manage Central config." } ], "consumes": [ diff --git a/generated/api/v1/credential_expiry_service.pb.gw.go b/generated/api/v1/credential_expiry_service.pb.gw.go index 6429afb93f087..30b40f12b9633 100644 --- a/generated/api/v1/credential_expiry_service.pb.gw.go +++ b/generated/api/v1/credential_expiry_service.pb.gw.go @@ -42,15 +42,15 @@ func request_CredentialExpiryService_GetCertExpiry_0(ctx context.Context, marsha protoReq GetCertExpiry_Request metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialExpiryService_GetCertExpiry_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetCertExpiry(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/credential_expiry_service.swagger.json b/generated/api/v1/credential_expiry_service.swagger.json index f5c32c154bd68..d553c06514444 100644 --- a/generated/api/v1/credential_expiry_service.swagger.json +++ b/generated/api/v1/credential_expiry_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "CredentialExpiryService" + "name": "CredentialExpiryService", + "description": "CredentialExpiryService APIs can be used to retrieve information related to expiry\nof credentials of various StackRox services." } ], "consumes": [ diff --git a/generated/api/v1/cve_service.swagger.json b/generated/api/v1/cve_service.swagger.json index 53503c19cd0a3..b94b6ccfe68aa 100644 --- a/generated/api/v1/cve_service.swagger.json +++ b/generated/api/v1/cve_service.swagger.json @@ -6,10 +6,12 @@ }, "tags": [ { - "name": "NodeCVEService" + "name": "NodeCVEService", + "description": "NodeCVEService APIs can be used to manage node cves." }, { - "name": "ClusterCVEService" + "name": "ClusterCVEService", + "description": "ClusterCVEService APIs can be used to manage cluster cves." } ], "consumes": [ diff --git a/generated/api/v1/db_service.pb.gw.go b/generated/api/v1/db_service.pb.gw.go index 0979bd8be0048..7e864977dc57b 100644 --- a/generated/api/v1/db_service.pb.gw.go +++ b/generated/api/v1/db_service.pb.gw.go @@ -83,9 +83,6 @@ func request_DBService_InterruptRestoreProcess_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["process_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "process_id") @@ -102,6 +99,9 @@ func request_DBService_InterruptRestoreProcess_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "attempt_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.InterruptRestoreProcess(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -138,9 +138,6 @@ func request_DBService_CancelRestoreProcess_0(ctx context.Context, marshaler run metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -149,6 +146,9 @@ func request_DBService_CancelRestoreProcess_0(ctx context.Context, marshaler run if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CancelRestoreProcess(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/db_service.swagger.json b/generated/api/v1/db_service.swagger.json index eff15bc22e485..792ea0368bf80 100644 --- a/generated/api/v1/db_service.swagger.json +++ b/generated/api/v1/db_service.swagger.json @@ -323,7 +323,7 @@ "COMPLETED" ], "default": "UNKNOWN", - "title": "- COMPLETED: successful if error is empty, unsuccessful otherwise" + "description": "- COMPLETED: successful if error is empty, unsuccessful otherwise" }, "v1DBRestoreRequestHeader": { "type": "object", diff --git a/generated/api/v1/debug_service.pb.gw.go b/generated/api/v1/debug_service.pb.gw.go index f12ab6475098f..8dab0495d0993 100644 --- a/generated/api/v1/debug_service.pb.gw.go +++ b/generated/api/v1/debug_service.pb.gw.go @@ -42,15 +42,15 @@ func request_DebugService_GetLogLevel_0(ctx context.Context, marshaler runtime.M protoReq GetLogLevelRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DebugService_GetLogLevel_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetLogLevel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/debug_service.swagger.json b/generated/api/v1/debug_service.swagger.json index 3749e09c0718f..8a986517ce8cc 100644 --- a/generated/api/v1/debug_service.swagger.json +++ b/generated/api/v1/debug_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "DebugService" + "name": "DebugService", + "description": "APIs for debugging." } ], "consumes": [ diff --git a/generated/api/v1/delegated_registry_config_service.swagger.json b/generated/api/v1/delegated_registry_config_service.swagger.json index 00f59b70a2a22..bed355282cd08 100644 --- a/generated/api/v1/delegated_registry_config_service.swagger.json +++ b/generated/api/v1/delegated_registry_config_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "DelegatedRegistryConfigService" + "name": "DelegatedRegistryConfigService", + "description": "DelegatedRegistryConfigService APIs can be used to control how to route image scans amongst\ncentral and secured clusters" } ], "consumes": [ @@ -117,7 +118,7 @@ "SPECIFIC" ], "default": "NONE", - "title": "- NONE: Scan all images via central services except for images from the OCP integrated registry\n - ALL: Scan all images via the secured clusters\n - SPECIFIC: Scan images that match `registries` or are from the OCP integrated registry via the secured clusters\notherwise scan via central services" + "description": "- NONE: Scan all images via central services except for images from the OCP integrated registry\n - ALL: Scan all images via the secured clusters\n - SPECIFIC: Scan images that match `registries` or are from the OCP integrated registry via the secured clusters\notherwise scan via central services" }, "googleRpcStatus": { "type": "object", diff --git a/generated/api/v1/deployment_service.pb.gw.go b/generated/api/v1/deployment_service.pb.gw.go index 004e1ad0b7d70..ae0d3da2eab9a 100644 --- a/generated/api/v1/deployment_service.pb.gw.go +++ b/generated/api/v1/deployment_service.pb.gw.go @@ -41,9 +41,6 @@ func request_DeploymentService_GetDeployment_0(ctx context.Context, marshaler ru metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_DeploymentService_GetDeployment_0(ctx context.Context, marshaler ru if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -80,9 +80,6 @@ func request_DeploymentService_GetDeploymentWithRisk_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -91,6 +88,9 @@ func request_DeploymentService_GetDeploymentWithRisk_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDeploymentWithRisk(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -119,9 +119,6 @@ func request_DeploymentService_GetDeploymentRiskAISummary_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -130,6 +127,9 @@ func request_DeploymentService_GetDeploymentRiskAISummary_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDeploymentRiskAISummary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -159,15 +159,15 @@ func request_DeploymentService_CountDeployments_0(ctx context.Context, marshaler protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DeploymentService_CountDeployments_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountDeployments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -194,15 +194,15 @@ func request_DeploymentService_ListDeployments_0(ctx context.Context, marshaler protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DeploymentService_ListDeployments_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListDeployments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -229,15 +229,15 @@ func request_DeploymentService_ListDeploymentsWithProcessInfo_0(ctx context.Cont protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DeploymentService_ListDeploymentsWithProcessInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListDeploymentsWithProcessInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -285,15 +285,15 @@ func request_DeploymentService_ExportDeployments_0(ctx context.Context, marshale protoReq ExportDeploymentRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DeploymentService_ExportDeployments_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } stream, err := client.ExportDeployments(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/generated/api/v1/deployment_service.swagger.json b/generated/api/v1/deployment_service.swagger.json index f5e9523e7e413..afa5445cc0098 100644 --- a/generated/api/v1/deployment_service.swagger.json +++ b/generated/api/v1/deployment_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "DeploymentService" + "name": "DeploymentService", + "description": "DeploymentService APIs can be used to manage deployments." } ], "consumes": [ @@ -456,7 +457,7 @@ "UNKNOWN" ], "default": "UNSET", - "title": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" + "description": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" }, "LabelSelectorRequirement": { "type": "object", @@ -1027,7 +1028,7 @@ "CLUSTER_ADMIN" ], "default": "UNSET", - "title": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" + "description": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" }, "storagePortConfig": { "type": "object", @@ -1141,7 +1142,7 @@ "SERVICEACCOUNT" ], "default": "UNKNOWN", - "title": "Next tag: 9" + "description": "Next tag: 9" }, "storageSecurityContext": { "type": "object", diff --git a/generated/api/v1/detection_service.swagger.json b/generated/api/v1/detection_service.swagger.json index 4bfb593f41f83..8be964b719432 100644 --- a/generated/api/v1/detection_service.swagger.json +++ b/generated/api/v1/detection_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "DetectionService" + "name": "DetectionService", + "description": "DetectionService APIs can be used to check for build and deploy time policy violations." } ], "consumes": [ @@ -279,7 +280,7 @@ "UNKNOWN" ], "default": "UNSET", - "title": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" + "description": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" }, "ExclusionImage": { "type": "object", @@ -1250,7 +1251,7 @@ "INTERNAL_ENTITIES" ], "default": "UNKNOWN_TYPE", - "title": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" + "description": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" }, "storagePermissionLevel": { "type": "string", @@ -1263,7 +1264,7 @@ "CLUSTER_ADMIN" ], "default": "UNSET", - "title": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" + "description": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" }, "storagePolicy": { "type": "object", diff --git a/generated/api/v1/discovered_cluster_service.pb.gw.go b/generated/api/v1/discovered_cluster_service.pb.gw.go index 10892f9ceb505..db873c116ff01 100644 --- a/generated/api/v1/discovered_cluster_service.pb.gw.go +++ b/generated/api/v1/discovered_cluster_service.pb.gw.go @@ -42,15 +42,15 @@ func request_DiscoveredClustersService_CountDiscoveredClusters_0(ctx context.Con protoReq CountDiscoveredClustersRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DiscoveredClustersService_CountDiscoveredClusters_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountDiscoveredClusters(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_DiscoveredClustersService_GetDiscoveredCluster_0(ctx context.Contex metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -87,6 +84,9 @@ func request_DiscoveredClustersService_GetDiscoveredCluster_0(ctx context.Contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDiscoveredCluster(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_DiscoveredClustersService_ListDiscoveredClusters_0(ctx context.Cont protoReq ListDiscoveredClustersRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DiscoveredClustersService_ListDiscoveredClusters_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListDiscoveredClusters(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/feature_flag_service.swagger.json b/generated/api/v1/feature_flag_service.swagger.json index 795e4e2532902..c7896b60c6319 100644 --- a/generated/api/v1/feature_flag_service.swagger.json +++ b/generated/api/v1/feature_flag_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "FeatureFlagService" + "name": "FeatureFlagService", + "description": "FeatureFlagService retrieves information about feature flags." } ], "consumes": [ diff --git a/generated/api/v1/group_service.pb.gw.go b/generated/api/v1/group_service.pb.gw.go index e1d807e755e26..4b73b238ea96b 100644 --- a/generated/api/v1/group_service.pb.gw.go +++ b/generated/api/v1/group_service.pb.gw.go @@ -43,15 +43,15 @@ func request_GroupService_GetGroups_0(ctx context.Context, marshaler runtime.Mar protoReq GetGroupsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_GetGroups_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetGroups(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -78,15 +78,15 @@ func request_GroupService_GetGroup_0(ctx context.Context, marshaler runtime.Mars protoReq storage.GroupProperties metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_GetGroup_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -170,15 +170,15 @@ func request_GroupService_UpdateGroup_0(ctx context.Context, marshaler runtime.M if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Group); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_UpdateGroup_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -208,15 +208,15 @@ func request_GroupService_DeleteGroup_0(ctx context.Context, marshaler runtime.M protoReq DeleteGroupRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_DeleteGroup_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/image_integration_service.pb.gw.go b/generated/api/v1/image_integration_service.pb.gw.go index 2d5fab0235a4b..1be46ef0ea676 100644 --- a/generated/api/v1/image_integration_service.pb.gw.go +++ b/generated/api/v1/image_integration_service.pb.gw.go @@ -42,9 +42,6 @@ func request_ImageIntegrationService_GetImageIntegration_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -53,6 +50,9 @@ func request_ImageIntegrationService_GetImageIntegration_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetImageIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -82,15 +82,15 @@ func request_ImageIntegrationService_GetImageIntegrations_0(ctx context.Context, protoReq GetImageIntegrationsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageIntegrationService_GetImageIntegrations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetImageIntegrations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -146,9 +146,6 @@ func request_ImageIntegrationService_PutImageIntegration_0(ctx context.Context, if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -157,6 +154,9 @@ func request_ImageIntegrationService_PutImageIntegration_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutImageIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -215,9 +215,6 @@ func request_ImageIntegrationService_DeleteImageIntegration_0(ctx context.Contex metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -226,6 +223,9 @@ func request_ImageIntegrationService_DeleteImageIntegration_0(ctx context.Contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteImageIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -257,9 +257,6 @@ func request_ImageIntegrationService_UpdateImageIntegration_0(ctx context.Contex if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["config.id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "config.id") @@ -268,6 +265,9 @@ func request_ImageIntegrationService_UpdateImageIntegration_0(ctx context.Contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "config.id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateImageIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/image_integration_service.swagger.json b/generated/api/v1/image_integration_service.swagger.json index 1385bd1518eb2..9006e3b4e1b25 100644 --- a/generated/api/v1/image_integration_service.swagger.json +++ b/generated/api/v1/image_integration_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ImageIntegrationService" + "name": "ImageIntegrationService", + "description": "ImageIntegrationService APIs manage image registry and image scanner integration." } ], "consumes": [ diff --git a/generated/api/v1/image_service.pb.gw.go b/generated/api/v1/image_service.pb.gw.go index b90487a79c248..bd047a05a4f2a 100644 --- a/generated/api/v1/image_service.pb.gw.go +++ b/generated/api/v1/image_service.pb.gw.go @@ -43,9 +43,6 @@ func request_ImageService_GetImage_0(ctx context.Context, marshaler runtime.Mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -60,6 +57,9 @@ func request_ImageService_GetImage_0(ctx context.Context, marshaler runtime.Mars if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_GetImage_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetImage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -95,15 +95,15 @@ func request_ImageService_CountImages_0(ctx context.Context, marshaler runtime.M protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_CountImages_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountImages(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -130,15 +130,15 @@ func request_ImageService_ListImages_0(ctx context.Context, marshaler runtime.Ma protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_ListImages_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListImages(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -213,15 +213,15 @@ func request_ImageService_DeleteImages_0(ctx context.Context, marshaler runtime. protoReq DeleteImagesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_DeleteImages_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteImages(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -275,15 +275,15 @@ func request_ImageService_UnwatchImage_0(ctx context.Context, marshaler runtime. protoReq UnwatchImageRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_UnwatchImage_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UnwatchImage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -331,15 +331,15 @@ func request_ImageService_ExportImages_0(ctx context.Context, marshaler runtime. protoReq ExportImageRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ImageService_ExportImages_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } stream, err := client.ExportImages(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/generated/api/v1/image_service.swagger.json b/generated/api/v1/image_service.swagger.json index 51036b8b504f4..013a93f5f0b2b 100644 --- a/generated/api/v1/image_service.swagger.json +++ b/generated/api/v1/image_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ImageService" + "name": "ImageService", + "description": "ImageService APIs manages image metadata." } ], "consumes": [ diff --git a/generated/api/v1/integration_health_service.pb.gw.go b/generated/api/v1/integration_health_service.pb.gw.go index 3e0b4c0bf7568..14e6b575d6a80 100644 --- a/generated/api/v1/integration_health_service.pb.gw.go +++ b/generated/api/v1/integration_health_service.pb.gw.go @@ -126,15 +126,15 @@ func request_IntegrationHealthService_GetVulnDefinitionsInfo_0(ctx context.Conte protoReq VulnDefinitionsInfoRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_IntegrationHealthService_GetVulnDefinitionsInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVulnDefinitionsInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/metadata_service.pb.gw.go b/generated/api/v1/metadata_service.pb.gw.go index 59807b91a4e92..30f10d2b48a76 100644 --- a/generated/api/v1/metadata_service.pb.gw.go +++ b/generated/api/v1/metadata_service.pb.gw.go @@ -63,15 +63,15 @@ func request_MetadataService_TLSChallenge_0(ctx context.Context, marshaler runti protoReq TLSChallengeRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MetadataService_TLSChallenge_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.TLSChallenge(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/mitre_service.pb.gw.go b/generated/api/v1/mitre_service.pb.gw.go index 20d08c6b0f6f3..b28db6432449f 100644 --- a/generated/api/v1/mitre_service.pb.gw.go +++ b/generated/api/v1/mitre_service.pb.gw.go @@ -62,9 +62,6 @@ func request_MitreAttackService_GetMitreAttackVector_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -73,6 +70,9 @@ func request_MitreAttackService_GetMitreAttackVector_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetMitreAttackVector(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/mitre_service.swagger.json b/generated/api/v1/mitre_service.swagger.json index 79ccd8044ec9b..cee9fb88f3394 100644 --- a/generated/api/v1/mitre_service.swagger.json +++ b/generated/api/v1/mitre_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "MitreAttackService" + "name": "MitreAttackService", + "description": "MitreAttackService can be used to retrieve information about MITRE ATT&CK matrix." } ], "consumes": [ diff --git a/generated/api/v1/namespace_service.pb.gw.go b/generated/api/v1/namespace_service.pb.gw.go index 0234b1f9b1610..83f6246c36824 100644 --- a/generated/api/v1/namespace_service.pb.gw.go +++ b/generated/api/v1/namespace_service.pb.gw.go @@ -42,15 +42,15 @@ func request_NamespaceService_GetNamespaces_0(ctx context.Context, marshaler run protoReq GetNamespaceRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NamespaceService_GetNamespaces_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNamespaces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_NamespaceService_GetNamespace_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -87,6 +84,9 @@ func request_NamespaceService_GetNamespace_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNamespace(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/network_baseline_service.pb.gw.go b/generated/api/v1/network_baseline_service.pb.gw.go index b8351ab14126a..a5b1194bc3030 100644 --- a/generated/api/v1/network_baseline_service.pb.gw.go +++ b/generated/api/v1/network_baseline_service.pb.gw.go @@ -44,9 +44,6 @@ func request_NetworkBaselineService_GetNetworkBaselineStatusForFlows_0(ctx conte if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -55,6 +52,9 @@ func request_NetworkBaselineService_GetNetworkBaselineStatusForFlows_0(ctx conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkBaselineStatusForFlows(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -88,9 +88,6 @@ func request_NetworkBaselineService_GetNetworkBaselineStatusForExternalFlows_0(c metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -105,6 +102,9 @@ func request_NetworkBaselineService_GetNetworkBaselineStatusForExternalFlows_0(c if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkBaselineService_GetNetworkBaselineStatusForExternalFlows_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkBaselineStatusForExternalFlows(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -139,9 +139,6 @@ func request_NetworkBaselineService_GetNetworkBaseline_0(ctx context.Context, ma metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -150,6 +147,9 @@ func request_NetworkBaselineService_GetNetworkBaseline_0(ctx context.Context, ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkBaseline(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -181,9 +181,6 @@ func request_NetworkBaselineService_ModifyBaselineStatusForPeers_0(ctx context.C if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -192,6 +189,9 @@ func request_NetworkBaselineService_ModifyBaselineStatusForPeers_0(ctx context.C if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ModifyBaselineStatusForPeers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -226,9 +226,6 @@ func request_NetworkBaselineService_LockNetworkBaseline_0(ctx context.Context, m if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -237,6 +234,9 @@ func request_NetworkBaselineService_LockNetworkBaseline_0(ctx context.Context, m if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.LockNetworkBaseline(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -271,9 +271,6 @@ func request_NetworkBaselineService_UnlockNetworkBaseline_0(ctx context.Context, if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -282,6 +279,9 @@ func request_NetworkBaselineService_UnlockNetworkBaseline_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UnlockNetworkBaseline(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/network_baseline_service.swagger.json b/generated/api/v1/network_baseline_service.swagger.json index 38d7a0bb1d716..0878afb1c71f5 100644 --- a/generated/api/v1/network_baseline_service.swagger.json +++ b/generated/api/v1/network_baseline_service.swagger.json @@ -531,7 +531,7 @@ "INTERNAL_ENTITIES" ], "default": "UNKNOWN_TYPE", - "title": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" + "description": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" }, "storageNetworkEntityScope": { "type": "object", @@ -627,7 +627,7 @@ "ANOMALOUS" ], "default": "BASELINE", - "title": "Status of this peer connection. As of now we only have two statuses:\n - BASELINE: the connection is in the current deployment baseline\n - ANOMALOUS: the connection is not recognized by the current deployment baseline" + "description": "Status of this peer connection. As of now we only have two statuses:\n - BASELINE: the connection is in the current deployment baseline\n - ANOMALOUS: the connection is not recognized by the current deployment baseline" }, "v1NetworkBaselineStatusPeer": { "type": "object", diff --git a/generated/api/v1/network_graph_service.pb.gw.go b/generated/api/v1/network_graph_service.pb.gw.go index f4854fbfcd6c8..7fbc8d1069b0d 100644 --- a/generated/api/v1/network_graph_service.pb.gw.go +++ b/generated/api/v1/network_graph_service.pb.gw.go @@ -43,9 +43,6 @@ func request_NetworkGraphService_GetNetworkGraph_0(ctx context.Context, marshale metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -60,6 +57,9 @@ func request_NetworkGraphService_GetNetworkGraph_0(ctx context.Context, marshale if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkGraphService_GetNetworkGraph_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -96,9 +96,6 @@ func request_NetworkGraphService_GetExternalNetworkEntities_0(ctx context.Contex metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -113,6 +110,9 @@ func request_NetworkGraphService_GetExternalNetworkEntities_0(ctx context.Contex if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkGraphService_GetExternalNetworkEntities_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetExternalNetworkEntities(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -149,9 +149,6 @@ func request_NetworkGraphService_GetExternalNetworkFlows_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -174,6 +171,9 @@ func request_NetworkGraphService_GetExternalNetworkFlows_0(ctx context.Context, if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkGraphService_GetExternalNetworkFlows_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetExternalNetworkFlows(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -218,9 +218,6 @@ func request_NetworkGraphService_GetExternalNetworkFlowsMetadata_0(ctx context.C metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -235,6 +232,9 @@ func request_NetworkGraphService_GetExternalNetworkFlowsMetadata_0(ctx context.C if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkGraphService_GetExternalNetworkFlowsMetadata_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetExternalNetworkFlowsMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -272,9 +272,6 @@ func request_NetworkGraphService_CreateExternalNetworkEntity_0(ctx context.Conte if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -283,6 +280,9 @@ func request_NetworkGraphService_CreateExternalNetworkEntity_0(ctx context.Conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cluster_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CreateExternalNetworkEntity(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -317,9 +317,6 @@ func request_NetworkGraphService_PatchExternalNetworkEntity_0(ctx context.Contex if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -328,6 +325,9 @@ func request_NetworkGraphService_PatchExternalNetworkEntity_0(ctx context.Contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PatchExternalNetworkEntity(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -359,9 +359,6 @@ func request_NetworkGraphService_DeleteExternalNetworkEntity_0(ctx context.Conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -370,6 +367,9 @@ func request_NetworkGraphService_DeleteExternalNetworkEntity_0(ctx context.Conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteExternalNetworkEntity(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/network_graph_service.swagger.json b/generated/api/v1/network_graph_service.swagger.json index 841f6bd97449b..88756b1f3b6f9 100644 --- a/generated/api/v1/network_graph_service.swagger.json +++ b/generated/api/v1/network_graph_service.swagger.json @@ -603,7 +603,7 @@ "INTERNAL_ENTITIES" ], "default": "UNKNOWN_TYPE", - "title": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" + "description": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" }, "storageNetworkEntityScope": { "type": "object", diff --git a/generated/api/v1/network_policy_service.pb.gw.go b/generated/api/v1/network_policy_service.pb.gw.go index c472d6ec1006f..5575bdd5119b1 100644 --- a/generated/api/v1/network_policy_service.pb.gw.go +++ b/generated/api/v1/network_policy_service.pb.gw.go @@ -41,9 +41,6 @@ func request_NetworkPolicyService_GetNetworkPolicy_0(ctx context.Context, marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_NetworkPolicyService_GetNetworkPolicy_0(ctx context.Context, marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_NetworkPolicyService_GetNetworkPolicies_0(ctx context.Context, mars protoReq GetNetworkPoliciesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_GetNetworkPolicies_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkPolicies(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -117,9 +117,6 @@ func request_NetworkPolicyService_GetNetworkGraph_0(ctx context.Context, marshal metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -134,6 +131,9 @@ func request_NetworkPolicyService_GetNetworkGraph_0(ctx context.Context, marshal if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_GetNetworkGraph_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -169,15 +169,15 @@ func request_NetworkPolicyService_GetNetworkGraphEpoch_0(ctx context.Context, ma protoReq GetNetworkGraphEpochRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_GetNetworkGraphEpoch_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNetworkGraphEpoch(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -206,9 +206,6 @@ func request_NetworkPolicyService_ApplyNetworkPolicy_0(ctx context.Context, mars if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Modification); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -217,6 +214,9 @@ func request_NetworkPolicyService_ApplyNetworkPolicy_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cluster_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ApplyNetworkPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -248,9 +248,6 @@ func request_NetworkPolicyService_GetUndoModification_0(ctx context.Context, mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -259,6 +256,9 @@ func request_NetworkPolicyService_GetUndoModification_0(ctx context.Context, mar if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cluster_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetUndoModification(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -292,9 +292,6 @@ func request_NetworkPolicyService_SimulateNetworkGraph_0(ctx context.Context, ma if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Modification); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -309,6 +306,9 @@ func request_NetworkPolicyService_SimulateNetworkGraph_0(ctx context.Context, ma if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_SimulateNetworkGraph_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.SimulateNetworkGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -351,9 +351,6 @@ func request_NetworkPolicyService_SendNetworkPolicyYAML_0(ctx context.Context, m if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Modification); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -368,6 +365,9 @@ func request_NetworkPolicyService_SendNetworkPolicyYAML_0(ctx context.Context, m if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_SendNetworkPolicyYAML_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.SendNetworkPolicyYAML(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -407,9 +407,6 @@ func request_NetworkPolicyService_GenerateNetworkPolicies_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -424,6 +421,9 @@ func request_NetworkPolicyService_GenerateNetworkPolicies_0(ctx context.Context, if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NetworkPolicyService_GenerateNetworkPolicies_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GenerateNetworkPolicies(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -461,9 +461,6 @@ func request_NetworkPolicyService_GetBaselineGeneratedNetworkPolicyForDeployment if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -472,6 +469,9 @@ func request_NetworkPolicyService_GetBaselineGeneratedNetworkPolicyForDeployment if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetBaselineGeneratedNetworkPolicyForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -503,9 +503,6 @@ func request_NetworkPolicyService_GetAllowedPeersFromCurrentPolicyForDeployment_ metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -514,6 +511,9 @@ func request_NetworkPolicyService_GetAllowedPeersFromCurrentPolicyForDeployment_ if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetAllowedPeersFromCurrentPolicyForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -545,9 +545,6 @@ func request_NetworkPolicyService_ApplyNetworkPolicyYamlForDeployment_0(ctx cont if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -556,6 +553,9 @@ func request_NetworkPolicyService_ApplyNetworkPolicyYamlForDeployment_0(ctx cont if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ApplyNetworkPolicyYamlForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -587,9 +587,6 @@ func request_NetworkPolicyService_GetUndoModificationForDeployment_0(ctx context metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -598,6 +595,9 @@ func request_NetworkPolicyService_GetUndoModificationForDeployment_0(ctx context if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetUndoModificationForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -626,9 +626,6 @@ func request_NetworkPolicyService_GetDiffFlowsBetweenPolicyAndBaselineForDeploym metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -637,6 +634,9 @@ func request_NetworkPolicyService_GetDiffFlowsBetweenPolicyAndBaselineForDeploym if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDiffFlowsBetweenPolicyAndBaselineForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -665,9 +665,6 @@ func request_NetworkPolicyService_GetDiffFlowsFromUndoModificationForDeployment_ metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -676,6 +673,9 @@ func request_NetworkPolicyService_GetDiffFlowsFromUndoModificationForDeployment_ if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetDiffFlowsFromUndoModificationForDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/network_policy_service.swagger.json b/generated/api/v1/network_policy_service.swagger.json index 6cae046bb1aa1..7b15ea6766ec8 100644 --- a/generated/api/v1/network_policy_service.swagger.json +++ b/generated/api/v1/network_policy_service.swagger.json @@ -843,7 +843,7 @@ "INTERNAL_ENTITIES" ], "default": "UNKNOWN_TYPE", - "title": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" + "description": "- INTERNAL_ENTITIES: INTERNAL_ENTITIES is for grouping all internal entities under a single network graph node" }, "storageNetworkPolicy": { "type": "object", diff --git a/generated/api/v1/node_service.pb.gw.go b/generated/api/v1/node_service.pb.gw.go index 02ab873b2bdf9..59afbd7b8a3e8 100644 --- a/generated/api/v1/node_service.pb.gw.go +++ b/generated/api/v1/node_service.pb.gw.go @@ -41,9 +41,6 @@ func request_NodeService_ListNodes_0(ctx context.Context, marshaler runtime.Mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -52,6 +49,9 @@ func request_NodeService_ListNodes_0(ctx context.Context, marshaler runtime.Mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cluster_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListNodes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -80,9 +80,6 @@ func request_NodeService_GetNode_0(ctx context.Context, marshaler runtime.Marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -99,6 +96,9 @@ func request_NodeService_GetNode_0(ctx context.Context, marshaler runtime.Marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -136,15 +136,15 @@ func request_NodeService_ExportNodes_0(ctx context.Context, marshaler runtime.Ma protoReq ExportNodeRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeService_ExportNodes_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } stream, err := client.ExportNodes(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/generated/api/v1/node_service.swagger.json b/generated/api/v1/node_service.swagger.json index a50f3bc54ff87..6643b1fca6c29 100644 --- a/generated/api/v1/node_service.swagger.json +++ b/generated/api/v1/node_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "NodeService" + "name": "NodeService", + "description": "storage.Node represents information about a node in the cluster." } ], "consumes": [ diff --git a/generated/api/v1/notifier_service.pb.gw.go b/generated/api/v1/notifier_service.pb.gw.go index a6efe55dc4d07..efc7340a7a505 100644 --- a/generated/api/v1/notifier_service.pb.gw.go +++ b/generated/api/v1/notifier_service.pb.gw.go @@ -42,9 +42,6 @@ func request_NotifierService_GetNotifier_0(ctx context.Context, marshaler runtim metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -53,6 +50,9 @@ func request_NotifierService_GetNotifier_0(ctx context.Context, marshaler runtim if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNotifier(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -105,9 +105,6 @@ func request_NotifierService_PutNotifier_0(ctx context.Context, marshaler runtim if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -116,6 +113,9 @@ func request_NotifierService_PutNotifier_0(ctx context.Context, marshaler runtim if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutNotifier(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -203,9 +203,6 @@ func request_NotifierService_DeleteNotifier_0(ctx context.Context, marshaler run metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -220,6 +217,9 @@ func request_NotifierService_DeleteNotifier_0(ctx context.Context, marshaler run if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NotifierService_DeleteNotifier_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteNotifier(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -257,9 +257,6 @@ func request_NotifierService_UpdateNotifier_0(ctx context.Context, marshaler run if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["notifier.id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "notifier.id") @@ -268,6 +265,9 @@ func request_NotifierService_UpdateNotifier_0(ctx context.Context, marshaler run if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "notifier.id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateNotifier(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/pod_service.pb.gw.go b/generated/api/v1/pod_service.pb.gw.go index 39b12fc48f088..2ce4c45231940 100644 --- a/generated/api/v1/pod_service.pb.gw.go +++ b/generated/api/v1/pod_service.pb.gw.go @@ -42,15 +42,15 @@ func request_PodService_GetPods_0(ctx context.Context, marshaler runtime.Marshal protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PodService_GetPods_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPods(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -77,15 +77,15 @@ func request_PodService_ExportPods_0(ctx context.Context, marshaler runtime.Mars protoReq ExportPodRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PodService_ExportPods_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } stream, err := client.ExportPods(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/generated/api/v1/pod_service.swagger.json b/generated/api/v1/pod_service.swagger.json index cb2ff3dfc3dd6..9999e581a2497 100644 --- a/generated/api/v1/pod_service.swagger.json +++ b/generated/api/v1/pod_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "PodService" + "name": "PodService", + "description": "PodService APIs can be used to manage pods." } ], "consumes": [ diff --git a/generated/api/v1/policy_category_service.pb.gw.go b/generated/api/v1/policy_category_service.pb.gw.go index 86e759ec0dbdc..a5a74a2474bfb 100644 --- a/generated/api/v1/policy_category_service.pb.gw.go +++ b/generated/api/v1/policy_category_service.pb.gw.go @@ -41,9 +41,6 @@ func request_PolicyCategoryService_GetPolicyCategory_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_PolicyCategoryService_GetPolicyCategory_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPolicyCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_PolicyCategoryService_GetPolicyCategories_0(ctx context.Context, ma protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PolicyCategoryService_GetPolicyCategories_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPolicyCategories(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -169,9 +169,6 @@ func request_PolicyCategoryService_DeletePolicyCategory_0(ctx context.Context, m metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -180,6 +177,9 @@ func request_PolicyCategoryService_DeletePolicyCategory_0(ctx context.Context, m if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeletePolicyCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/policy_category_service.swagger.json b/generated/api/v1/policy_category_service.swagger.json index 7c631508e36af..6814e80eb5f00 100644 --- a/generated/api/v1/policy_category_service.swagger.json +++ b/generated/api/v1/policy_category_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "PolicyCategoryService" + "name": "PolicyCategoryService", + "description": "PolicyCategoryService APIs can be used to manage policy categories." } ], "consumes": [ diff --git a/generated/api/v1/policy_service.pb.gw.go b/generated/api/v1/policy_service.pb.gw.go index 2477addfe0469..0d08aab492beb 100644 --- a/generated/api/v1/policy_service.pb.gw.go +++ b/generated/api/v1/policy_service.pb.gw.go @@ -42,9 +42,6 @@ func request_PolicyService_GetPolicy_0(ctx context.Context, marshaler runtime.Ma metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -53,6 +50,9 @@ func request_PolicyService_GetPolicy_0(ctx context.Context, marshaler runtime.Ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -83,9 +83,6 @@ func request_PolicyService_GetPolicyMitreVectors_0(ctx context.Context, marshale metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -100,6 +97,9 @@ func request_PolicyService_GetPolicyMitreVectors_0(ctx context.Context, marshale if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PolicyService_GetPolicyMitreVectors_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPolicyMitreVectors(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -135,15 +135,15 @@ func request_PolicyService_ListPolicies_0(ctx context.Context, marshaler runtime protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PolicyService_ListPolicies_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListPolicies(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -173,15 +173,15 @@ func request_PolicyService_PostPolicy_0(ctx context.Context, marshaler runtime.M if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Policy); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_PolicyService_PostPolicy_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PostPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -213,9 +213,6 @@ func request_PolicyService_PutPolicy_0(ctx context.Context, marshaler runtime.Ma if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -224,6 +221,9 @@ func request_PolicyService_PutPolicy_0(ctx context.Context, marshaler runtime.Ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -258,9 +258,6 @@ func request_PolicyService_PatchPolicy_0(ctx context.Context, marshaler runtime. if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -269,6 +266,9 @@ func request_PolicyService_PatchPolicy_0(ctx context.Context, marshaler runtime. if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PatchPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -300,9 +300,6 @@ func request_PolicyService_DeletePolicy_0(ctx context.Context, marshaler runtime metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -311,6 +308,9 @@ func request_PolicyService_DeletePolicy_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeletePolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -342,9 +342,6 @@ func request_PolicyService_EnableDisablePolicyNotification_0(ctx context.Context if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["policy_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "policy_id") @@ -353,6 +350,9 @@ func request_PolicyService_EnableDisablePolicyNotification_0(ctx context.Context if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "policy_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.EnableDisablePolicyNotification(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -459,9 +459,6 @@ func request_PolicyService_QueryDryRunJobStatus_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["job_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "job_id") @@ -470,6 +467,9 @@ func request_PolicyService_QueryDryRunJobStatus_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "job_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.QueryDryRunJobStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -498,9 +498,6 @@ func request_PolicyService_CancelDryRunJob_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["job_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "job_id") @@ -509,6 +506,9 @@ func request_PolicyService_CancelDryRunJob_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "job_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CancelDryRunJob(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/policy_service.swagger.json b/generated/api/v1/policy_service.swagger.json index faeb58adf12cf..e084db38a9d74 100644 --- a/generated/api/v1/policy_service.swagger.json +++ b/generated/api/v1/policy_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "PolicyService" + "name": "PolicyService", + "description": "PolicyService APIs can be used to manage policies." } ], "consumes": [ diff --git a/generated/api/v1/probe_upload_service.pb.gw.go b/generated/api/v1/probe_upload_service.pb.gw.go index f35454c7cde77..526b315d8a40e 100644 --- a/generated/api/v1/probe_upload_service.pb.gw.go +++ b/generated/api/v1/probe_upload_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ProbeUploadService_GetExistingProbes_0(ctx context.Context, marshal protoReq GetExistingProbesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProbeUploadService_GetExistingProbes_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetExistingProbes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/process_baseline_service.pb.gw.go b/generated/api/v1/process_baseline_service.pb.gw.go index bd4fa797522a9..bf2f96e5b64d5 100644 --- a/generated/api/v1/process_baseline_service.pb.gw.go +++ b/generated/api/v1/process_baseline_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ProcessBaselineService_GetProcessBaseline_0(ctx context.Context, ma protoReq GetProcessBaselineRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProcessBaselineService_GetProcessBaseline_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetProcessBaseline(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -185,15 +185,15 @@ func request_ProcessBaselineService_DeleteProcessBaselines_0(ctx context.Context protoReq DeleteProcessBaselinesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProcessBaselineService_DeleteProcessBaselines_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteProcessBaselines(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/process_baseline_service.swagger.json b/generated/api/v1/process_baseline_service.swagger.json index 8ab759940ec00..1e81749ddf499 100644 --- a/generated/api/v1/process_baseline_service.swagger.json +++ b/generated/api/v1/process_baseline_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ProcessBaselineService" + "name": "ProcessBaselineService", + "description": "`ProcessBaselineService` APIs can be used to manage process baselines." } ], "consumes": [ diff --git a/generated/api/v1/process_listening_on_port_service.pb.gw.go b/generated/api/v1/process_listening_on_port_service.pb.gw.go index ad3a52474e845..abf8f51636f0c 100644 --- a/generated/api/v1/process_listening_on_port_service.pb.gw.go +++ b/generated/api/v1/process_listening_on_port_service.pb.gw.go @@ -43,9 +43,6 @@ func request_ListeningEndpointsService_GetListeningEndpoints_0(ctx context.Conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -60,6 +57,9 @@ func request_ListeningEndpointsService_GetListeningEndpoints_0(ctx context.Conte if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ListeningEndpointsService_GetListeningEndpoints_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetListeningEndpoints(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/process_listening_on_port_service.swagger.json b/generated/api/v1/process_listening_on_port_service.swagger.json index 7953707c2f48a..52fe688b4fe00 100644 --- a/generated/api/v1/process_listening_on_port_service.swagger.json +++ b/generated/api/v1/process_listening_on_port_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ListeningEndpointsService" + "name": "ListeningEndpointsService", + "description": "ListeningEndpointsService API can be used to list listening endpoints and the processes that opened them." } ], "consumes": [ diff --git a/generated/api/v1/process_service.pb.gw.go b/generated/api/v1/process_service.pb.gw.go index f25925019271f..82447c5165500 100644 --- a/generated/api/v1/process_service.pb.gw.go +++ b/generated/api/v1/process_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ProcessService_CountProcesses_0(ctx context.Context, marshaler runt protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProcessService_CountProcesses_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountProcesses(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_ProcessService_GetProcessesByDeployment_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -87,6 +84,9 @@ func request_ProcessService_GetProcessesByDeployment_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetProcessesByDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -115,9 +115,6 @@ func request_ProcessService_GetGroupedProcessByDeployment_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -126,6 +123,9 @@ func request_ProcessService_GetGroupedProcessByDeployment_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetGroupedProcessByDeployment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -154,9 +154,6 @@ func request_ProcessService_GetGroupedProcessByDeploymentAndContainer_0(ctx cont metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["deployment_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "deployment_id") @@ -165,6 +162,9 @@ func request_ProcessService_GetGroupedProcessByDeploymentAndContainer_0(ctx cont if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "deployment_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetGroupedProcessByDeploymentAndContainer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/process_service.swagger.json b/generated/api/v1/process_service.swagger.json index c517bfb0d3de2..6df20adc41588 100644 --- a/generated/api/v1/process_service.swagger.json +++ b/generated/api/v1/process_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "ProcessService" + "name": "ProcessService", + "description": "ProcessService APIs can be used to list processes executed in deployments." } ], "consumes": [ diff --git a/generated/api/v1/rbac_service.pb.gw.go b/generated/api/v1/rbac_service.pb.gw.go index 83e52643eb37b..e762978e6b3be 100644 --- a/generated/api/v1/rbac_service.pb.gw.go +++ b/generated/api/v1/rbac_service.pb.gw.go @@ -41,9 +41,6 @@ func request_RbacService_GetRole_0(ctx context.Context, marshaler runtime.Marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_RbacService_GetRole_0(ctx context.Context, marshaler runtime.Marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_RbacService_ListRoles_0(ctx context.Context, marshaler runtime.Mars protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RbacService_ListRoles_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListRoles(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -115,9 +115,6 @@ func request_RbacService_GetRoleBinding_0(ctx context.Context, marshaler runtime metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -126,6 +123,9 @@ func request_RbacService_GetRoleBinding_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRoleBinding(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -155,15 +155,15 @@ func request_RbacService_ListRoleBindings_0(ctx context.Context, marshaler runti protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RbacService_ListRoleBindings_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListRoleBindings(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -189,9 +189,6 @@ func request_RbacService_GetSubject_0(ctx context.Context, marshaler runtime.Mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -200,6 +197,9 @@ func request_RbacService_GetSubject_0(ctx context.Context, marshaler runtime.Mar if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetSubject(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -229,15 +229,15 @@ func request_RbacService_ListSubjects_0(ctx context.Context, marshaler runtime.M protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RbacService_ListSubjects_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListSubjects(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/resource_collection_service.pb.gw.go b/generated/api/v1/resource_collection_service.pb.gw.go index 6de4f6646b426..17989bc5527ed 100644 --- a/generated/api/v1/resource_collection_service.pb.gw.go +++ b/generated/api/v1/resource_collection_service.pb.gw.go @@ -64,9 +64,6 @@ func request_CollectionService_GetCollection_0(ctx context.Context, marshaler ru metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -81,6 +78,9 @@ func request_CollectionService_GetCollection_0(ctx context.Context, marshaler ru if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CollectionService_GetCollection_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_CollectionService_GetCollectionCount_0(ctx context.Context, marshal protoReq GetCollectionCountRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CollectionService_GetCollectionCount_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetCollectionCount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -180,9 +180,6 @@ func request_CollectionService_UpdateCollection_0(ctx context.Context, marshaler if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -191,6 +188,9 @@ func request_CollectionService_UpdateCollection_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -223,15 +223,15 @@ func request_CollectionService_ListCollections_0(ctx context.Context, marshaler protoReq ListCollectionsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CollectionService_ListCollections_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListCollections(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -257,9 +257,6 @@ func request_CollectionService_DeleteCollection_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -268,6 +265,9 @@ func request_CollectionService_DeleteCollection_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteCollection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/role_service.pb.gw.go b/generated/api/v1/role_service.pb.gw.go index 69f19e28bf71c..ffd6c83912a0d 100644 --- a/generated/api/v1/role_service.pb.gw.go +++ b/generated/api/v1/role_service.pb.gw.go @@ -63,9 +63,6 @@ func request_RoleService_GetRole_0(ctx context.Context, marshaler runtime.Marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -74,6 +71,9 @@ func request_RoleService_GetRole_0(ctx context.Context, marshaler runtime.Marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -126,9 +126,6 @@ func request_RoleService_CreateRole_0(ctx context.Context, marshaler runtime.Mar if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.Role); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") @@ -137,6 +134,9 @@ func request_RoleService_CreateRole_0(ctx context.Context, marshaler runtime.Mar if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CreateRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -171,9 +171,6 @@ func request_RoleService_UpdateRole_0(ctx context.Context, marshaler runtime.Mar if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") @@ -182,6 +179,9 @@ func request_RoleService_UpdateRole_0(ctx context.Context, marshaler runtime.Mar if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -213,9 +213,6 @@ func request_RoleService_DeleteRole_0(ctx context.Context, marshaler runtime.Mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -224,6 +221,9 @@ func request_RoleService_DeleteRole_0(ctx context.Context, marshaler runtime.Mar if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -273,9 +273,6 @@ func request_RoleService_GetPermissionSet_0(ctx context.Context, marshaler runti metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -284,6 +281,9 @@ func request_RoleService_GetPermissionSet_0(ctx context.Context, marshaler runti if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetPermissionSet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -363,9 +363,6 @@ func request_RoleService_PutPermissionSet_0(ctx context.Context, marshaler runti if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -374,6 +371,9 @@ func request_RoleService_PutPermissionSet_0(ctx context.Context, marshaler runti if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutPermissionSet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -405,9 +405,6 @@ func request_RoleService_DeletePermissionSet_0(ctx context.Context, marshaler ru metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -416,6 +413,9 @@ func request_RoleService_DeletePermissionSet_0(ctx context.Context, marshaler ru if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeletePermissionSet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -444,9 +444,6 @@ func request_RoleService_GetSimpleAccessScope_0(ctx context.Context, marshaler r metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -455,6 +452,9 @@ func request_RoleService_GetSimpleAccessScope_0(ctx context.Context, marshaler r if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetSimpleAccessScope(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -534,9 +534,6 @@ func request_RoleService_PutSimpleAccessScope_0(ctx context.Context, marshaler r if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -545,6 +542,9 @@ func request_RoleService_PutSimpleAccessScope_0(ctx context.Context, marshaler r if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutSimpleAccessScope(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -576,9 +576,6 @@ func request_RoleService_DeleteSimpleAccessScope_0(ctx context.Context, marshale metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -587,6 +584,9 @@ func request_RoleService_DeleteSimpleAccessScope_0(ctx context.Context, marshale if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteSimpleAccessScope(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -619,15 +619,15 @@ func request_RoleService_ComputeEffectiveAccessScope_0(ctx context.Context, mars if err := marshaler.NewDecoder(req.Body).Decode(&protoReq.AccessScope); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RoleService_ComputeEffectiveAccessScope_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ComputeEffectiveAccessScope(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -657,15 +657,15 @@ func request_RoleService_GetClustersForPermissions_0(ctx context.Context, marsha protoReq GetClustersForPermissionsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RoleService_GetClustersForPermissions_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetClustersForPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -693,9 +693,6 @@ func request_RoleService_GetNamespacesForClusterAndPermissions_0(ctx context.Con metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -710,6 +707,9 @@ func request_RoleService_GetNamespacesForClusterAndPermissions_0(ctx context.Con if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RoleService_GetNamespacesForClusterAndPermissions_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNamespacesForClusterAndPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/search_service.pb.gw.go b/generated/api/v1/search_service.pb.gw.go index d3552e35e4529..d378f935e30c5 100644 --- a/generated/api/v1/search_service.pb.gw.go +++ b/generated/api/v1/search_service.pb.gw.go @@ -42,15 +42,15 @@ func request_SearchService_Search_0(ctx context.Context, marshaler runtime.Marsh protoReq RawSearchRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SearchService_Search_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.Search(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -77,15 +77,15 @@ func request_SearchService_Options_0(ctx context.Context, marshaler runtime.Mars protoReq SearchOptionsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SearchService_Options_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.Options(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -112,15 +112,15 @@ func request_SearchService_Autocomplete_0(ctx context.Context, marshaler runtime protoReq RawSearchRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SearchService_Autocomplete_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.Autocomplete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/search_service.swagger.json b/generated/api/v1/search_service.swagger.json index f5a8a5301592d..50579f6c31c4a 100644 --- a/generated/api/v1/search_service.swagger.json +++ b/generated/api/v1/search_service.swagger.json @@ -531,7 +531,7 @@ "IMAGE_CVE_INFOS" ], "default": "SEARCH_UNSET", - "title": "Next available tag: 85" + "description": "Next available tag: 85" }, "v1SearchOptionsResponse": { "type": "object", diff --git a/generated/api/v1/secret_service.pb.gw.go b/generated/api/v1/secret_service.pb.gw.go index 5cc80c64bf04c..d48c3cd9aaf6a 100644 --- a/generated/api/v1/secret_service.pb.gw.go +++ b/generated/api/v1/secret_service.pb.gw.go @@ -41,9 +41,6 @@ func request_SecretService_GetSecret_0(ctx context.Context, marshaler runtime.Ma metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_SecretService_GetSecret_0(ctx context.Context, marshaler runtime.Ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetSecret(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_SecretService_CountSecrets_0(ctx context.Context, marshaler runtime protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SecretService_CountSecrets_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountSecrets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_SecretService_ListSecrets_0(ctx context.Context, marshaler runtime. protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SecretService_ListSecrets_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListSecrets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -151,15 +151,15 @@ func request_SecretService_ListSecretsExtended_0(ctx context.Context, marshaler protoReq ListSecretsExtendedRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SecretService_ListSecretsExtended_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListSecretsExtended(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/sensor_upgrade_service.pb.gw.go b/generated/api/v1/sensor_upgrade_service.pb.gw.go index 77e2081bf85f1..8ae39c741f536 100644 --- a/generated/api/v1/sensor_upgrade_service.pb.gw.go +++ b/generated/api/v1/sensor_upgrade_service.pb.gw.go @@ -89,9 +89,6 @@ func request_SensorUpgradeService_TriggerSensorUpgrade_0(ctx context.Context, ma metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -100,6 +97,9 @@ func request_SensorUpgradeService_TriggerSensorUpgrade_0(ctx context.Context, ma if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.TriggerSensorUpgrade(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -128,9 +128,6 @@ func request_SensorUpgradeService_TriggerSensorCertRotation_0(ctx context.Contex metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -139,6 +136,9 @@ func request_SensorUpgradeService_TriggerSensorCertRotation_0(ctx context.Contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.TriggerSensorCertRotation(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/sensor_upgrade_service.swagger.json b/generated/api/v1/sensor_upgrade_service.swagger.json index a324e351aea17..0a768d37d377d 100644 --- a/generated/api/v1/sensor_upgrade_service.swagger.json +++ b/generated/api/v1/sensor_upgrade_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "SensorUpgradeService" + "name": "SensorUpgradeService", + "description": "SensorUpgradeService is deprecated. Use operator-based installation instead;\nthe operator handles sensor upgrades and certificate rotation." } ], "consumes": [ diff --git a/generated/api/v1/service_account_service.pb.gw.go b/generated/api/v1/service_account_service.pb.gw.go index 8958f848a264f..8bddc40cc38c8 100644 --- a/generated/api/v1/service_account_service.pb.gw.go +++ b/generated/api/v1/service_account_service.pb.gw.go @@ -41,9 +41,6 @@ func request_ServiceAccountService_GetServiceAccount_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_ServiceAccountService_GetServiceAccount_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetServiceAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_ServiceAccountService_ListServiceAccounts_0(ctx context.Context, ma protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ServiceAccountService_ListServiceAccounts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListServiceAccounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/signature_integration_service.pb.gw.go b/generated/api/v1/signature_integration_service.pb.gw.go index 9159179d308eb..3e0726fa787d4 100644 --- a/generated/api/v1/signature_integration_service.pb.gw.go +++ b/generated/api/v1/signature_integration_service.pb.gw.go @@ -63,9 +63,6 @@ func request_SignatureIntegrationService_GetSignatureIntegration_0(ctx context.C metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -74,6 +71,9 @@ func request_SignatureIntegrationService_GetSignatureIntegration_0(ctx context.C if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetSignatureIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -132,9 +132,6 @@ func request_SignatureIntegrationService_PutSignatureIntegration_0(ctx context.C if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -143,6 +140,9 @@ func request_SignatureIntegrationService_PutSignatureIntegration_0(ctx context.C if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.PutSignatureIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -174,9 +174,6 @@ func request_SignatureIntegrationService_DeleteSignatureIntegration_0(ctx contex metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -185,6 +182,9 @@ func request_SignatureIntegrationService_DeleteSignatureIntegration_0(ctx contex if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteSignatureIntegration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/user_service.pb.gw.go b/generated/api/v1/user_service.pb.gw.go index 231759af351f3..842f59fccefe4 100644 --- a/generated/api/v1/user_service.pb.gw.go +++ b/generated/api/v1/user_service.pb.gw.go @@ -62,9 +62,6 @@ func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -73,6 +70,9 @@ func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v1/vuln_mgmt_service.pb.gw.go b/generated/api/v1/vuln_mgmt_service.pb.gw.go index 211534deb1394..77e79d7c43d65 100644 --- a/generated/api/v1/vuln_mgmt_service.pb.gw.go +++ b/generated/api/v1/vuln_mgmt_service.pb.gw.go @@ -42,15 +42,15 @@ func request_VulnMgmtService_VulnMgmtExportWorkloads_0(ctx context.Context, mars protoReq VulnMgmtExportWorkloadsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VulnMgmtService_VulnMgmtExportWorkloads_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } stream, err := client.VulnMgmtExportWorkloads(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/generated/api/v1/vuln_mgmt_service.swagger.json b/generated/api/v1/vuln_mgmt_service.swagger.json index f35a4d8d751fa..3ffb4f3fa154c 100644 --- a/generated/api/v1/vuln_mgmt_service.swagger.json +++ b/generated/api/v1/vuln_mgmt_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "VulnMgmtService" + "name": "VulnMgmtService", + "description": "VulnMgmtService APIs are used to manage workload vulnerabilities." } ], "consumes": [ @@ -183,7 +184,7 @@ "UNKNOWN" ], "default": "UNSET", - "title": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" + "description": "For any update to EnvVarSource, please also update 'ui/src/messages/common.js'" }, "LabelSelectorRequirement": { "type": "object", @@ -1399,7 +1400,7 @@ "CLUSTER_ADMIN" ], "default": "UNSET", - "title": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" + "description": "For any update to PermissionLevel, also update:\n- pkg/searchbasedpolicies/builders/k8s_rbac.go\n- ui/src/messages/common.js" }, "storagePortConfig": { "type": "object", diff --git a/generated/api/v2/base_image_service.pb.gw.go b/generated/api/v2/base_image_service.pb.gw.go index 4f16f63e08867..c3a0d7cf29f6f 100644 --- a/generated/api/v2/base_image_service.pb.gw.go +++ b/generated/api/v2/base_image_service.pb.gw.go @@ -92,9 +92,6 @@ func request_BaseImageServiceV2_UpdateBaseImageTagPattern_0(ctx context.Context, if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -103,6 +100,9 @@ func request_BaseImageServiceV2_UpdateBaseImageTagPattern_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateBaseImageTagPattern(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -134,9 +134,6 @@ func request_BaseImageServiceV2_DeleteBaseImageReference_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -145,6 +142,9 @@ func request_BaseImageServiceV2_DeleteBaseImageReference_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteBaseImageReference(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/base_image_service.swagger.json b/generated/api/v2/base_image_service.swagger.json index 60d0dd4bb7c61..4fe252015cbb7 100644 --- a/generated/api/v2/base_image_service.swagger.json +++ b/generated/api/v2/base_image_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "BaseImageServiceV2" + "name": "BaseImageServiceV2", + "description": "BaseImageServiceV2 APIs manages base image references." } ], "consumes": [ diff --git a/generated/api/v2/compliance_integration_service.pb.gw.go b/generated/api/v2/compliance_integration_service.pb.gw.go index b3ae5fb8f858a..1fedb13211987 100644 --- a/generated/api/v2/compliance_integration_service.pb.gw.go +++ b/generated/api/v2/compliance_integration_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ComplianceIntegrationService_ListComplianceIntegrations_0(ctx conte protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceIntegrationService_ListComplianceIntegrations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListComplianceIntegrations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/compliance_integration_service.swagger.json b/generated/api/v2/compliance_integration_service.swagger.json index 8457fc910e24f..c3162a7b61802 100644 --- a/generated/api/v2/compliance_integration_service.swagger.json +++ b/generated/api/v2/compliance_integration_service.swagger.json @@ -150,7 +150,7 @@ "UNHEALTHY" ], "default": "HEALTHY", - "title": "Represents the status of compliance operator" + "description": "Represents the status of compliance operator" }, "v2ClusterPlatformType": { "type": "string", diff --git a/generated/api/v2/compliance_profile_service.pb.gw.go b/generated/api/v2/compliance_profile_service.pb.gw.go index 76840ac44f315..54b5a20c7959b 100644 --- a/generated/api/v2/compliance_profile_service.pb.gw.go +++ b/generated/api/v2/compliance_profile_service.pb.gw.go @@ -41,9 +41,6 @@ func request_ComplianceProfileService_GetComplianceProfile_0(ctx context.Context metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_ComplianceProfileService_GetComplianceProfile_0(ctx context.Context if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_ComplianceProfileService_ListComplianceProfiles_0(ctx context.Conte protoReq ProfilesForClusterRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceProfileService_ListComplianceProfiles_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListComplianceProfiles(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -116,15 +116,15 @@ func request_ComplianceProfileService_ListProfileSummaries_0(ctx context.Context protoReq ClustersProfileSummaryRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceProfileService_ListProfileSummaries_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListProfileSummaries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/compliance_results_service.pb.gw.go b/generated/api/v2/compliance_results_service.pb.gw.go index 1e74de8c07eb7..0b059ab384986 100644 --- a/generated/api/v2/compliance_results_service.pb.gw.go +++ b/generated/api/v2/compliance_results_service.pb.gw.go @@ -41,9 +41,6 @@ func request_ComplianceResultsService_GetComplianceScanCheckResult_0(ctx context metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_ComplianceResultsService_GetComplianceScanCheckResult_0(ctx context if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceScanCheckResult(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_ComplianceResultsService_GetComplianceScanResults_0(ctx context.Con protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceScanResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceScanResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -117,9 +117,6 @@ func request_ComplianceResultsService_GetComplianceScanConfigurationResults_0(ct metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["scan_config_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "scan_config_name") @@ -134,6 +131,9 @@ func request_ComplianceResultsService_GetComplianceScanConfigurationResults_0(ct if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceScanConfigurationResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceScanConfigurationResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -170,9 +170,6 @@ func request_ComplianceResultsService_GetComplianceProfileResults_0(ctx context. metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -187,6 +184,9 @@ func request_ComplianceResultsService_GetComplianceProfileResults_0(ctx context. if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceProfileResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -223,9 +223,6 @@ func request_ComplianceResultsService_GetComplianceProfileCheckResult_0(ctx cont metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -248,6 +245,9 @@ func request_ComplianceResultsService_GetComplianceProfileCheckResult_0(ctx cont if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceProfileCheckResult_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileCheckResult(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -292,9 +292,6 @@ func request_ComplianceResultsService_GetComplianceProfileClusterResults_0(ctx c metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -317,6 +314,9 @@ func request_ComplianceResultsService_GetComplianceProfileClusterResults_0(ctx c if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceProfileClusterResults_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileClusterResults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -361,9 +361,6 @@ func request_ComplianceResultsService_GetComplianceProfileCheckDetails_0(ctx con metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -386,6 +383,9 @@ func request_ComplianceResultsService_GetComplianceProfileCheckDetails_0(ctx con if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsService_GetComplianceProfileCheckDetails_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileCheckDetails(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/compliance_results_stats_service.pb.gw.go b/generated/api/v2/compliance_results_stats_service.pb.gw.go index a5a101f60df19..ea656923fb95f 100644 --- a/generated/api/v2/compliance_results_stats_service.pb.gw.go +++ b/generated/api/v2/compliance_results_stats_service.pb.gw.go @@ -43,9 +43,6 @@ func request_ComplianceResultsStatsService_GetComplianceProfileStats_0(ctx conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -60,6 +57,9 @@ func request_ComplianceResultsStatsService_GetComplianceProfileStats_0(ctx conte if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceProfileStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -95,15 +95,15 @@ func request_ComplianceResultsStatsService_GetComplianceProfilesStats_0(ctx cont protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceProfilesStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfilesStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -131,9 +131,6 @@ func request_ComplianceResultsStatsService_GetComplianceProfilesClusterStats_0(c metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -148,6 +145,9 @@ func request_ComplianceResultsStatsService_GetComplianceProfilesClusterStats_0(c if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceProfilesClusterStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfilesClusterStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -184,9 +184,6 @@ func request_ComplianceResultsStatsService_GetComplianceProfileCheckStats_0(ctx metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -209,6 +206,9 @@ func request_ComplianceResultsStatsService_GetComplianceProfileCheckStats_0(ctx if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceProfileCheckStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceProfileCheckStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -253,9 +253,6 @@ func request_ComplianceResultsStatsService_GetComplianceClusterScanStats_0(ctx c metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -270,6 +267,9 @@ func request_ComplianceResultsStatsService_GetComplianceClusterScanStats_0(ctx c if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceClusterScanStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceClusterScanStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -305,15 +305,15 @@ func request_ComplianceResultsStatsService_GetComplianceOverallClusterStats_0(ct protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceOverallClusterStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceOverallClusterStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -341,9 +341,6 @@ func request_ComplianceResultsStatsService_GetComplianceClusterStats_0(ctx conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["profile_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "profile_name") @@ -358,6 +355,9 @@ func request_ComplianceResultsStatsService_GetComplianceClusterStats_0(ctx conte if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceResultsStatsService_GetComplianceClusterStats_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceClusterStats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/compliance_rule_service.pb.gw.go b/generated/api/v2/compliance_rule_service.pb.gw.go index 4a934175a8038..6584493a35518 100644 --- a/generated/api/v2/compliance_rule_service.pb.gw.go +++ b/generated/api/v2/compliance_rule_service.pb.gw.go @@ -43,9 +43,6 @@ func request_ComplianceRuleService_GetComplianceRule_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["rule_name"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "rule_name") @@ -60,6 +57,9 @@ func request_ComplianceRuleService_GetComplianceRule_0(ctx context.Context, mars if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceRuleService_GetComplianceRule_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceRule(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/compliance_scan_configuration_service.pb.gw.go b/generated/api/v2/compliance_scan_configuration_service.pb.gw.go index 67b0864a0e3f9..c500547c45890 100644 --- a/generated/api/v2/compliance_scan_configuration_service.pb.gw.go +++ b/generated/api/v2/compliance_scan_configuration_service.pb.gw.go @@ -42,15 +42,15 @@ func request_ComplianceScanConfigurationService_ListComplianceScanConfigurations protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceScanConfigurationService_ListComplianceScanConfigurations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListComplianceScanConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -76,9 +76,6 @@ func request_ComplianceScanConfigurationService_GetComplianceScanConfiguration_0 metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -87,6 +84,9 @@ func request_ComplianceScanConfigurationService_GetComplianceScanConfiguration_0 if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetComplianceScanConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -145,9 +145,6 @@ func request_ComplianceScanConfigurationService_UpdateComplianceScanConfiguratio if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -156,6 +153,9 @@ func request_ComplianceScanConfigurationService_UpdateComplianceScanConfiguratio if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateComplianceScanConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -187,9 +187,6 @@ func request_ComplianceScanConfigurationService_DeleteComplianceScanConfiguratio metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -198,6 +195,9 @@ func request_ComplianceScanConfigurationService_DeleteComplianceScanConfiguratio if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteComplianceScanConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -226,9 +226,6 @@ func request_ComplianceScanConfigurationService_RunComplianceScanConfiguration_0 metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -237,6 +234,9 @@ func request_ComplianceScanConfigurationService_RunComplianceScanConfiguration_0 if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.RunComplianceScanConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -294,9 +294,6 @@ func request_ComplianceScanConfigurationService_GetReportHistory_0(ctx context.C metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -311,6 +308,9 @@ func request_ComplianceScanConfigurationService_GetReportHistory_0(ctx context.C if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceScanConfigurationService_GetReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -347,9 +347,6 @@ func request_ComplianceScanConfigurationService_GetMyReportHistory_0(ctx context metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -364,6 +361,9 @@ func request_ComplianceScanConfigurationService_GetMyReportHistory_0(ctx context if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceScanConfigurationService_GetMyReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetMyReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -398,9 +398,6 @@ func request_ComplianceScanConfigurationService_DeleteReport_0(ctx context.Conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -409,6 +406,9 @@ func request_ComplianceScanConfigurationService_DeleteReport_0(ctx context.Conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -438,15 +438,15 @@ func request_ComplianceScanConfigurationService_ListComplianceScanConfigProfiles protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceScanConfigurationService_ListComplianceScanConfigProfiles_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListComplianceScanConfigProfiles(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -474,9 +474,6 @@ func request_ComplianceScanConfigurationService_ListComplianceScanConfigClusterP metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cluster_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cluster_id") @@ -491,6 +488,9 @@ func request_ComplianceScanConfigurationService_ListComplianceScanConfigClusterP if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ComplianceScanConfigurationService_ListComplianceScanConfigClusterProfiles_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListComplianceScanConfigClusterProfiles(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/node_report_service.pb.gw.go b/generated/api/v2/node_report_service.pb.gw.go index c1bad98935a47..d9a561139487d 100644 --- a/generated/api/v2/node_report_service.pb.gw.go +++ b/generated/api/v2/node_report_service.pb.gw.go @@ -71,9 +71,6 @@ func request_NodeReportService_UpdateNodeReportConfiguration_0(ctx context.Conte if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -82,6 +79,9 @@ func request_NodeReportService_UpdateNodeReportConfiguration_0(ctx context.Conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -114,15 +114,15 @@ func request_NodeReportService_ListNodeReportConfigurations_0(ctx context.Contex protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_ListNodeReportConfigurations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListNodeReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -149,15 +149,15 @@ func request_NodeReportService_CountNodeReportConfigurations_0(ctx context.Conte protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_CountNodeReportConfigurations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountNodeReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -183,9 +183,6 @@ func request_NodeReportService_GetNodeReportConfiguration_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -194,6 +191,9 @@ func request_NodeReportService_GetNodeReportConfiguration_0(ctx context.Context, if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -222,9 +222,6 @@ func request_NodeReportService_DeleteNodeReportConfiguration_0(ctx context.Conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -233,6 +230,9 @@ func request_NodeReportService_DeleteNodeReportConfiguration_0(ctx context.Conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteNodeReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -290,9 +290,6 @@ func request_NodeReportService_GetNodeReportHistory_0(ctx context.Context, marsh metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -307,6 +304,9 @@ func request_NodeReportService_GetNodeReportHistory_0(ctx context.Context, marsh if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetNodeReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -343,9 +343,6 @@ func request_NodeReportService_GetMyNodeReportHistory_0(ctx context.Context, mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -360,6 +357,9 @@ func request_NodeReportService_GetMyNodeReportHistory_0(ctx context.Context, mar if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetMyNodeReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetMyNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -394,9 +394,6 @@ func request_NodeReportService_GetNodeReportStatus_0(ctx context.Context, marsha metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -405,6 +402,9 @@ func request_NodeReportService_GetNodeReportStatus_0(ctx context.Context, marsha if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetNodeReportStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -433,9 +433,6 @@ func request_NodeReportService_CancelNodeReport_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -444,6 +441,9 @@ func request_NodeReportService_CancelNodeReport_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CancelNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -472,9 +472,6 @@ func request_NodeReportService_DeleteNodeReport_0(ctx context.Context, marshaler metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -483,6 +480,9 @@ func request_NodeReportService_DeleteNodeReport_0(ctx context.Context, marshaler if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteNodeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -539,15 +539,15 @@ func request_NodeReportService_GetViewBasedNodeReportHistory_0(ctx context.Conte protoReq GetViewBasedReportHistoryRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedNodeReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetViewBasedNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -574,15 +574,15 @@ func request_NodeReportService_GetViewBasedMyNodeReportHistory_0(ctx context.Con protoReq GetViewBasedReportHistoryRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NodeReportService_GetViewBasedMyNodeReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetViewBasedMyNodeReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/node_report_service.swagger.json b/generated/api/v2/node_report_service.swagger.json index 7af235e1fb798..f66f7737488b5 100644 --- a/generated/api/v2/node_report_service.swagger.json +++ b/generated/api/v2/node_report_service.swagger.json @@ -1070,7 +1070,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportHistoryResponse": { "type": "object", @@ -1108,7 +1108,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportSchedule": { "type": "object", @@ -1232,7 +1232,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportStatus": { "type": "object", diff --git a/generated/api/v2/report_service.pb.gw.go b/generated/api/v2/report_service.pb.gw.go index c950a85a074fa..360f14b3d2e54 100644 --- a/generated/api/v2/report_service.pb.gw.go +++ b/generated/api/v2/report_service.pb.gw.go @@ -71,9 +71,6 @@ func request_ReportService_UpdateReportConfiguration_0(ctx context.Context, mars if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -82,6 +79,9 @@ func request_ReportService_UpdateReportConfiguration_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -114,15 +114,15 @@ func request_ReportService_ListReportConfigurations_0(ctx context.Context, marsh protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_ListReportConfigurations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -149,15 +149,15 @@ func request_ReportService_CountReportConfigurations_0(ctx context.Context, mars protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_CountReportConfigurations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CountReportConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -183,9 +183,6 @@ func request_ReportService_GetReportConfiguration_0(ctx context.Context, marshal metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -194,6 +191,9 @@ func request_ReportService_GetReportConfiguration_0(ctx context.Context, marshal if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -222,9 +222,6 @@ func request_ReportService_DeleteReportConfiguration_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -233,6 +230,9 @@ func request_ReportService_DeleteReportConfiguration_0(ctx context.Context, mars if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteReportConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -261,9 +261,6 @@ func request_ReportService_GetReportStatus_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -272,6 +269,9 @@ func request_ReportService_GetReportStatus_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetReportStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -302,9 +302,6 @@ func request_ReportService_GetReportHistory_0(ctx context.Context, marshaler run metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -319,6 +316,9 @@ func request_ReportService_GetReportHistory_0(ctx context.Context, marshaler run if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_GetReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -355,9 +355,6 @@ func request_ReportService_GetMyReportHistory_0(ctx context.Context, marshaler r metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -372,6 +369,9 @@ func request_ReportService_GetMyReportHistory_0(ctx context.Context, marshaler r if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_GetMyReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetMyReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -433,9 +433,6 @@ func request_ReportService_CancelReport_0(ctx context.Context, marshaler runtime metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -444,6 +441,9 @@ func request_ReportService_CancelReport_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CancelReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -472,9 +472,6 @@ func request_ReportService_DeleteReport_0(ctx context.Context, marshaler runtime metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -483,6 +480,9 @@ func request_ReportService_DeleteReport_0(ctx context.Context, marshaler runtime if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -539,15 +539,15 @@ func request_ReportService_GetViewBasedMyReportHistory_0(ctx context.Context, ma protoReq GetViewBasedReportHistoryRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_GetViewBasedMyReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetViewBasedMyReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -574,15 +574,15 @@ func request_ReportService_GetViewBasedReportHistory_0(ctx context.Context, mars protoReq GetViewBasedReportHistoryRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ReportService_GetViewBasedReportHistory_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetViewBasedReportHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/report_service.swagger.json b/generated/api/v2/report_service.swagger.json index c1dc856396903..20949012ad90d 100644 --- a/generated/api/v2/report_service.swagger.json +++ b/generated/api/v2/report_service.swagger.json @@ -1082,7 +1082,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportHistoryResponse": { "type": "object", @@ -1120,7 +1120,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportSchedule": { "type": "object", @@ -1244,7 +1244,7 @@ "NODE_VULNERABILITY" ], "default": "VULNERABILITY", - "title": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" + "description": "- VULNERABILITY: for historical reasons, this short form was used to mean \"Image Vulnerability\"" }, "v2ReportStatus": { "type": "object", diff --git a/generated/api/v2/virtual_machine_service.pb.gw.go b/generated/api/v2/virtual_machine_service.pb.gw.go index 7a0f59a3c2ec1..ab4bf0dc49f3f 100644 --- a/generated/api/v2/virtual_machine_service.pb.gw.go +++ b/generated/api/v2/virtual_machine_service.pb.gw.go @@ -43,9 +43,6 @@ func request_VirtualMachineService_GetVirtualMachine_0(ctx context.Context, mars metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -60,6 +57,9 @@ func request_VirtualMachineService_GetVirtualMachine_0(ctx context.Context, mars if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineService_GetVirtualMachine_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVirtualMachine(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -95,15 +95,15 @@ func request_VirtualMachineService_ListVirtualMachines_0(ctx context.Context, ma protoReq ListVirtualMachinesRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineService_ListVirtualMachines_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVirtualMachines(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/virtual_machine_v2_service.pb.gw.go b/generated/api/v2/virtual_machine_v2_service.pb.gw.go index 1ed2bd3562d05..aeafc249b1ffc 100644 --- a/generated/api/v2/virtual_machine_v2_service.pb.gw.go +++ b/generated/api/v2/virtual_machine_v2_service.pb.gw.go @@ -41,9 +41,6 @@ func request_VirtualMachineV2Service_GetVM_0(ctx context.Context, marshaler runt metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_VirtualMachineV2Service_GetVM_0(ctx context.Context, marshaler runt if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVM(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -82,9 +82,6 @@ func request_VirtualMachineV2Service_GetVMVulnSummary_0(ctx context.Context, mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -99,6 +96,9 @@ func request_VirtualMachineV2Service_GetVMVulnSummary_0(ctx context.Context, mar if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_GetVMVulnSummary_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVMVulnSummary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -135,9 +135,6 @@ func request_VirtualMachineV2Service_ListVMCVEsByVM_0(ctx context.Context, marsh metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["vm_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "vm_id") @@ -152,6 +149,9 @@ func request_VirtualMachineV2Service_ListVMCVEsByVM_0(ctx context.Context, marsh if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_ListVMCVEsByVM_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVMCVEsByVM(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -186,9 +186,6 @@ func request_VirtualMachineV2Service_GetVMCVEComponents_0(ctx context.Context, m metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["vm_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "vm_id") @@ -205,6 +202,9 @@ func request_VirtualMachineV2Service_GetVMCVEComponents_0(ctx context.Context, m if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "cve_id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVMCVEComponents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -243,9 +243,6 @@ func request_VirtualMachineV2Service_ListVMComponents_0(ctx context.Context, mar metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["vm_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "vm_id") @@ -260,6 +257,9 @@ func request_VirtualMachineV2Service_ListVMComponents_0(ctx context.Context, mar if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_ListVMComponents_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVMComponents(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -295,15 +295,15 @@ func request_VirtualMachineV2Service_ListVMs_0(ctx context.Context, marshaler ru protoReq ListVMsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_ListVMs_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVMs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -330,15 +330,15 @@ func request_VirtualMachineV2Service_ListVMCVEs_0(ctx context.Context, marshaler protoReq ListVMCVEsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_ListVMCVEs_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVMCVEs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -365,15 +365,15 @@ func request_VirtualMachineV2Service_GetVMDashboardCounts_0(ctx context.Context, protoReq VMDashboardCountsRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_GetVMDashboardCounts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVMDashboardCounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -401,9 +401,6 @@ func request_VirtualMachineV2Service_GetVMCVEDetail_0(ctx context.Context, marsh metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cve_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cve_id") @@ -418,6 +415,9 @@ func request_VirtualMachineV2Service_GetVMCVEDetail_0(ctx context.Context, marsh if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_GetVMCVEDetail_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVMCVEDetail(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -454,9 +454,6 @@ func request_VirtualMachineV2Service_ListVMCVEAffectedVMs_0(ctx context.Context, metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["cve_id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "cve_id") @@ -471,6 +468,9 @@ func request_VirtualMachineV2Service_ListVMCVEAffectedVMs_0(ctx context.Context, if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VirtualMachineV2Service_ListVMCVEAffectedVMs_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVMCVEAffectedVMs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/virtual_machine_v2_service.swagger.json b/generated/api/v2/virtual_machine_v2_service.swagger.json index 39b003a344de8..ec95ea8fc97a4 100644 --- a/generated/api/v2/virtual_machine_v2_service.swagger.json +++ b/generated/api/v2/virtual_machine_v2_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "VirtualMachineV2Service" + "name": "VirtualMachineV2Service", + "description": "NOTE: RPC ordering matters for grpc-gateway routing. The gateway prepends\neach handler (last registered = first matched), so wildcard routes like\n{id} must be defined FIRST to end up at the back of the matching list.\nLiteral routes defined after will match before the wildcard." } ], "consumes": [ diff --git a/generated/api/v2/vuln_exception_service.pb.gw.go b/generated/api/v2/vuln_exception_service.pb.gw.go index c2cf3ff4bdc5c..f2132de0f7ad9 100644 --- a/generated/api/v2/vuln_exception_service.pb.gw.go +++ b/generated/api/v2/vuln_exception_service.pb.gw.go @@ -41,9 +41,6 @@ func request_VulnerabilityExceptionService_GetVulnerabilityException_0(ctx conte metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -52,6 +49,9 @@ func request_VulnerabilityExceptionService_GetVulnerabilityException_0(ctx conte if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GetVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -81,15 +81,15 @@ func request_VulnerabilityExceptionService_ListVulnerabilityExceptions_0(ctx con protoReq RawQuery metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_VulnerabilityExceptionService_ListVulnerabilityExceptions_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ListVulnerabilityExceptions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -172,9 +172,6 @@ func request_VulnerabilityExceptionService_ApproveVulnerabilityException_0(ctx c if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -183,6 +180,9 @@ func request_VulnerabilityExceptionService_ApproveVulnerabilityException_0(ctx c if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.ApproveVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -217,9 +217,6 @@ func request_VulnerabilityExceptionService_DenyVulnerabilityException_0(ctx cont if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -228,6 +225,9 @@ func request_VulnerabilityExceptionService_DenyVulnerabilityException_0(ctx cont if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DenyVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -262,9 +262,6 @@ func request_VulnerabilityExceptionService_UpdateVulnerabilityException_0(ctx co if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -273,6 +270,9 @@ func request_VulnerabilityExceptionService_UpdateVulnerabilityException_0(ctx co if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.UpdateVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -304,9 +304,6 @@ func request_VulnerabilityExceptionService_CancelVulnerabilityException_0(ctx co metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -315,6 +312,9 @@ func request_VulnerabilityExceptionService_CancelVulnerabilityException_0(ctx co if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.CancelVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -343,9 +343,6 @@ func request_VulnerabilityExceptionService_DeleteVulnerabilityException_0(ctx co metadata runtime.ServerMetadata err error ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } val, ok := pathParams["id"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") @@ -354,6 +351,9 @@ func request_VulnerabilityExceptionService_DeleteVulnerabilityException_0(ctx co if err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.DeleteVulnerabilityException(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/api/v2/vuln_exception_service.swagger.json b/generated/api/v2/vuln_exception_service.swagger.json index 6bb1cd47996dc..348e5cb80392e 100644 --- a/generated/api/v2/vuln_exception_service.swagger.json +++ b/generated/api/v2/vuln_exception_service.swagger.json @@ -6,7 +6,8 @@ }, "tags": [ { - "name": "VulnerabilityExceptionService" + "name": "VulnerabilityExceptionService", + "description": "VulnerabilityExceptionService APIs can be used to manage create and manage vulnerability exceptions." } ], "consumes": [ diff --git a/generated/internalapi/central/development_service.pb.gw.go b/generated/internalapi/central/development_service.pb.gw.go index 1de6586c60742..21ba64121e2dd 100644 --- a/generated/internalapi/central/development_service.pb.gw.go +++ b/generated/internalapi/central/development_service.pb.gw.go @@ -69,15 +69,15 @@ func request_DevelopmentService_URLHasValidCert_0(ctx context.Context, marshaler protoReq URLHasValidCertRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DevelopmentService_URLHasValidCert_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.URLHasValidCert(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } @@ -104,15 +104,15 @@ func request_DevelopmentService_RandomData_0(ctx context.Context, marshaler runt protoReq RandomDataRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_DevelopmentService_RandomData_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.RandomData(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/generated/internalapi/central/v1/token_service.pb.gw.go b/generated/internalapi/central/v1/token_service.pb.gw.go index 7d726f38314dd..ee94c3884fed2 100644 --- a/generated/internalapi/central/v1/token_service.pb.gw.go +++ b/generated/internalapi/central/v1/token_service.pb.gw.go @@ -42,15 +42,15 @@ func request_TokenService_GenerateTokenForPermissionsAndScope_0(ctx context.Cont protoReq GenerateTokenForPermissionsAndScopeRequest metadata runtime.ServerMetadata ) - if req.Body != nil { - _, _ = io.Copy(io.Discard, req.Body) - } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_TokenService_GenerateTokenForPermissionsAndScope_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } msg, err := client.GenerateTokenForPermissionsAndScope(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } diff --git a/go.mod b/go.mod index c7e6f3cb34b02..2e2331e0d1e39 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.9.0 @@ -157,7 +157,7 @@ require ( golang.stackrox.io/grpc-http1 v0.5.1 google.golang.org/api v0.299.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d - google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 google.golang.org/grpc v1.84.0 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 @@ -283,29 +283,28 @@ require ( github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.26.0 // indirect - github.com/go-openapi/errors v0.22.8 // indirect - github.com/go-openapi/jsonpointer v1.0.0 // indirect - github.com/go-openapi/jsonreference v1.0.0 // indirect - github.com/go-openapi/loads v0.25.1 // indirect - github.com/go-openapi/runtime v0.33.0 // indirect - github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect - github.com/go-openapi/spec v0.22.9 // indirect - github.com/go-openapi/strfmt v0.27.0 // indirect - github.com/go-openapi/swag v0.26.1 // indirect - github.com/go-openapi/swag/cmdutils v0.27.0 // indirect - github.com/go-openapi/swag/conv v0.28.0 // indirect - github.com/go-openapi/swag/fileutils v0.28.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.1 // indirect - github.com/go-openapi/swag/jsonutils v0.28.0 // indirect - github.com/go-openapi/swag/loading v0.28.0 // indirect - github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/netutils v0.27.0 // indirect - github.com/go-openapi/swag/pools v0.28.0 // indirect - github.com/go-openapi/swag/stringutils v0.28.0 // indirect - github.com/go-openapi/swag/typeutils v0.28.0 // indirect - github.com/go-openapi/swag/yamlutils v0.28.0 // indirect - github.com/go-openapi/validate v0.26.3 // indirect + github.com/go-openapi/analysis v1.0.0 // indirect + github.com/go-openapi/errors v0.22.9 // indirect + github.com/go-openapi/jsonpointer v1.0.1 // indirect + github.com/go-openapi/jsonreference v1.0.1 // indirect + github.com/go-openapi/loads v0.25.2 // indirect + github.com/go-openapi/runtime v0.33.2 // indirect + github.com/go-openapi/runtime/server-middleware v0.33.2 // indirect + github.com/go-openapi/spec v1.0.0 // indirect + github.com/go-openapi/strfmt v0.27.2 // indirect + github.com/go-openapi/swag v0.29.2 // indirect + github.com/go-openapi/swag/cmdutils v0.29.2 // indirect + github.com/go-openapi/swag/conv v0.29.2 // indirect + github.com/go-openapi/swag/fileutils v0.29.2 // indirect + github.com/go-openapi/swag/jsonutils v0.29.2 // indirect + github.com/go-openapi/swag/loading v0.29.2 // indirect + github.com/go-openapi/swag/mangling v0.29.2 // indirect + github.com/go-openapi/swag/netutils v0.29.2 // indirect + github.com/go-openapi/swag/pools v0.29.2 // indirect + github.com/go-openapi/swag/stringutils v0.29.2 // indirect + github.com/go-openapi/swag/typeutils v0.29.2 // indirect + github.com/go-openapi/swag/yamlutils v0.29.2 // indirect + github.com/go-openapi/validate v1.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -379,7 +378,7 @@ require ( github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/np-guard/models v0.5.7 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect @@ -444,19 +443,19 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.45.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.46.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 74babc197c6a6..f130186feba73 100644 --- a/go.sum +++ b/go.sum @@ -523,69 +523,67 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.26.0 h1:1xECln1iMMmQnTjgcknC1vi1hA4KISt6IHpSwnqcuwI= -github.com/go-openapi/analysis v0.26.0/go.mod h1:40gERFi/2dyXA1FaqRRLxkv1IlC6X+GPDNd1xrYAjZE= -github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= -github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= +github.com/go-openapi/analysis v1.0.0 h1:sNvbAGCJqUTqIAodr9IVqJMmuZas3YS9ms1dGK9yiJ4= +github.com/go-openapi/analysis v1.0.0/go.mod h1:NhYjJ57fnE+bcE7UwrJyMkhWA3Dfz7TdiBfTVAnos4Y= +github.com/go-openapi/errors v0.22.9 h1:HI9+SyVYiRzyeBQGv0CbWbQa+u2S0nny7G7AhY+UMiM= +github.com/go-openapi/errors v0.22.9/go.mod h1:R73q66sXP2smzAGgFOI4nqtusRK2xN5pYCjsij+KX58= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= -github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= +github.com/go-openapi/jsonpointer v1.0.1/go.mod h1:wI7ZYsFmbIi9nBXOZqgDaS/bqOchRGZjqxFli7FBYxY= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= -github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/loads v0.25.1 h1:toKQdIDLxlqfKLLGUUmUsiTd5/X0Chzvde9EGYQP/Ac= -github.com/go-openapi/loads v0.25.1/go.mod h1:33Hen4tsKXHL45TyYojvfD5fZUFN4O1y4r/XhsRW2zc= -github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU= -github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw= -github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= -github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= -github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= -github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= -github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= -github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= +github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38= +github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A= +github.com/go-openapi/loads v0.25.2 h1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII= +github.com/go-openapi/loads v0.25.2/go.mod h1:RXsfJEQGGNv4uw8u/KdmF8Vh8OR4fTfs9ZO4QrzNohA= +github.com/go-openapi/runtime v0.33.2 h1:HSxskMs0WmpCdQvBWVxHt2t2mXMwn8DDav3VtVidwig= +github.com/go-openapi/runtime v0.33.2/go.mod h1:NQpSLiIsEAIpZDEs6xUrhjm6ZiQh85X8bHKlqLPgNP8= +github.com/go-openapi/runtime/server-middleware v0.33.2 h1:BVFjAaW4Jh/kZ4QSsgDrGzLuSuaKzSiyvappUFKxgUk= +github.com/go-openapi/runtime/server-middleware v0.33.2/go.mod h1:E3mWY61/UgBJ5EUmKh+h6A1oQQdD1U4raowVMmPmQug= +github.com/go-openapi/spec v1.0.0 h1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA= +github.com/go-openapi/spec v1.0.0/go.mod h1:boj1PRhqS0x5jylgcNp9BRWxenphrh7vVNYZ90IRCoo= +github.com/go-openapi/strfmt v0.27.2 h1:SG32SlbwNy92s0KJiVxt2joJeFdqIYHvwrA0OU6HqzQ= +github.com/go-openapi/strfmt v0.27.2/go.mod h1:M4CKsMO0Fb8qR10+1Ra75wCKNNquy+Vj+4LWZrhTo2E= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= -github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= -github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y= -github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= -github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= -github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= -github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= -github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= -github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= -github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= -github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= -github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= -github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8= -github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= -github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= -github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= -github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= -github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc= -github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8= +github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4= +github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo= +github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro= +github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw= +github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0= +github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q= +github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI= +github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0= +github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k= +github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2/go.mod h1:HC2egPORFzbj/gf05Zrfgz8Mgplx3oW7hgGi7pjZVhM= +github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJffsyJ2dVlY= +github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI= +github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog= +github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8= +github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8= +github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE= +github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c= +github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc= +github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk= +github.com/go-openapi/swag/stringutils v0.29.2/go.mod h1:i9cdh7sGaa0nm3CqIvH5IM5h2QClk1wns2ktKeILvRI= +github.com/go-openapi/swag/typeutils v0.29.2 h1:O7aVvkTs3pXwikgtrPLenigEMdQFgONKRooQA9pgf2I= +github.com/go-openapi/swag/typeutils v0.29.2/go.mod h1:7+GDG+uz9Ke+eVt4rClZg7wklSDp8hT/mKNh/pC26TE= +github.com/go-openapi/swag/yamlutils v0.29.2 h1:IFKFFeDnuIwzfsuWRQU+rI8iL3g4XyAYjV/RlnlxPLM= +github.com/go-openapi/swag/yamlutils v0.29.2/go.mod h1:7MGqtcrK73sxQ4ceiyIf8qiXS/s09JLMdR5esK5euYQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0 h1:wPW6YRgx3+SID1yUy/Xwa17L8kFEaEKod2VRbJDZNUs= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0/go.mod h1:mI1M88etYbc3PhgHsWQK2kwvNwW5aGFqMPbmib+SGIs= +github.com/go-openapi/testify/v2 v2.8.0 h1:19QDx5b57p8KjO2E4sgs/woL4Akp+FGLRlVm2xn0Rho= +github.com/go-openapi/testify/v2 v2.8.0/go.mod h1:4gUN8jC+RqOE2qjW/vDmfyTvl5BCht4qL7oca0odrak= +github.com/go-openapi/validate v1.0.0 h1:dFsYCLVUQUL6Vi2lQSexgwmCXDuHe7eWRDhQxkE+xYA= +github.com/go-openapi/validate v1.0.0/go.mod h1:wwXGRqMQzOZ7PCqBcgNk+DD9+Cacnxv7we5T0M/eA3Y= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -768,8 +766,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4/go.mod h1:0xydIeg2omQ9DH6 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 h1:Bd7KaOxzULLxtZ/K5s1aLbWhR0+5RToO65TXHsf3bqQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0/go.mod h1:nN7ts3dFXKtCZWc//yfkpcQNKJABg16/uDVAZpLDalo= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -1049,8 +1047,8 @@ github.com/np-guard/netpol-analyzer v1.4.4/go.mod h1:T31vBeTIBl1ksQr9NTW5GJBMHpm github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= -github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec= +github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= @@ -1460,12 +1458,12 @@ go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9d go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -1486,22 +1484,22 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/B go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 h1:KdRxPiAoMptR3vfWzvjjvutTsSiwbC2uG0496rzZNfo= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0/go.mod h1:K/qSA+3G7Eovxi4K09wzrAgkWRnosS0DAOZeEpve7sM= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= -go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU= +go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= @@ -1562,8 +1560,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -2036,8 +2034,8 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= -google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= -google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= From f1d90bc6a74a098d835361c3f8b8919298b14531 Mon Sep 17 00:00:00 2001 From: David Shrewsberry <99685630+dashrews78@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:55:54 -0400 Subject: [PATCH 465/638] ROX-36946: upgrade Postgres images for central-db (#22831) --- .github/docker/images.hcl | 2 +- image/postgres/Dockerfile | 2 +- image/postgres/download.sh | 3 +- image/postgres/konflux.Dockerfile | 4 +- image/postgres/scripts/docker-entrypoint.sh | 4 +- image/rhel/download.sh | 3 +- image/rhel/konflux.Dockerfile | 2 +- rpms.in.yaml | 2 +- rpms.lock.yaml | 248 +++++++++--------- .../CentralDatabaseHealthCard.cy.jsx | 4 +- .../CentralDatabaseHealthCard.tsx | 4 +- 11 files changed, 140 insertions(+), 138 deletions(-) diff --git a/.github/docker/images.hcl b/.github/docker/images.hcl index 4e9de4284681e..b4b6d47ac7760 100644 --- a/.github/docker/images.hcl +++ b/.github/docker/images.hcl @@ -28,7 +28,7 @@ group "databases" { targets = ["central-db", "scanner-v4-db"] } -# Central database (PostgreSQL 15) +# Central database (PostgreSQL 16) target "central-db" { context = "image/postgres" dockerfile = "Dockerfile" diff --git a/image/postgres/Dockerfile b/image/postgres/Dockerfile index edb3503720933..dd775fda126b7 100644 --- a/image/postgres/Dockerfile +++ b/image/postgres/Dockerfile @@ -1,4 +1,4 @@ -ARG PG_VERSION=15 +ARG PG_VERSION=16 FROM quay.io/sclorg/postgresql-${PG_VERSION}-c9s:latest AS final USER root diff --git a/image/postgres/download.sh b/image/postgres/download.sh index 624b992ee4867..977dfde78cf4f 100755 --- a/image/postgres/download.sh +++ b/image/postgres/download.sh @@ -2,7 +2,7 @@ set -euo pipefail -postgres_major=15 +postgres_major=16 pg_rhel_major=9 arch="$(uname -m)" @@ -16,6 +16,7 @@ output_dir="/rpms" mkdir $output_dir if [[ "$arch" == "s390x" ]]; then + dnf module enable -y postgresql:16 dnf install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs postgresql-server postgresql-contrib mv /tmp/postgresql-contrib-*.rpm "${output_dir}/postgres-contrib.rpm" mv /tmp/postgresql-server-*.rpm "${output_dir}/postgres-server.rpm" diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index e68bb68a7eb91..763e3bb197bd0 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ -ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:a3a5c31afcd91ef5d96f20bcf7aa7b47b10c034f86b549b04985ebed6d14567e AS final +ARG PG_VERSION=16 +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:106060b6e14c948aed9e33854b1a67d4089f35aa8a11e404da67156b1f8eb7e4 AS final USER root diff --git a/image/postgres/scripts/docker-entrypoint.sh b/image/postgres/scripts/docker-entrypoint.sh index 371b5dbd60021..6eafa1e5ac6bb 100755 --- a/image/postgres/scripts/docker-entrypoint.sh +++ b/image/postgres/scripts/docker-entrypoint.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# This file is from https://github.com/docker-library/postgres/tree/master/15/bookworm +# This file is from https://github.com/docker-library/postgres/tree/master/16/bookworm set -Eeo pipefail # TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) @@ -362,7 +362,7 @@ _main() { ### ROX_TLS_MIN_VERSION: passed to ssl_min_protocol_version (e.g. "TLSv1.2", "TLSv1.3"). ### ROX_TLS_OPENSSL_CIPHERS: colon-separated OpenSSL cipher names ### (e.g. "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384"). - ### Only affects TLS 1.2; TLS 1.3 ciphers are not configurable in PostgreSQL 15. + ### Only affects TLS 1.2; TLS 1.3 ciphers are not configurable in PostgreSQL 16. if [ -n "${ROX_TLS_MIN_VERSION:-}" ]; then set -- "$@" -c "ssl_min_protocol_version=${ROX_TLS_MIN_VERSION}" fi diff --git a/image/rhel/download.sh b/image/rhel/download.sh index 82570540b1b49..2cc7af11b156e 100755 --- a/image/rhel/download.sh +++ b/image/rhel/download.sh @@ -35,11 +35,12 @@ fi mkdir -p "$output_dir/rpms" if [[ "$arch" == "s390x" ]]; then + dnf module enable -y postgresql:16 dnf install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs mv /tmp/postgresql-private-libs-*.rpm "${output_dir}/rpms/postgres-libs.rpm" mv /tmp/postgresql-*.rpm "${output_dir}/rpms/postgres.rpm" else - postgres_major=15 + postgres_major=16 pg_rhel_major=9 postgres_repo_url="https://download.postgresql.org/pub/repos/yum/reporpms/EL-${pg_rhel_major}-${arch}/pgdg-redhat-repo-latest.noarch.rpm" dnf install --disablerepo='*' -y "${postgres_repo_url}" diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 7361053a6a0f8..5d4a2baf85c66 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -1,4 +1,4 @@ -ARG PG_VERSION=15 +ARG PG_VERSION=16 FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS go-builder diff --git a/rpms.in.yaml b/rpms.in.yaml index 8df6cca8fdb6a..ee1616dcd4c38 100644 --- a/rpms.in.yaml +++ b/rpms.in.yaml @@ -19,7 +19,7 @@ packages: - tar moduleEnable: # final stage in image/rhel/konflux.Dockerfile -- postgresql:15 +- postgresql:16 contentOrigin: repofiles: [ "rpms.rhel.repo" ] context: diff --git a/rpms.lock.yaml b/rpms.lock.yaml index eaaf8f5d53047..e139cae85bea8 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -18,13 +18,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 216292 - checksum: sha256:3fd73bd271e9c04dc297eb08c3829d276faee372f77666aad29f4b676ea220f4 + size: 218086 + checksum: sha256:d38712f7aa4e5b3821ba899d6b4f62821407f6f239e17bf240ca488fb1d35376 name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 31291354 @@ -137,20 +137,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1828909 - checksum: sha256:06bde44430fc94e09d7dac2c748f19b41161fdc2fa4b4b6cd861ca9a7a1532c0 + size: 2049624 + checksum: sha256:5295a9a347f24655946ca8870d76cc7acdaa96c1e631a6880193576e92061d27 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 150016 - checksum: sha256:869f22709281040978c3130c972685c217b4be0c4f915c69c3036b886d75bcc2 + size: 149710 + checksum: sha256:e4b9f9f73ee8986c64148b838c9a32492d75e4c6352810542952a8126eaa31ef name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 32440 @@ -361,13 +361,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1024204 - checksum: sha256:a4b7202ac90653a7d3e072c2444bde6a9270d6a818eb6f2ffcfcaa50774f1fad + size: 1026105 + checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 60311 @@ -1070,12 +1070,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 + evr: 16.15-1.module+el9.8.0+24803+9dca000b - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 20894742 @@ -1232,12 +1232,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 81978017 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/f310ddcbea4a32af8415a78c2ccf7b7eb34ebf5fe763be2bfe0ae728654c1449-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 68829 - checksum: sha256:80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc + size: 68974 + checksum: sha256:f310ddcbea4a32af8415a78c2ccf7b7eb34ebf5fe763be2bfe0ae728654c1449 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1655,13 +1655,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 216324 - checksum: sha256:f44a17730803f53266874678031b8121541b5b1a8047a3205c2576630216f468 + size: 218118 + checksum: sha256:c12a101cc4a7fd08f4fa1a430629fe45fa7fd56e59d5f4094aaa159c9a0aabea name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gcc-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 29072214 @@ -1774,20 +1774,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1893060 - checksum: sha256:cb037d48821efe06c06c45369e4a4fd9ca9f048022bd62bf7373028b9c8c74d9 + size: 2115888 + checksum: sha256:a5f63dd209db8b709ebda515c4a71c746360e2438ed54a448920a84b431ab3d3 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 170264 - checksum: sha256:64a378b9cb22b6228f0b1a5a1b2b3b50b959e7d8d093953f5c09058c8e2167f3 + size: 171976 + checksum: sha256:96366eb8e165d019f05d42c0b5fb801e658377f65b1d92bae29a8e9d73722725 name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 32494 @@ -1998,13 +1998,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gawk-5.1.0-6.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 1064251 - checksum: sha256:b0cc389ad0855900c79f2cfa525df8cbc663093056b0b5d29ec3e36a189b325e + size: 1062624 + checksum: sha256:4c2d81c6874201646d5c5eee719ecdb95f610b277b3782bb1956a19f71d17d59 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 65838 @@ -2714,12 +2714,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 + evr: 16.15-1.module+el9.8.0+24803+9dca000b - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 20894742 @@ -2876,12 +2876,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 81978017 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/a2ba371afa8702da112fc35262a64188c67b41c55df301eb20b44e2dff3a6b7d-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 70701 - checksum: sha256:008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7 + size: 70407 + checksum: sha256:a2ba371afa8702da112fc35262a64188c67b41c55df301eb20b44e2dff3a6b7d - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3298,13 +3298,13 @@ arches: name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 216312 - checksum: sha256:fc71db041e9b749a3f8bcbc9f812509625cfaf5d4fa83d37324c4dbdeb00a36e + size: 218106 + checksum: sha256:4828be3b34d4f1e54ec4b0ff53c1291f18b01be78165e10ec4967b6b061e13d4 name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gcc-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 26825105 @@ -3424,20 +3424,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1861033 - checksum: sha256:a3aa3b5a24a81f1cb8a36a0eecba4f0eb00a54cbb5840d101533cf4793d9c67e + size: 2085027 + checksum: sha256:7c047295d9ae473c18fbbeb9b0ce4a432ae6a75f1a8378d857809cbad2596214 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 147716 - checksum: sha256:738dfc0971a4ec61b9092d9598809c5cb082c6aff8db768507d9c0aaa5ae35ea + size: 150732 + checksum: sha256:fa72693328f1f6fa5958912ca1d048aec07563e4bc7f013ccb1f4050805a8703 name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 32298 @@ -3648,13 +3648,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gawk-5.1.0-6.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1029188 - checksum: sha256:d34fd3f586240f43f71bc74824ae513cba2e4a6812f0ebbd101122e7e99bafe8 + size: 1029963 + checksum: sha256:7382392d06b5e21161966cec0c187bba6afb9710047ab17d79e7b55ce43def40 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 60437 @@ -4351,12 +4351,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 + evr: 16.15-1.module+el9.8.0+24803+9dca000b - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 20894742 @@ -4513,12 +4513,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 81978017 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/532ca027fab74fd8ae42adae8ec472c1ddf8940c704f3ad3d36114c91c0d210b-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 65924 - checksum: sha256:dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7 + size: 65587 + checksum: sha256:532ca027fab74fd8ae42adae8ec472c1ddf8940c704f3ad3d36114c91c0d210b - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4936,13 +4936,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 216340 - checksum: sha256:c1fcc71c1cc1160d58ace4b60cc6733b68d6f6d406e5ec5ce24327787f452cd1 + size: 218134 + checksum: sha256:bedaac1d71ca9f41e3398dc413dea2008bfd20799de1b4db14e37ebb7a7ce74d name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 33982584 @@ -5048,20 +5048,20 @@ arches: name: openssl-devel evr: 1:3.5.8-1.el9_8 sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1842434 - checksum: sha256:18506bf4217ef40600adcb830fe651840f041423ef3bea67bd73d6f8c78e383c + size: 2067493 + checksum: sha256:f078cf1183f6bb750e39cdcab4d2ecb3f936d745b7360b741a6919e926855ad7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 154163 - checksum: sha256:18d4936ef18627f03facdf1b8f54e313d04f67883d0019abee9a599b7e3e621b + size: 153488 + checksum: sha256:f36cf284eb08753b6b2576f8eed63c8c2651ef82e618fe1641a6b0ff4cd2d4ca name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 32484 @@ -5272,13 +5272,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 1045534 - checksum: sha256:99fda6725a2c668bae29fbab74d1b347e074f4e8c8ed18d656cb928fb6fc92b7 + size: 1046649 + checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 60152 @@ -5974,12 +5974,12 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 + evr: 16.15-1.module+el9.8.0+24803+9dca000b - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 20894742 @@ -6136,12 +6136,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 81978017 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/d4f5a831afd58619b36a4842a572502fc730e04eefb3e8acca4679a59be2eeff-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 72089 - checksum: sha256:c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512 + size: 72271 + checksum: sha256:d4f5a831afd58619b36a4842a572502fc730e04eefb3e8acca4679a59be2eeff diff --git a/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.cy.jsx b/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.cy.jsx index f609bbeba28a9..15d4a0ec3f2cf 100644 --- a/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.cy.jsx +++ b/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.cy.jsx @@ -5,7 +5,7 @@ function patchDatabaseStatusResponse(overrides = {}) { req.reply({ databaseAvailable: true, databaseType: 'PostgresDB', - databaseVersion: '15.0', + databaseVersion: '16.0', databaseIsExternal: false, ...overrides, }); @@ -20,7 +20,7 @@ describe(Cypress.spec.relative, () => { patchDatabaseStatusResponse(); cy.mount(); cy.get(`${cardHeader}:contains("no errors")`); - cy.get(`${cardHeader}:contains("PostgresDB 15.0")`); + cy.get(`${cardHeader}:contains("PostgresDB 16.0")`); }); it('should not show a warning if the database is external', () => { diff --git a/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.tsx b/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.tsx index 83430c2e5b54e..66376891a1f6c 100644 --- a/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.tsx +++ b/ui/apps/platform/src/Containers/SystemHealth/CentralDatabaseHealth/CentralDatabaseHealthCard.tsx @@ -20,7 +20,7 @@ function isDatabaseVersionSupported(databaseVersion: string | undefined): boolea if (!databaseVersion) { return true; } - return databaseVersion.startsWith('15'); + return databaseVersion.startsWith('16'); } function getDatabaseHealthInfo(data: DatabaseStatus | undefined): { @@ -42,7 +42,7 @@ function getDatabaseHealthInfo(data: DatabaseStatus | undefined): { messages: [ 'Running an unsupported configuration of PostgreSQL', `Current version: ${data.databaseVersion}`, - `Required version: 15`, + `Required version: 16`, ], }; } From d4b416393f52e7a61f0f861452d9a95c5a55a751 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 02:47:51 +0000 Subject: [PATCH 466/638] chore(deps): update all dependencies (#23176) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/create-custom-snapshot.yaml | 2 +- .tekton/operator-index-build.yaml | 2 +- image/rhel/konflux.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index d82ad2a8b5bbb..6fc004f6e642f 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 + image: registry.redhat.io/ubi9-minimal:latest@sha256:1d7c5517a4a1a8e2688620b39ee980e82505ca1ab7ae5541b5463120ae9b3897 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index a3c1aa9ceff8f..55794f741a56c 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:1d7c5517a4a1a8e2688620b39ee980e82505ca1ab7ae5541b5463120ae9b3897 imagePullPolicy: IfNotPresent script: | #!/bin/bash diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 5d4a2baf85c66..8de4fe0903b4c 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -49,7 +49,7 @@ RUN arch=$(uname -m) ; \ /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:ef4f18a7e736696b1314bdc21bcf57bfd50ba3ea4f80c13833f777316b25c035 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:99cef5d0a64011463c411db508b1570f9edc37219dd38eabbf90bb8c0e45ccc3 as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app From e838d97294d13a50ab7dd77db6c042897adb848a Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 1 Oct 2026 07:54:35 +0200 Subject: [PATCH 467/638] ROX-36578: use roxie in BOYDB tests (#22667) Co-authored-by: Vlad Bologa --- .github/workflows/e2e-byodb-tests.yaml | 10 ++++++ qa-tests-backend/scripts/run-part-1.sh | 2 ++ .../jobs/gke_external_pg_17_qa_e2e_tests.py | 1 + tests/e2e/lib-compat.sh | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index ea298e07a8dba..ede1805fa1ecd 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -98,6 +98,15 @@ jobs: - name: Create artifact directory run: mkdir -p "$ARTIFACT_DIR" + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + - name: Run pre-test shell: python env: @@ -141,6 +150,7 @@ jobs: CLOUD_SOURCES_TEST_OCM_CLIENT_ID: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_ID }} CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET }} BUILD_ID: ${{ github.run_id }} + USE_ROXIE_DEPLOY: true timeout-minutes: 245 run: tests/byodb/run.sh /tmp/byodb-test-logs diff --git a/qa-tests-backend/scripts/run-part-1.sh b/qa-tests-backend/scripts/run-part-1.sh index ef446d76f608a..90a8bf9d3ec3e 100755 --- a/qa-tests-backend/scripts/run-part-1.sh +++ b/qa-tests-backend/scripts/run-part-1.sh @@ -58,6 +58,8 @@ config_part_1() { if [[ "$use_roxie_deploy" == "true" ]]; then info "Using roxie-based config_part_1 for qa-tests-backend" + ensure_roxie_on_path + local config_file config_file="$(mktemp)" diff --git a/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py b/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py index 2c652d5e83a9a..787be524961f8 100755 --- a/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py +++ b/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py @@ -15,6 +15,7 @@ os.environ["KUBERNETES_PROVIDER"] = "gke" os.environ["POSTGRES_VERSION"] = "17" os.environ["BYODB_TEST"] = "true" +os.environ["USE_ROXIE_DEPLOY"] = "true" os.environ["ROX_RISK_REPROCESSING_INTERVAL"] = "15s" os.environ["ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL"] = "30s" diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index 2b85aa4392e4a..0a13226f860c5 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -96,6 +96,11 @@ roxie_config_from_environment_compat() { handle_endpoints_for_test "$config_file" # Echoes info internally. + if [[ -n "${EXTERNAL_DB:-}" ]]; then + info "Configuring external database..." + handle_external_database_settings "$config_file" "$namespace" + fi + info "Configuring custom central environment..." while read -r var_val; do local name="${var_val%%=*}" @@ -197,6 +202,32 @@ roxie_config_from_environment_compat() { handle_virtual_machines_configuration "$config_file" } +handle_external_database_settings() { + local config_file="$1" + local namespace="$2" + + merge_yaml "$config_file" < Date: Thu, 1 Oct 2026 04:15:35 -0400 Subject: [PATCH 468/638] fix(build): s390x builds (#23180) --- image/rhel/Dockerfile | 34 ++++++++++++++++++++++++++++++---- image/rhel/README.md | 11 +++++++++++ image/rhel/download.sh | 6 +++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/image/rhel/Dockerfile b/image/rhel/Dockerfile index 73ebed2028f8d..b0658e3886547 100644 --- a/image/rhel/Dockerfile +++ b/image/rhel/Dockerfile @@ -1,9 +1,20 @@ +ARG TARGETARCH +ARG PG_VERSION=16 + FROM registry.access.redhat.com/ubi9:latest AS ubi-base FROM registry.access.redhat.com/ubi9/ubi-micro:latest AS ubi-micro-base -FROM ubi-base AS downloads +# PGDG does not publish s390x RPMs, and UBI only provides PostgreSQL 13. +# Use CentOS Stream's PostgreSQL module for that architecture's download stage. +FROM ubi-base AS downloads-amd64 +FROM ubi-base AS downloads-arm64 +FROM ubi-base AS downloads-ppc64le +FROM quay.io/centos/centos:stream9 AS downloads-s390x + +FROM downloads-${TARGETARCH} AS downloads ARG DEBUG_BUILD=no +ARG PG_VERSION WORKDIR / COPY download.sh /download.sh @@ -39,6 +50,23 @@ RUN rpm --import /tmp/RPM-GPG-KEY-CentOS-Official && \ dnf clean all --installroot=/out/ && \ rm -rf /out/var/cache/dnf /out/var/cache/yum +FROM ubi-micro-base AS runtime-base + +ARG PG_VERSION + +COPY --from=package_installer /out/ / + +# Execute the clients in the final runtime to check their version and libraries. +RUN set -eu; \ + for client in pg_dump pg_restore; do \ + version="$("$client" --version)"; \ + echo "$version"; \ + case "$version" in \ + "$client (PostgreSQL) ${PG_VERSION}."*) ;; \ + *) echo "Expected PostgreSQL ${PG_VERSION} client: $client" >&2; exit 1 ;; \ + esac; \ + done + FROM ubi-base AS cli-tarballs RUN dnf install -y pigz @@ -50,7 +78,7 @@ RUN cd /tmp/roxctl && \ tar -I pigz -cvf "${f%.exe}.tar.gz" "$f"; \ done -FROM ubi-micro-base +FROM runtime-base ARG LABEL_VERSION ARG LABEL_RELEASE @@ -73,8 +101,6 @@ ENV PATH="/stackrox:$PATH" \ ROX_IMAGE_FLAVOR=${ROX_IMAGE_FLAVOR} \ ROX_PRODUCT_BRANDING=${ROX_PRODUCT_BRANDING} -COPY --from=package_installer /out/ / - COPY static-bin /stackrox/ COPY --from=downloads /output/go/ /go/ diff --git a/image/rhel/README.md b/image/rhel/README.md index c3283e8a714f3..8eff97ca75f4b 100644 --- a/image/rhel/README.md +++ b/image/rhel/README.md @@ -3,3 +3,14 @@ The RedHat based main image is currently used for the RedHat marketplace as well as for DoD customers. This image is built in opinionated way based on the DoD Centralized Artifacts Repository (DCAR) requirements outlined [here](https://dccscr.dsop.io/dsop/dccscr/tree/master/contributor-onboarding) + +## PostgreSQL clients + +`Dockerfile` downloads PostgreSQL client RPMs from PGDG for amd64, arm64, and +ppc64le. For s390x it uses a CentOS Stream 9 download stage because PGDG does not +publish s390x packages and UBI's repositories do not provide the PostgreSQL 16 +module. The runtime remains UBI on every architecture. + +`PG_VERSION` selects the client major version. The `runtime-base` stage runs +`pg_dump` and `pg_restore` and checks their versions in the runtime filesystem, +so missing libraries or an older client fail the image build. diff --git a/image/rhel/download.sh b/image/rhel/download.sh index 2cc7af11b156e..c706129851a0a 100755 --- a/image/rhel/download.sh +++ b/image/rhel/download.sh @@ -3,6 +3,7 @@ set -euo pipefail arch=$(uname -m) +postgres_major="${PG_VERSION:-16}" goarch="$arch" dnf_list_args=() if [[ "$arch" == "x86_64" ]]; then @@ -35,16 +36,15 @@ fi mkdir -p "$output_dir/rpms" if [[ "$arch" == "s390x" ]]; then - dnf module enable -y postgresql:16 + dnf module enable -y "postgresql:${postgres_major}" dnf install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs mv /tmp/postgresql-private-libs-*.rpm "${output_dir}/rpms/postgres-libs.rpm" mv /tmp/postgresql-*.rpm "${output_dir}/rpms/postgres.rpm" else - postgres_major=16 pg_rhel_major=9 postgres_repo_url="https://download.postgresql.org/pub/repos/yum/reporpms/EL-${pg_rhel_major}-${arch}/pgdg-redhat-repo-latest.noarch.rpm" dnf install --disablerepo='*' -y "${postgres_repo_url}" - postgres_minor=$(dnf list ${dnf_list_args[@]+"${dnf_list_args[@]}"} --disablerepo='*' ""--enablerepo=pgdg${postgres_major} -y postgresql${postgres_major}-server."$arch" | tail -n 1 | awk '{print $2}') + postgres_minor=$(dnf list ${dnf_list_args[@]+"${dnf_list_args[@]}"} --disablerepo='*' "--enablerepo=pgdg${postgres_major}" -y "postgresql${postgres_major}-server.${arch}" | tail -n 1 | awk '{print $2}') postgres_minor="$postgres_minor.$arch" postgres_url="https://download.postgresql.org/pub/repos/yum/${postgres_major}/redhat/rhel-${pg_rhel_major}-${arch}" From 06c357c1cfaaffc4e5c1b1155df4bd090e01bf7d Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:26:18 +0200 Subject: [PATCH 469/638] chore(linters): fixes for reflecttypeassert slicesclip and nolintlint (#22781) --- .../evaluator/base_evaluators.go | 4 +- pkg/concurrency/value_stream.go | 2 +- pkg/postgres/walker/walker.go | 2 +- pkg/protocompat/oneof.go | 2 +- pkg/protoconv/resources/resources.go | 6 +- pkg/protoreflect/field.go | 2 +- pkg/search/predicate/base_predicates.go | 2 +- pkg/search/predicate/timestamp_predicate.go | 2 +- pkg/search/walker.go | 2 +- pkg/telemetry/data/data_test.go | 2 +- roxctl/central/generate/interactive.go | 2 - .../kubernetes/listener/resources/convert.go | 4 +- tools/linters/go.mod | 107 ++++---- tools/linters/go.sum | 232 +++++++++--------- tools/roxvet/common/traverse.go | 5 +- 15 files changed, 189 insertions(+), 187 deletions(-) diff --git a/pkg/booleanpolicy/evaluator/base_evaluators.go b/pkg/booleanpolicy/evaluator/base_evaluators.go index 78fec49431158..d187c64eb5a64 100644 --- a/pkg/booleanpolicy/evaluator/base_evaluators.go +++ b/pkg/booleanpolicy/evaluator/base_evaluators.go @@ -252,7 +252,7 @@ func generateTimestampMatcher(value string, matchAll bool) (baseMatcherAndExtrac } } return func(instance reflect.Value) []valueMatchedPair { - ts, ok := instance.Interface().(*protocompat.Timestamp) + ts, ok := reflect.TypeAssert[*protocompat.Timestamp](instance) if !ok { return nil } @@ -335,7 +335,7 @@ func generateBoolMatcher(value string, _ reflect.Type, matchAll bool) (baseMatch } func generateIntMatcher(value string, fieldType reflect.Type, matchAll bool) (baseMatcherAndExtractor, error) { - if enum, ok := reflect.Zero(fieldType).Interface().(protoreflect.ProtoEnum); ok { + if enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(fieldType)); ok { return generateEnumMatcher(value, enum, matchAll) } if matchAll && value != "" { diff --git a/pkg/concurrency/value_stream.go b/pkg/concurrency/value_stream.go index 354be592cb9d3..c106a7d9faafb 100644 --- a/pkg/concurrency/value_stream.go +++ b/pkg/concurrency/value_stream.go @@ -101,7 +101,7 @@ func (i *valueStreamIterBase[T]) Done() <-chan struct{} { return i.nextC } -func (*valueStreamIterBase[T]) isValueStreamIter(T) {} //nolint:unused // This is required for generic magic +func (*valueStreamIterBase[T]) isValueStreamIter(T) {} type valueStreamStrictIter[T any] struct { valueStreamIterBase[T] diff --git a/pkg/postgres/walker/walker.go b/pkg/postgres/walker/walker.go index fcfe5a6e82e1e..fb79783ca2119 100644 --- a/pkg/postgres/walker/walker.go +++ b/pkg/postgres/walker/walker.go @@ -357,7 +357,7 @@ func tableName(parent, child string) string { } func typeIsEnum(typ reflect.Type) bool { - enum, ok := reflect.Zero(typ).Interface().(protoreflect.ProtoEnum) + enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(typ)) if !ok { return false } diff --git a/pkg/protocompat/oneof.go b/pkg/protocompat/oneof.go index 96d043e7249c5..aa148cd07926d 100644 --- a/pkg/protocompat/oneof.go +++ b/pkg/protocompat/oneof.go @@ -25,7 +25,7 @@ func GetOneOfTypesByInterface(msgType reflect.Type, oneOfInterfaceType reflect.T oneOfFieldTypes := make([]reflect.Type, 0) // Get proto message from Go reflect type. - msg, ok := reflect.New(msgType).Interface().(proto.Message) + msg, ok := reflect.TypeAssert[proto.Message](reflect.New(msgType)) if !ok { return oneOfFieldTypes } diff --git a/pkg/protoconv/resources/resources.go b/pkg/protoconv/resources/resources.go index e95a3a12736b7..e03afc7e1a050 100644 --- a/pkg/protoconv/resources/resources.go +++ b/pkg/protoconv/resources/resources.go @@ -106,7 +106,7 @@ func SpecToPodTemplateSpec(spec reflect.Value) (v1.PodTemplateSpec, error) { if templateInterface.Type().Kind() == reflect.Pointer && !templateInterface.IsNil() { templateInterface = templateInterface.Elem() } - podTemplate, ok := templateInterface.Interface().(v1.PodTemplateSpec) + podTemplate, ok := reflect.TypeAssert[v1.PodTemplateSpec](templateInterface) if !ok { return v1.PodTemplateSpec{}, errors.New("not a valid PodTemplateSpec") } @@ -356,12 +356,12 @@ func (w *DeploymentWrap) populateReplicas(spec reflect.Value, obj interface{}) { return } - replicasPointer, ok := replicaField.Interface().(*int32) + replicasPointer, ok := reflect.TypeAssert[*int32](replicaField) if ok && replicasPointer != nil { w.Deployment.Replicas = int64(*replicasPointer) } - replicas, ok := replicaField.Interface().(int32) + replicas, ok := reflect.TypeAssert[int32](replicaField) if ok { w.Deployment.Replicas = int64(replicas) } diff --git a/pkg/protoreflect/field.go b/pkg/protoreflect/field.go index c7a97b95069f1..f06ae1d1ed51e 100644 --- a/pkg/protoreflect/field.go +++ b/pkg/protoreflect/field.go @@ -7,7 +7,7 @@ import ( ) func IsProtoMessage(msgType reflect.Type) bool { - _, ok := reflect.New(msgType).Interface().(protocompat.Message) + _, ok := reflect.TypeAssert[protocompat.Message](reflect.New(msgType)) return ok } diff --git a/pkg/search/predicate/base_predicates.go b/pkg/search/predicate/base_predicates.go index 1497c46ed3935..556695794f25e 100644 --- a/pkg/search/predicate/base_predicates.go +++ b/pkg/search/predicate/base_predicates.go @@ -38,7 +38,7 @@ func createBasePredicate(fullPath string, fieldType reflect.Type, value string) case reflect.Bool: return createBoolPredicate(fullPath, value) case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: - if enum, ok := reflect.Zero(fieldType).Interface().(protoreflect.ProtoEnum); ok { + if enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(fieldType)); ok { return createEnumPredicate(fullPath, value, enum) } return createIntPredicate(fullPath, value) diff --git a/pkg/search/predicate/timestamp_predicate.go b/pkg/search/predicate/timestamp_predicate.go index 2dd98eb589f75..18aea5f5e070e 100644 --- a/pkg/search/predicate/timestamp_predicate.go +++ b/pkg/search/predicate/timestamp_predicate.go @@ -14,7 +14,7 @@ func createTimestampPredicate(fullPath, value string) (internalPredicate, error) return nil, err } return internalPredicateFunc(func(instance reflect.Value) (*search.Result, bool) { - instanceTS, ok := instance.Interface().(*protocompat.Timestamp) + instanceTS, ok := reflect.TypeAssert[*protocompat.Timestamp](instance) if ok && baseMatcher(instanceTS) { return &search.Result{ diff --git a/pkg/search/walker.go b/pkg/search/walker.go index 2a4a3df0e3571..0e6262a8617a5 100644 --- a/pkg/search/walker.go +++ b/pkg/search/walker.go @@ -137,7 +137,7 @@ func (s *searchWalker) walkRecursive(prefix string, original reflect.Type) v1.Se case reflect.Bool: return v1.SearchDataType_SEARCH_BOOL case reflect.Uint32, reflect.Uint64, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: - enum, ok := reflect.Zero(original).Interface().(protoreflect.ProtoEnum) + enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(original)) if !ok { return v1.SearchDataType_SEARCH_NUMERIC } diff --git a/pkg/telemetry/data/data_test.go b/pkg/telemetry/data/data_test.go index 1f9b94d47d909..2f8850001fe5b 100644 --- a/pkg/telemetry/data/data_test.go +++ b/pkg/telemetry/data/data_test.go @@ -26,7 +26,7 @@ func validateJSONFieldNames(t *testing.T, val reflect.Value) { validateJSONFieldNames(t, val.Index(i)) } case reflect.Map: - m, ok := val.Interface().(map[string]interface{}) + m, ok := reflect.TypeAssert[map[string]interface{}](val) assert.True(t, ok) for mKey, mVal := range m { validateFieldName(t, mKey) diff --git a/roxctl/central/generate/interactive.go b/roxctl/central/generate/interactive.go index fe544dca69b66..e5df581fb6f4a 100644 --- a/roxctl/central/generate/interactive.go +++ b/roxctl/central/generate/interactive.go @@ -352,11 +352,9 @@ func processFlagWraps(argSlice *argSlice, fws []flagWrap) { depUnmet := false for _, dep := range fw.Annotations[flags.DependenciesKey] { flag := flagsByName[dep] - //nolint:staticcheck // SA5011 flag is definitely not nil because utils.Must panics. if flag == nil { utils.CrashOnError(errors.Errorf("invalid flag dependency %q", dep)) } - //nolint:staticcheck // SA5011 flag is definitely not nil because utils.Must panics. if !argSlice.flagNameIsSetExplicitly(flag.Name) { depUnmet = true break diff --git a/sensor/kubernetes/listener/resources/convert.go b/sensor/kubernetes/listener/resources/convert.go index 79650f98be89c..7329c81b51fb4 100644 --- a/sensor/kubernetes/listener/resources/convert.go +++ b/sensor/kubernetes/listener/resources/convert.go @@ -476,14 +476,14 @@ func (w *deploymentWrap) getLabelSelector(spec reflect.Value) (*metav1.LabelSele } // Selector is of map type for replication controller - if labelMap, ok := s.Interface().(map[string]string); ok { + if labelMap, ok := reflect.TypeAssert[map[string]string](s); ok { return &metav1.LabelSelector{ MatchLabels: labelMap, }, nil } // All other resources uses labelSelector. - if ls, ok := s.Interface().(*metav1.LabelSelector); ok { + if ls, ok := reflect.TypeAssert[*metav1.LabelSelector](s); ok { return ls, nil } diff --git a/tools/linters/go.mod b/tools/linters/go.mod index ab424a14e39e4..3c6174baa1a78 100644 --- a/tools/linters/go.mod +++ b/tools/linters/go.mod @@ -3,35 +3,36 @@ module github.com/stackrox/stackrox/tools/linters go 1.26.3 require ( - github.com/golangci/golangci-lint/v2 v2.12.2 + github.com/golangci/golangci-lint/v2 v2.13.2 github.com/nilslice/protolock v0.17.0 github.com/rhysd/actionlint v1.7.12 github.com/sethvargo/ratchet v0.12.0 - golang.org/x/vuln v1.6.0 + golang.org/x/vuln v1.8.0 ) require ( - 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gocheckcompilerdirectives v1.4.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - charm.land/lipgloss/v2 v2.0.3 // indirect + charm.land/lipgloss/v2 v2.0.6 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/golib v0.6.0 // indirect + dev.gaijin.team/go/exhaustruct/v5 v5.0.3 // indirect + dev.gaijin.team/go/golib v0.8.1 // indirect github.com/4meepo/tagalign v1.4.3 // indirect - github.com/Abirdcfly/dupword v0.1.7 // indirect + github.com/Abirdcfly/dupword v0.1.8 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect - github.com/AlwxSin/noinlineerr v1.0.5 // indirect - github.com/Antonboom/errname v1.1.1 // indirect - github.com/Antonboom/nilnil v1.1.1 // indirect + github.com/AlwxSin/noinlineerr v1.0.6 // indirect + github.com/Antonboom/errname v1.1.2 // indirect + github.com/Antonboom/nilnil v1.1.2 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.6.0 // indirect - github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect + github.com/ClickHouse/clickhouse-go-linter v1.2.1 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.24.1 // indirect + github.com/alecthomas/chroma/v2 v2.27.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.1.0 // indirect @@ -45,19 +46,19 @@ require ( github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.8.0 // indirect + github.com/bombsimon/wsl/v5 v5.9.0 // indirect github.com/braydonk/yaml v0.9.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/butuzov/ireturn v0.4.1 // indirect - github.com/butuzov/mirror v1.3.0 // indirect + github.com/butuzov/mirror v1.3.3 // indirect github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect - github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect + github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect @@ -67,20 +68,19 @@ require ( github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/dlclark/regexp2 v1.12.0 // indirect + github.com/dlclark/regexp2/v2 v2.2.1 // indirect github.com/docker/cli v29.6.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/emicklei/proto v1.9.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.19.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.6 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/firefart/nonamedreturns v1.0.8 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.20 // indirect - github.com/go-critic/go-critic v0.14.3 // indirect + github.com/ghostiam/protogetter v0.3.21 // indirect + github.com/go-critic/go-critic v0.14.4 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -95,9 +95,10 @@ require ( github.com/gofrs/flock v0.13.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/asciicheck v0.5.0 // indirect + github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 // indirect github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect - github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 // indirect github.com/golangci/golines v0.15.0 // indirect github.com/golangci/misspell v0.8.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect @@ -120,24 +121,23 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.10.0 // indirect + github.com/jgautheron/goconst v1.11.0 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect - github.com/kisielk/errcheck v1.10.0 // indirect + github.com/kisielk/errcheck v1.20.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/klauspost/compress v1.19.0 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect - github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/exptostd v0.4.5 // indirect - github.com/ldez/gomoddirectives v0.8.0 // indirect + github.com/ldez/gomoddirectives v0.9.0 // indirect github.com/ldez/grignotin v0.10.1 // indirect github.com/ldez/structtags v0.6.1 // indirect github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect @@ -145,9 +145,9 @@ require ( github.com/maratori/testableexamples v1.0.1 // indirect github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-runewidth v0.0.24 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.15.0 // indirect @@ -158,12 +158,11 @@ require ( github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.23.0 // indirect + github.com/nunnatsa/ginkgolinter v0.24.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -173,19 +172,19 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/raeperd/recvcheck v0.3.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rogpeppe/go-internal v1.16.0 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.26.1 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect + github.com/securego/gosec/v2 v2.28.0 // indirect + github.com/sirupsen/logrus v1.10.1 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.5.1 // indirect github.com/sourcegraph/go-diff v0.8.0 // indirect @@ -197,8 +196,8 @@ require ( github.com/spf13/viper v1.13.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.11.1 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/stretchr/testify v1.12.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.6 // indirect github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect @@ -208,7 +207,7 @@ require ( github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.1 // indirect - github.com/uudashr/iface v1.4.2 // indirect + github.com/uudashr/iface v1.5.1 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect @@ -218,24 +217,24 @@ require ( go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.12.0 // indirect go.augendre.info/arangolint v0.4.0 // indirect - go.augendre.info/fatcontext v0.9.0 // indirect + go.augendre.info/fatcontext v0.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect - golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/mod v0.38.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect + golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.22.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.48.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/tools v0.50.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.7.0 // indirect - mvdan.cc/gofumpt v0.9.2 // indirect - mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect + honnef.co/go/tools v0.8.1 // indirect + mvdan.cc/gofumpt v0.11.0 // indirect + mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 // indirect ) diff --git a/tools/linters/go.sum b/tools/linters/go.sum index cee066806ecae..1efaa36ce124b 100644 --- a/tools/linters/go.sum +++ b/tools/linters/go.sum @@ -1,9 +1,9 @@ -4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= -4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gocheckcompilerdirectives v1.4.0 h1:ZLq62rbGWVmQhiZ8kuNVIT/M09xCSTdJz9K3xOdT/CY= +4d63.com/gocheckcompilerdirectives v1.4.0/go.mod h1:9ZOAiMOjqC/nRwci2fcUXVHUNLG/cH6r6rhUh+jTFtQ= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= -charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= +charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ= +charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -42,29 +42,31 @@ codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= -dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= +dev.gaijin.team/go/exhaustruct/v5 v5.0.3 h1:yOeA7DNjlT8y4yfmN6nWWYYggA13N523YAj9/TXbuTM= +dev.gaijin.team/go/exhaustruct/v5 v5.0.3/go.mod h1:KwtBsX8nHHH1YxhxkpiBq6bfsmw5WnazWpNvJPHgY9Y= +dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E= +dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= -github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= -github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= +github.com/Abirdcfly/dupword v0.1.8 h1:SrhcUuGsROBuChFxHALRYzyyPODWn9zwghmzPvD9Cd8= +github.com/Abirdcfly/dupword v0.1.8/go.mod h1:XZrhVnI7YGpsTiWZANSQaBJ4QpL/Tq5vIEdKJJAs9WI= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= -github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= -github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= -github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= -github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= -github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= -github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= +github.com/AlwxSin/noinlineerr v1.0.6 h1:KAvuxunTe9QxvqrFB7nZTdb/7Wzas4AvifslTnG0Ld8= +github.com/AlwxSin/noinlineerr v1.0.6/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= +github.com/Antonboom/errname v1.1.2 h1:dxwONZJua3VB8Xh/VaCjqAcqF645sWWv7xj26zy7tdQ= +github.com/Antonboom/errname v1.1.2/go.mod h1:YeZIpgLMxT+SNkruGgYkLhzq/9vs3fsolTZegKaKDZI= +github.com/Antonboom/nilnil v1.1.2 h1:aNlFuJhaEseXe4fHO3xbjXlSeEiQVYa2lEkWD2s2hAY= +github.com/Antonboom/nilnil v1.1.2/go.mod h1:0ynwvphOLmAuMwTNDyBnDZmSwZoDpcFXmUHmzoHH2WA= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7tSF5XZF0FJG/MuCBI= -github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= +github.com/ClickHouse/clickhouse-go-linter v1.2.1 h1:zGEKIyd5YL08ieWG/LOUmlau2DxbxPVOfAeo+4Jz3ck= +github.com/ClickHouse/clickhouse-go-linter v1.2.1/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= @@ -75,8 +77,8 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsu github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= -github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= +github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= +github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= @@ -112,8 +114,8 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6 github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= -github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= +github.com/bombsimon/wsl/v5 v5.9.0 h1:WCrgZ7RQnZO5oEwbVTlYgBdU3wL294kR1BSWV8vTfsU= +github.com/bombsimon/wsl/v5 v5.9.0/go.mod h1:kjo4HiAV5FDkHC8/uzJq9mBffEEd6WT/nvN7DoMovDM= github.com/braydonk/yaml v0.9.0 h1:ewGMrVmEVpsm3VwXQDR388sLg5+aQ8Yihp6/hc4m+h4= github.com/braydonk/yaml v0.9.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= @@ -122,8 +124,8 @@ github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDw github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I= github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4= -github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= -github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/butuzov/mirror v1.3.3 h1:v0RsWBhfFc1RQqE/f3sHpSttKDtodFn0gFmtYyD4/hA= +github.com/butuzov/mirror v1.3.3/go.mod h1:h9BzzwYnTiHO0GzgvaTqIg7VSsOUhdIv51cHFFBmX1w= github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= @@ -137,10 +139,10 @@ github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1Di github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= -github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI= -github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI= -github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= -github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA= +github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro= +github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ= +github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= @@ -168,12 +170,15 @@ github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEy github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0= +github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/docker/cli v29.6.2+incompatible h1:/bjePvcbbFTnRrMfWJBY7AjfICdsiLVgHn6LwTVOcqw= github.com/docker/cli v29.6.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= @@ -190,18 +195,18 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= -github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= +github.com/firefart/nonamedreturns v1.0.8 h1:iB32Dl17zJl1zlVEj/WlUWgx0HiRyQ85OUw1WHa4/II= +github.com/firefart/nonamedreturns v1.0.8/go.mod h1:vxFNvm5AfP/8rgAKFzYmnqx0yp1HjrYsErZ9pHPTznA= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= -github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= -github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= -github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= +github.com/ghostiam/protogetter v0.3.21 h1:EeWTGvL/Eyosp653hiWb6Byx4b69iJC4/E+za7vQHoI= +github.com/ghostiam/protogetter v0.3.21/go.mod h1:iAKSpyoHwYzay+OpjoWgwzRtPFthEfuUvmlomTThck0= +github.com/go-critic/go-critic v0.14.4 h1:dSX4C3pWSeuMVxvQh6yG8U0ReSf3YOmKi4nwX5q7n/8= +github.com/go-critic/go-critic v0.14.4/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -213,8 +218,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= +github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -279,14 +284,16 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= +github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 h1:fVLolA3dG6s0brGetsiDAtcnpMSwVa2LqXJEw/9RJG4= +github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6/go.mod h1:1xo+NFW5S+bEf2DKXhaxuzvcDN5AR6o/KMwvqC5Mkpo= github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A= github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.12.2 h1:7+d1uY0bq1MU2UV3R5pW5Q7QWdcoq4naMRXM+gsJKrs= -github.com/golangci/golangci-lint/v2 v2.12.2/go.mod h1:opqHHuIcTG2R+4akzWMd4o1BnD9/1LcjICWOujr91U8= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA= +github.com/golangci/golangci-lint/v2 v2.13.2 h1:bCyq3E4vo9qwzifjpzJqYndEt7Ncva80qkk8G2B4TXU= +github.com/golangci/golangci-lint/v2 v2.13.2/go.mod h1:5xaMd1kAxV7GSBPEyngw8gnnjoRocqW9UVKdBR6627w= github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= @@ -335,8 +342,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw= +github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -373,8 +380,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.10.0 h1:Ptt+OoE4NaEWKhLrWrrN3IpZdGLiqaf7WLnEX/iv4Jw= -github.com/jgautheron/goconst v1.10.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= +github.com/jgautheron/goconst v1.11.0 h1:KgN90z5qXt5f0Uzf3cWXev3hfMMFUyNeKdpkSBRvLDk= +github.com/jgautheron/goconst v1.11.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -390,8 +397,8 @@ github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= -github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= -github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= +github.com/kisielk/errcheck v1.20.0 h1:9rwHBNKzd4wkDWcROy3DvFGNqEPlkxBg305rvk7HabI= +github.com/kisielk/errcheck v1.20.0/go.mod h1:O+f80MKNwX8Oor2jwgpeQ9An7uJm+hRSgT+h22knRJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= @@ -411,12 +418,10 @@ github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= -github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= -github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= -github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= -github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= +github.com/ldez/gomoddirectives v0.9.0 h1:2YV/EX7nVlWL4jySusYTzBKHuE3D2fgcRsQuMa3yIoo= +github.com/ldez/gomoddirectives v0.9.0/go.mod h1:DdZzfm9MdXCjn2/UGYXCFfo+tzrp2Ib4iD2Q0kIJkwE= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= @@ -427,8 +432,8 @@ github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= -github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= +github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -445,12 +450,12 @@ github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= -github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= +github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -480,12 +485,12 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= -github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= -github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8= +github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c= +github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= +github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= +github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -499,12 +504,11 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= -github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -538,8 +542,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= -github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/raeperd/recvcheck v0.3.0 h1:PM+XYvyxIj3bo+kobJfFTdTuU3Lmfu96mKDbyHDbRt8= +github.com/raeperd/recvcheck v0.3.0/go.mod h1:PZNwG+HztFYMH2ZPq0Hu3QgkV2yiA6VrtNz9c1fXWJo= github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -547,8 +551,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= @@ -558,14 +562,14 @@ github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7 github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= -github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= +github.com/securego/gosec/v2 v2.28.0 h1:ZsSdiDb0AtTpLFVol5z91gbMei9ZiLEPG/pZjZujp7c= +github.com/securego/gosec/v2 v2.28.0/go.mod h1:lb4/9AHe+lJy/kjWmWRWWsEipvbwGKuxf+tY1Pmjdnk= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/ratchet v0.12.0 h1:UY1sEevYHi28ryw9c8eHOLonc6oePZDUXHbzx0ESQqw= @@ -573,8 +577,8 @@ github.com/sethvargo/ratchet v0.12.0/go.mod h1:Unu+6zOcsnJj5x/hKgiaRKbGKPZkysoYk github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= +github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= @@ -601,13 +605,13 @@ github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -630,8 +634,8 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= -github.com/uudashr/iface v1.4.2 h1:06Vq5RKVYThBsj0Bnw4oasMjD1r+7CE/bcKOA8dVSvg= -github.com/uudashr/iface v1.4.2/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/uudashr/iface v1.5.1 h1:BS3yrgaT55s3dAtJoxuTnsZNbsejq/mVuzp49YDyfBA= +github.com/uudashr/iface v1.5.1/go.mod h1:5UWoT6SvTdTww/KToRj6clO+n4Kg3/NrAQvgOB+5Ggw= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -658,8 +662,8 @@ go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= -go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= -go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= +go.augendre.info/fatcontext v0.10.0 h1:HhFopmivh8U1+AU7f0kuwUeg2eiIns7YsGQOMHwSJ90= +go.augendre.info/fatcontext v0.10.0/go.mod h1:pqpGvA9GlrXy+aXkp8L2dKz12Zp4g2FhzcAtwToU+2w= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -671,10 +675,11 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= -go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -696,8 +701,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= +golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f h1:+lI8cDJ4uceLipg2f1ODay7fEuLkk0BIHXd6PB8icxo= +golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -722,8 +727,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -758,8 +763,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -781,8 +786,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= 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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -823,15 +828,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518 h1:F5BWKvW126NXR74uxkxuc1jQHhm/rwm/J3rSiFyuRs4= +golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518/go.mod h1:i+ivNqjDnTF3WTElsdk5g9V5DTSBYgdNo7xTU9SDwYA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -843,8 +847,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -895,14 +899,14 @@ golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0t golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= +golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/vuln v1.6.0 h1:FeMO9Rm/HwyduOztbvKcOw+zvDEPr4I4aQNSfevFcKY= -golang.org/x/vuln v1.6.0/go.mod h1:bWlG2493/sjR7ksvicBgMrznH3eYQEyK8ifUYBrqUbg= +golang.org/x/vuln v1.8.0 h1:clG4qBU6zH5VKjti8n5j8BBuYzoSha392xXMkXS351U= +golang.org/x/vuln v1.8.0/go.mod h1:Fzm4XK3Hbl1ZvZ7JpNTEWb7CJWOZ7m2LX0GLu4Fsrwo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -982,8 +986,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1011,12 +1015,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= -honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= -mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= -mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= +honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w= +honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= +mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc= +mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8/go.mod h1:MrS/+zJ1M2xvGXhKktiHbNQeQPyg3Qel+KkzT+f7/i4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/roxvet/common/traverse.go b/tools/roxvet/common/traverse.go index 5a4d2ce5fd22c..358a16d3498a9 100644 --- a/tools/roxvet/common/traverse.go +++ b/tools/roxvet/common/traverse.go @@ -2,6 +2,7 @@ package common import ( "go/ast" + "slices" "golang.org/x/tools/go/ast/inspector" ) @@ -11,7 +12,7 @@ import ( func FilteredPreorder(inspector *inspector.Inspector, fileFilter FileFilter, nodeTypes []ast.Node, fn func(n ast.Node)) { // Limit the nodeTypes slice's capacity to its length. This ensures that any call to `append` will not write into // the backing storage, but allocate a new slice. - effectiveTypes := nodeTypes[:len(nodeTypes):len(nodeTypes)] + effectiveTypes := slices.Clip(nodeTypes) hadFile := hasFile(nodeTypes) if !hadFile { effectiveTypes = append(effectiveTypes, (*ast.File)(nil)) @@ -33,7 +34,7 @@ func FilteredPreorder(inspector *inspector.Inspector, fileFilter FileFilter, nod // FilteredNodes calls `inspector.Nodes(nodeTypes, fn)`, but filters out all files that do not pass the given // fileFilter. func FilteredNodes(inspector *inspector.Inspector, fileFilter FileFilter, nodeTypes []ast.Node, fn func(n ast.Node, push bool) bool) { - effTypes := nodeTypes[:len(nodeTypes):len(nodeTypes)] + effTypes := slices.Clip(nodeTypes) hadFile := hasFile(nodeTypes) if !hadFile { effTypes = append(effTypes, (*ast.File)(nil)) From 6de6672262311b1dfb184d7591061acac3017e78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:57:50 +0200 Subject: [PATCH 470/638] chore(deps): bump go.uber.org/atomic from 1.11.0 to 1.12.0 (#23185) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2e2331e0d1e39..04875322d5bec 100644 --- a/go.mod +++ b/go.mod @@ -139,7 +139,7 @@ require ( github.com/tkuchiki/go-timezone v0.2.3 github.com/travelaudience/go-promhttp v1.0.1 github.com/vbauerster/mpb/v4 v4.12.2 - go.uber.org/atomic v1.11.0 + go.uber.org/atomic v1.12.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.6.0 go.uber.org/zap v1.28.0 diff --git a/go.sum b/go.sum index f130186feba73..32ecd1cc50323 100644 --- a/go.sum +++ b/go.sum @@ -1506,8 +1506,8 @@ go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT7 go.step.sm/crypto v0.87.0 h1:+u0pDz1OV9M/SswA6KtcQ3GSrYMMelbL//0W/ceJVYI= go.step.sm/crypto v0.87.0/go.mod h1:gBr1mpMiKs5804/Yt03uAoOBiw3DFxARZnGUKEUO82M= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= From 93448f6da8fba38efda1326be36efe645c8e66b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:25:58 +0200 Subject: [PATCH 471/638] chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.14.0 to 1.14.1 (#23188) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 04875322d5bec..b4cb8b0d4564c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( dario.cat/mergo v1.0.2 github.com/Azure/azure-sdk-for-go-extensions v0.7.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 github.com/Azure/azure-sdk-for-go/sdk/monitor/ingestion/azlogs v1.1.0 github.com/BurntSushi/toml v1.6.0 @@ -197,7 +197,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v9 v9.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/ComplianceAsCode/compliance-sdk v0.0.0-20250925092238-0b432985772e // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 // indirect diff --git a/go.sum b/go.sum index 32ecd1cc50323..695e7e236910a 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/Azure/azure-sdk-for-go-extensions v0.7.0 h1:ko7jxarxFmfm3OAtIkKcnC2yK github.com/Azure/azure-sdk-for-go-extensions v0.7.0/go.mod h1:f/wRrqvvh197V5r4jGADV7528UdO/zfL+/Ud92BMSag= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k= @@ -118,8 +118,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= From 737d4d88c30a8c1757b04413b8d247dd63385aa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:26:10 +0200 Subject: [PATCH 472/638] chore(deps): bump cloud.google.com/go/storage from 1.64.0 to 1.68.0 (#23187) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4cb8b0d4564c..ffefe4292bc09 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( cloud.google.com/go/compute/metadata v0.9.1 cloud.google.com/go/containeranalysis v0.20.0 cloud.google.com/go/securitycenter v1.46.0 - cloud.google.com/go/storage v1.64.0 + cloud.google.com/go/storage v1.68.0 dario.cat/mergo v1.0.2 github.com/Azure/azure-sdk-for-go-extensions v0.7.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 diff --git a/go.sum b/go.sum index 695e7e236910a..31b41eb5ab7ae 100644 --- a/go.sum +++ b/go.sum @@ -73,8 +73,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.64.0 h1:KLpxI/oX9LxeRsNqn877d2WyeT3ryiEwnGt8pwcSPZg= -cloud.google.com/go/storage v1.64.0/go.mod h1:lWyAtwvDZHdL3k68WVKbESP6bmWaV23ZJJ/JEVw/ZaQ= +cloud.google.com/go/storage v1.68.0 h1:gqrAMJ51OZjYgU6AJ2U60um90YQhSjq8HEIQNtJ4C/8= +cloud.google.com/go/storage v1.68.0/go.mod h1:UsS9OgFg/XHOSYakQ8ZtLWWeyGkk1WnmD/GsGfN0BHM= cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= From 8deb3e9588e082fad180b538b24543b5323657ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:46:27 +0200 Subject: [PATCH 473/638] chore(deps): bump github.com/kudobuilder/kuttl from 0.26.0 to 0.27.0 in /operator/tools/kuttl (#23148) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator/tools/kuttl/go.mod | 95 +++++++-------- operator/tools/kuttl/go.sum | 235 ++++++++++++++++++------------------ 2 files changed, 166 insertions(+), 164 deletions(-) diff --git a/operator/tools/kuttl/go.mod b/operator/tools/kuttl/go.mod index 78782800d13cc..efd998592d696 100644 --- a/operator/tools/kuttl/go.mod +++ b/operator/tools/kuttl/go.mod @@ -2,7 +2,7 @@ module github.com/stackrox/rox/operator/tools/kuttl go 1.26.3 -require github.com/kudobuilder/kuttl v0.26.0 +require github.com/kudobuilder/kuttl v0.27.0 require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect @@ -22,31 +22,31 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.10.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/swag v0.26.0 // indirect - github.com/go-openapi/swag/cmdutils v0.26.0 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/fileutils v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/mangling v0.26.0 // indirect - github.com/go-openapi/swag/netutils v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect - github.com/google/cel-go v0.29.0 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect + github.com/google/cel-go v0.31.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -57,21 +57,22 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/moby/api v1.54.2 // indirect - github.com/moby/moby/client v0.4.1 // indirect + github.com/moby/moby/api v1.56.0 // indirect + github.com/moby/moby/client v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.40.0 // indirect + github.com/onsi/ginkgo/v2 v2.32.1 // indirect + github.com/onsi/gomega v1.43.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_golang v1.24.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.5 // indirect - github.com/prometheus/procfs v0.20.1 // indirect + github.com/prometheus/common v0.70.0 // indirect + github.com/prometheus/procfs v0.21.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -79,42 +80,42 @@ require ( github.com/thoas/go-funk v0.9.3 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect - golang.org/x/net v0.55.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.0 // indirect - k8s.io/apiextensions-apiserver v0.36.0 // indirect - k8s.io/apimachinery v0.36.0 // indirect - k8s.io/client-go v0.36.0 // indirect + k8s.io/api v0.37.0 // indirect + k8s.io/apiextensions-apiserver v0.37.0 // indirect + k8s.io/apimachinery v0.37.0 // indirect + k8s.io/client-go v0.37.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect - sigs.k8s.io/controller-runtime v0.24.0 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect + sigs.k8s.io/controller-runtime v0.25.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kind v0.31.0 // indirect + sigs.k8s.io/kind v0.33.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/kuttl/go.sum b/operator/tools/kuttl/go.sum index 57a9d9a3222f9..6d90ca396d9c2 100644 --- a/operator/tools/kuttl/go.sum +++ b/operator/tools/kuttl/go.sum @@ -35,8 +35,8 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= @@ -54,50 +54,50 @@ github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxv github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= -github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -105,8 +105,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -117,14 +117,14 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kudobuilder/kuttl v0.26.0 h1:8PXqnuismPqmxk5yslqfCezXLfkCbR+PIqz8KDzuo7M= -github.com/kudobuilder/kuttl v0.26.0/go.mod h1:jsTOHjoskFIcrkIWnLjAztV5n86o+YqUOjgTK/5lKsc= +github.com/kudobuilder/kuttl v0.27.0 h1:Dc+h0OaoLSaNdSt0caMtHFr0mvdhz7flDuZzq1k1wuU= +github.com/kudobuilder/kuttl v0.27.0/go.mod h1:mpoA6IwOP7pe3hxGO5ojVcpbE6KphWTn7QNN9qpZaWw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -135,10 +135,10 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= -github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= -github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/moby/api v1.56.0 h1:GQzua3NA599ASSIICx0iFgiJeO9YkdDARvQsm23ZZuQ= +github.com/moby/moby/api v1.56.0/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk= +github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= +github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -147,10 +147,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= -github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU= +github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -162,14 +162,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -183,30 +183,30 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -215,37 +215,38 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= +golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -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/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -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-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +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/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -262,31 +263,31 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= -k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= +k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= +k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= +k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= +k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= +k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= -sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.25.1 h1:BKgU9OeE8xv8EbbM8cY0NVzTQs35rokkdq1jh12fMb4= +sigs.k8s.io/controller-runtime v0.25.1/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g= -sigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= +sigs.k8s.io/kind v0.33.0 h1:AjvDv3vOygb/VKLVQW87lfktIBzkxR8Ump9DjxC8+Lk= +sigs.k8s.io/kind v0.33.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From e7de9a18e5b8a6dc121d509e8c96fb479be5281f Mon Sep 17 00:00:00 2001 From: Misha Sugakov <537715+msugakov@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:14:00 +0200 Subject: [PATCH 474/638] chore(ci): Collect `StorageClass`-es after tests (#23157) --- scripts/ci/collect-service-logs.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/collect-service-logs.sh b/scripts/ci/collect-service-logs.sh index 82f0753973a3c..6564f669f4aa9 100755 --- a/scripts/ci/collect-service-logs.sh +++ b/scripts/ci/collect-service-logs.sh @@ -69,7 +69,8 @@ main() { set +e for object in daemonsets deployments services pods secrets serviceaccounts validatingwebhookconfigurations \ - catalogsources subscriptions clusterserviceversions central securedclusters nodes persistentvolumeclaims; do + catalogsources subscriptions clusterserviceversions central securedclusters nodes persistentvolumeclaims \ + storageclasses; do # A feel good command before pulling logs info ">>> Collecting ${object} from namespace ${namespace} <<<" out="$(mktemp)" From 25abe457bbcc430ed448950d56e4ac0294692789 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:13:02 +0000 Subject: [PATCH 475/638] chore(deps): bump cloud.google.com/go/securitycenter from 1.46.0 to 1.47.0 (#23186) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ffefe4292bc09..3dbb7f19cfbbd 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/artifactregistry v1.27.0 cloud.google.com/go/compute/metadata v0.9.1 cloud.google.com/go/containeranalysis v0.20.0 - cloud.google.com/go/securitycenter v1.46.0 + cloud.google.com/go/securitycenter v1.47.0 cloud.google.com/go/storage v1.68.0 dario.cat/mergo v1.0.2 github.com/Azure/azure-sdk-for-go-extensions v0.7.0 diff --git a/go.sum b/go.sum index 31b41eb5ab7ae..54bcba05a120b 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/securitycenter v1.46.0 h1:MfzR4Bj5W51MAIyJlA4molK/vj0xO64te4yCGAbwXL0= -cloud.google.com/go/securitycenter v1.46.0/go.mod h1:7mAlzsCsKlEVmciAFORl431laDGpoKGFkSQndAzFs30= +cloud.google.com/go/securitycenter v1.47.0 h1:cKjSLURYE5tewWgH6VW6Jo6YaZWG+FUUFh/SmrqrcHU= +cloud.google.com/go/securitycenter v1.47.0/go.mod h1:OWKAGzP36rJ4pFSjU441g+vi7WTRdMFRVw1rykvMwPE= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= From 5962b4cf993a84fa64e063c60c8d5ebabd4cf97a Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 1 Oct 2026 13:33:21 +0200 Subject: [PATCH 476/638] ROX-37373: reduce GKE E2E cluster to 2 nodes for GHA nongroovy (#23034) --- .github/workflows/e2e-nongroovy-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index e3df36cf7a676..9682455373095 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -75,7 +75,7 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + NUM_NODES: "2" GKE_SPOT: "false" timeout-minutes: 20 run: | From 7d20bd737a1f145d329e6e4436fc0d83973ceda9 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 1 Oct 2026 08:00:56 -0400 Subject: [PATCH 477/638] ROX-37316: Improve analytics for compliance scan schedules wizard (#23132) --- .../Schedules/CreateScanConfigPage.tsx | 2 +- .../Schedules/EditScanConfigDetail.tsx | 5 ++++- .../Schedules/ScanConfigDetailPage.tsx | 4 ++-- .../Schedules/ScanConfigsPage.tsx | 4 ++-- .../Schedules/Wizard/ScanConfigOptions.tsx | 4 ++-- .../Schedules/Wizard/ScanConfigWizardForm.tsx | 20 +++++++++++++++++-- .../compliance.scanConfigs.utils.tsx | 2 +- ui/apps/platform/src/hooks/useAnalytics.ts | 9 ++++++++- 8 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx index d686ad51b0f3c..851ac85b7e708 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx @@ -28,7 +28,7 @@ function CreateScanConfigPage(): ReactElement { - + ); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx index 6ed9bef795cf7..361fb2e4ae78c 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx @@ -81,7 +81,10 @@ function EditScanConfigDetail({ )} {!isLoading && scanConfig && ( - + )} diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx index 9b2898d1beb44..e6f587d39f9b8 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx @@ -8,7 +8,7 @@ import useRestQuery from 'hooks/useRestQuery'; import { getComplianceScanConfiguration } from 'services/ComplianceScanConfigurationService'; import EditScanConfigDetail from './EditScanConfigDetail'; import ViewScanConfigDetail from './ViewScanConfigDetail'; -import type { PageActions } from './compliance.scanConfigs.utils'; +import type { SchedulePageAction } from './compliance.scanConfigs.utils'; type ScanConfigDetailPageProps = { hasWriteAccessForCompliance: boolean; @@ -18,7 +18,7 @@ function ScanConfigDetailPage({ hasWriteAccessForCompliance, }: ScanConfigDetailPageProps): ReactElement { const { scanConfigId } = useParams() as { scanConfigId: string }; - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const scanConfigFetcher = useCallback(() => { const { request, cancel } = getComplianceScanConfiguration(scanConfigId); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx index 6e98bdcfacc63..3b25225ef7acc 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx @@ -4,7 +4,7 @@ import { Banner } from '@patternfly/react-core'; import usePageAction from 'hooks/usePageAction'; import usePermissions from 'hooks/usePermissions'; import { complianceEnhancedSchedulesPath } from 'routePaths'; -import type { PageActions } from './compliance.scanConfigs.utils'; +import type { SchedulePageAction } from './compliance.scanConfigs.utils'; import CreateScanConfigPage from './CreateScanConfigPage'; import ComplianceNotFoundPage from '../ComplianceNotFoundPage'; import ScanConfigDetailPage from './ScanConfigDetailPage'; @@ -17,7 +17,7 @@ function ScanConfigsPage() { * /main/compliance-enhanced/scan-configs?action=create * /main/compliance-enhanced/scan-configs/configId */ - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForCompliance = hasReadWriteAccess('Compliance'); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx index 9e683163df5e2..cbcaef265f594 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx @@ -20,7 +20,7 @@ import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; import RepeatScheduleDropdown from 'Components/PatternFly/RepeatScheduleDropdown'; import usePageAction from 'hooks/usePageAction'; -import type { PageActions, ScanConfigFormValues } from '../compliance.scanConfigs.utils'; +import type { ScanConfigFormValues, SchedulePageAction } from '../compliance.scanConfigs.utils'; import { helperTextForName, helperTextForNameEdit, helperTextForTime } from './useFormikScanConfig'; @@ -28,7 +28,7 @@ import './ScanConfigOptions.css'; function ScanConfigOptions(): ReactElement { const formik: FormikContextType = useFormikContext(); - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const isEditAction = pageAction === 'edit'; function handleSelectChange(id: string, value: string): void { diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx index f6c6061e064b9..3949b3893004a 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx @@ -25,7 +25,7 @@ import ReportConfiguration from './ReportConfiguration'; import ReviewConfig from './ReviewConfig'; import useFormikScanConfig from './useFormikScanConfig'; import { convertFormikToScanConfig } from '../compliance.scanConfigs.utils'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; +import type { ScanConfigFormValues, SchedulePageAction } from '../compliance.scanConfigs.utils'; const PARAMETERS = 'Set parameters'; const PARAMETERS_ID = 'parameters'; @@ -40,6 +40,7 @@ const REVIEW_CONFIG_ID = 'review'; type ScanConfigWizardFormProps = { initialFormValues?: ScanConfigFormValues; + pageAction: SchedulePageAction; }; type CustomWizardFooterProps = { @@ -115,7 +116,10 @@ function CustomWizardFooter({ ); } -function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): ReactElement { +function ScanConfigWizardForm({ + initialFormValues, + pageAction, +}: ScanConfigWizardFormProps): ReactElement { const { analyticsTrack } = useAnalytics(); const navigate = useNavigate(); const formik = useFormikScanConfig(initialFormValues); @@ -133,6 +137,8 @@ function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): setIsCreating(true); setCreateScanConfigError(''); const complianceScanConfig = convertFormikToScanConfig(formik.values); + const { clusters, scanConfig } = complianceScanConfig; + const { notifiers, profiles, scanSchedule } = scanConfig; try { await saveScanConfig(complianceScanConfig); @@ -141,6 +147,11 @@ function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): properties: { success: true, errorMessage: '', + action: pageAction, + clusters: clusters.length, + intervalType: scanSchedule.intervalType, + notifiers: notifiers.length, + profiles: profiles.length, }, }); navigate(complianceEnhancedSchedulesPath); @@ -150,6 +161,11 @@ function ScanConfigWizardForm({ initialFormValues }: ScanConfigWizardFormProps): properties: { success: false, errorMessage: getAxiosErrorMessage(error), + action: pageAction, + clusters: clusters.length, + intervalType: scanSchedule.intervalType, + notifiers: notifiers.length, + profiles: profiles.length, }, }); setCreateScanConfigError(getAxiosErrorMessage(error)); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/compliance.scanConfigs.utils.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/compliance.scanConfigs.utils.tsx index e1f6f4ad98235..bc455e5c67555 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/compliance.scanConfigs.utils.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/compliance.scanConfigs.utils.tsx @@ -36,7 +36,7 @@ export type ScanConfigFormValues = { report: ScanReportConfiguration; }; -export type PageActions = 'create' | 'edit' | 'clone'; +export type SchedulePageAction = 'create' | 'edit'; export function getTimeWithHourMinuteFromISO8601(timeISO8601: string) { // Given an ISO 8601 date time string from response, diff --git a/ui/apps/platform/src/hooks/useAnalytics.ts b/ui/apps/platform/src/hooks/useAnalytics.ts index 0a97d7bb47202..7a8e59980948e 100644 --- a/ui/apps/platform/src/hooks/useAnalytics.ts +++ b/ui/apps/platform/src/hooks/useAnalytics.ts @@ -3,8 +3,10 @@ import Raven from 'raven-js'; import mapValues from 'lodash/mapValues'; import type { ReportPageAction } from 'Components/Reports/reports.types'; +import type { SchedulePageAction } from 'Containers/ComplianceEnhanced/Schedules/compliance.scanConfigs.utils'; import type { ImageType } from 'services/ReportsService.types'; import type { Telemetry } from 'types/config.proto'; +import type { Schedule } from 'types/schedule.proto'; import { ensureExhaustive, tupleTypeGuard } from 'utils/type.utils'; import type { UnionFrom } from 'utils/type.utils'; import { getQueryObject, getQueryString } from 'utils/queryStringUtils'; @@ -391,7 +393,7 @@ export type AnalyticsEvent = properties: { action: ReportPageAction; cvesSince: 'allVuln'; // | 'sinceLastSentScheduledReport' | 'sinceStartDate' - intervalType: 'DAILY' | 'MONTHLY' | 'WEEKLY' | 'UNSET'; + intervalType: Schedule['intervalType']; notifiers: number; resourceScope: string; }; @@ -548,6 +550,11 @@ export type AnalyticsEvent = properties: { success: true | false; errorMessage: string; + action: SchedulePageAction; + clusters: number; + intervalType: Schedule['intervalType']; + notifiers: number; + profiles: number; }; } | { From 032d48a33915fc746c57d6ecc7abce4cbe467981 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:09:04 +0200 Subject: [PATCH 478/638] ci: Fail add-vms action on skipped VMs and accept rhel8 (#22710) --- .github/workflows/add-vms-to-cluster.yml | 7 ++++--- scripts/ci/add-vms/action.yml | 6 +++--- scripts/ci/add-vms/add-vms.bats | 1 + scripts/ci/add-vms/add-vms.sh | 15 ++++++++++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/add-vms-to-cluster.yml b/.github/workflows/add-vms-to-cluster.yml index b790abaf6be2a..0ace3f5f384e7 100644 --- a/.github/workflows/add-vms-to-cluster.yml +++ b/.github/workflows/add-vms-to-cluster.yml @@ -1,5 +1,5 @@ -name: "Add VMs to Cluster" -run-name: "Add ${{ inputs.num-vms }} ${{ inputs.vm-os }} VMs to ${{ inputs.cluster-name }}" +name: "Ensure VMs on Cluster" +run-name: "Ensure ${{ inputs.num-vms }} ${{ inputs.vm-os }} VMs on ${{ inputs.cluster-name }}" on: workflow_dispatch: @@ -9,7 +9,7 @@ on: required: true type: string num-vms: - description: "Number of VMs to deploy" + description: "Desired count of {os}-1..N, not how many to add. Existing VMs are reused; 2 is a no-op if rhel9-1 and rhel9-2 already exist." required: false default: 1 type: number @@ -19,6 +19,7 @@ on: default: "rhel9" type: choice options: + - rhel8 - rhel9 - rhel10 ssh-public-key: diff --git a/scripts/ci/add-vms/action.yml b/scripts/ci/add-vms/action.yml index 3b300baf010ec..41eca4d2c9bda 100644 --- a/scripts/ci/add-vms/action.yml +++ b/scripts/ci/add-vms/action.yml @@ -1,4 +1,4 @@ -name: "Add VMs to ACS Cluster" +name: "Ensure VMs on ACS Cluster" description: "Install OpenShift Virtualization, deploy VMs, and install roxagent on an Infra-managed cluster" inputs: @@ -6,11 +6,11 @@ inputs: description: "Infra cluster name (from `infractl list`)" required: true num-vms: - description: "Number of VMs to deploy" + description: "Desired count of {os}-1..N. Existing VMs are reused." required: false default: "1" vm-os: - description: "VM OS: rhel9 or rhel10" + description: "VM OS: rhel8, rhel9, or rhel10" required: false default: "rhel9" ssh-public-key: diff --git a/scripts/ci/add-vms/add-vms.bats b/scripts/ci/add-vms/add-vms.bats index 4b37bc3dba926..1c6ce5090598f 100644 --- a/scripts/ci/add-vms/add-vms.bats +++ b/scripts/ci/add-vms/add-vms.bats @@ -21,6 +21,7 @@ load "../../test_helpers.bats" ' _ "${BATS_TEST_DIRNAME}/add-vms.sh" assert_success + assert_output --partial "::error title=Add VMs incomplete::Skipped: rhel10-3; agent failed: rhel10-3" run cat "$summary_file" assert_output --partial "## Add VMs to Cluster" diff --git a/scripts/ci/add-vms/add-vms.sh b/scripts/ci/add-vms/add-vms.sh index 517a0143a5625..fd4f89cc880d5 100755 --- a/scripts/ci/add-vms/add-vms.sh +++ b/scripts/ci/add-vms/add-vms.sh @@ -7,7 +7,7 @@ # Options: # --cluster NAME Infra cluster name (calls infractl for kubeconfig) # --num-vms N Number of VMs (default: 1) -# --os OS VM OS: rhel9|rhel10 (default: rhel9) +# --os OS VM OS: rhel8|rhel9|rhel10 (default: rhel9) # --ssh-key PATH Path to user SSH public key to add to target VMs # --namespace NS Target namespace (default: openshift-cnv) # --vm-prefix PREFIX VM name prefix (default: same as OS) @@ -64,7 +64,7 @@ parse_args() { if ! [[ "$NUM_VMS" =~ ^[0-9]+$ ]] || (( NUM_VMS < 1 )); then die "--num-vms must be a positive integer" fi - [[ "$VM_OS" =~ ^rhel(9|10)$ ]] || die "--os must be rhel9 or rhel10" + [[ "$VM_OS" =~ ^rhel(8|9|10)$ ]] || die "--os must be rhel8, rhel9, or rhel10" } # Sets KUBECONFIG: either fetches it from infractl for the given cluster @@ -218,7 +218,11 @@ write_github_summary() { echo "" } >> "$GITHUB_STEP_SUMMARY" - echo "::notice title=VM action summary::See the job summary for VM access, key download, and native service verification." + if [[ ${#SKIPPED_VMS[@]} -gt 0 || ${#NATIVE_AGENT_FAILED_VMS[@]} -gt 0 ]]; then + echo "::error title=Add VMs incomplete::Skipped: ${SKIPPED_VMS[*]}; agent failed: ${NATIVE_AGENT_FAILED_VMS[*]}. See the job summary." + else + echo "::notice title=VM action summary::See the job summary for VM access, key download, and native service verification." + fi } cleanup() { @@ -268,6 +272,8 @@ main() { accessible_vms+=("${MANAGED_VMS[@]}" "${ADOPTED_VMS[@]}") if [[ ${#accessible_vms[@]} -eq 0 ]]; then + # Summary first so the ::error annotation still names the skipped VMs. + print_summary die "No accessible VMs — cannot install agent. All VMs failed SSH readiness." else export AUTOMATION_SSH_PRIVKEY @@ -278,6 +284,9 @@ main() { # Step 4: Summary print_summary + if [[ ${#SKIPPED_VMS[@]} -gt 0 || ${#NATIVE_AGENT_FAILED_VMS[@]} -gt 0 ]]; then + die "Some VMs were skipped or the native agent failed to start" + fi } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then From a9e53f013824e36a9e22c06cf69ec1034f3c7794 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 1 Oct 2026 14:22:18 +0200 Subject: [PATCH 479/638] chore: add Slack user ID for @rhmdnd (#23162) --- scripts/ci/get-slack-user-id.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ci/get-slack-user-id.sh b/scripts/ci/get-slack-user-id.sh index b8287adce5be9..4f246c5179d7b 100755 --- a/scripts/ci/get-slack-user-id.sh +++ b/scripts/ci/get-slack-user-id.sh @@ -57,6 +57,7 @@ case "$github_login" in 'parametalol') slack_user='U02MJ72K1B5' ;; 'pedrottimark') slack_user='U01RN8V8DEH' ;; 'porridge') slack_user='U020XCUG2LA' ;; +'rhmdnd') slack_user='U01C24L97D1' ;; 'rhybrillou') slack_user='U02GPRG4NHF' ;; 'robbycochran') slack_user='U03NAEPKDE1' ;; 'rukletsov') slack_user='U01G6P17RTK' ;; From 18a59ab58c508bf37c5417f6a33bf7f329b8c99b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:43:24 +0200 Subject: [PATCH 480/638] chore(deps): bump docker/bake-action from 7.3.0 to 7.4.0 (#23150) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d2932976e7487..607c34fe7803f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -498,7 +498,7 @@ jobs: - name: Build and push DB images in parallel with bake if: | github.event_name == 'push' || !github.event.pull_request.head.repo.fork - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # ratchet:docker/bake-action@v7.3.0 + uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # ratchet:docker/bake-action@v7.4.0 with: source: . files: .github/docker/images.hcl From 3cf91015ae1ab7e96a7dcefc2bd21ea6e6a55e83 Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:43:54 +0200 Subject: [PATCH 481/638] fix(CI): Collect roxagent journalctl logs after VM scan e2e (#22820) --- .openshift-ci/post_tests.py | 24 +- scripts/ci/collect-vm-guest-logs.sh | 287 +++++++++++++++++++ scripts/ci/delete-vm-ns.sh | 61 ++++ scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py | 7 +- scripts/ci/lib.sh | 20 ++ tests/e2e/run-vm-scanning.sh | 2 + tests/e2e/vm-scanning-lib.sh | 76 +++++ tests/vm_scanning_suite_test.go | 6 +- 8 files changed, 477 insertions(+), 6 deletions(-) create mode 100755 scripts/ci/collect-vm-guest-logs.sh create mode 100755 scripts/ci/delete-vm-ns.sh diff --git a/.openshift-ci/post_tests.py b/.openshift-ci/post_tests.py index 10f8ed4947ace..64f27674b5204 100644 --- a/.openshift-ci/post_tests.py +++ b/.openshift-ci/post_tests.py @@ -34,6 +34,7 @@ class PostTestsConstants: DIAGNOSTIC_OUTPUT = "diagnostic-bundle" CENTRAL_DATA_OUTPUT = "central-data" STACKROX_LOG_DIR = "/tmp/stackrox-logs" + VM_GUEST_LOG_DIR = "/tmp/vm-diagnostics" class NullPostTest: @@ -151,12 +152,13 @@ def _store_osci_artifact(self, source): class PostClusterTest(StoreArtifacts): """The standard cluster test suite of debug gathering and analysis""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def __init__( self, collect_collector_metrics=True, collect_central_artifacts=True, collect_service_logs=True, + collect_vm_guest_logs=False, check_stackrox_logs=False, artifact_destination_prefix=None, ): @@ -169,6 +171,7 @@ def __init__( self.service_logs_destination = self.K8S_LOG_DIR self._collect_collector_metrics = collect_collector_metrics self._collect_service_logs = collect_service_logs + self._collect_vm_guest_logs = collect_vm_guest_logs self._check_stackrox_logs = check_stackrox_logs self.k8s_namespaces = [ "stackrox", @@ -202,6 +205,8 @@ def run(self, test_outputs=None): self.grab_central_data() if self._collect_service_logs: self.collect_service_logs() + if self._collect_vm_guest_logs: + self.collect_vm_guest_logs() if self._check_stackrox_logs: self.check_stackrox_logs() self.store_artifacts(test_outputs) @@ -232,6 +237,23 @@ def collect_service_logs(self): ) self.data_to_store.append(self.service_logs_destination) + def collect_vm_guest_logs(self): + # Per-VM virtctl ssh is capped in the script. Cleanup is a second + # process because a collect timeout SIGKILLs the first before it can + # delete namespaces. + self.run_with_best_effort( + [ + "scripts/ci/collect-vm-guest-logs.sh", + self.VM_GUEST_LOG_DIR, + ], + timeout=10 * 60, + ) + self.data_to_store.append(self.VM_GUEST_LOG_DIR) + self.run_with_best_effort( + ["scripts/ci/delete-vm-ns.sh"], + timeout=60, + ) + def collect_collector_metrics(self): self.run_with_best_effort( [ diff --git a/scripts/ci/collect-vm-guest-logs.sh b/scripts/ci/collect-vm-guest-logs.sh new file mode 100755 index 0000000000000..ed9fae05bd5fe --- /dev/null +++ b/scripts/ci/collect-vm-guest-logs.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# collect-vm-guest-logs.sh copies roxagent journals from VM-scanning e2e guests +# via virtctl ssh. Best-effort: always exits 0. +# +# Usage: +# collect-vm-guest-logs.sh +# +# Environment: +# VM_SCAN_NAMESPACE_PREFIX default: vm-scan-e2e +# VM_GUEST_USER default: cloud-user +# VM_SCAN_E2E_DIR host files shared with the test run (default: /tmp/vm-scan-e2e) +# VM_SSH_PRIVATE_KEY_PATH PEM identity for virtctl (default: $VM_SCAN_E2E_DIR/ssh-identity) +# VIRTCTL_PATH optional virtctl override +# VM_GUEST_JOURNAL_TIMEOUT per-VM virtctl ssh ceiling (default: 90s) +# VM_GUEST_COLLECT_DEADLINE total collection budget (default: 540s) + +SCRIPTS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" +# shellcheck source=../../scripts/ci/lib.sh +source "$SCRIPTS_ROOT/scripts/ci/lib.sh" + +usage() { + echo "./scripts/ci/collect-vm-guest-logs.sh " +} + +vm_scan_e2e_dir() { + printf '%s\n' "${VM_SCAN_E2E_DIR:-/tmp/vm-scan-e2e}" +} + +resolve_virtctl() { + if [[ -n "${VIRTCTL_PATH:-}" ]]; then + if [[ -x "$VIRTCTL_PATH" ]]; then + printf '%s\n' "$VIRTCTL_PATH" + return 0 + fi + info "VIRTCTL_PATH is not executable: ${VIRTCTL_PATH}" >&2 + return 1 + fi + local persisted + persisted="$(vm_scan_e2e_dir)/virtctl" + if [[ -x "$persisted" ]]; then + printf '%s\n' "$persisted" + return 0 + fi + local pointed + pointed="$(vm_scan_e2e_dir)/virtctl-path" + if [[ -f "$pointed" ]]; then + local src + src="$(<"$pointed")" + if [[ -n "$src" && -x "$src" ]]; then + printf '%s\n' "$src" + return 0 + fi + fi + command -v virtctl +} + +resolve_ssh_identity() { + if [[ -n "${VM_SSH_PRIVATE_KEY_PATH:-}" && -f "${VM_SSH_PRIVATE_KEY_PATH}" ]]; then + printf '%s\n' "${VM_SSH_PRIVATE_KEY_PATH}" + return 0 + fi + local persisted + persisted="$(vm_scan_e2e_dir)/ssh-identity" + if [[ -f "$persisted" ]]; then + printf '%s\n' "$persisted" + return 0 + fi + return 1 +} + +# list_vm_scan_vmis prints "namespace name" lines for VMIs in vm-scan-e2e namespaces. +# Returns 1 when kubectl cannot list VMIs (distinct from zero matches). +list_vm_scan_vmis() { + local prefix="${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" + local out err ns name + out="$(mktemp)" + err="$(mktemp)" + if ! kubectl --request-timeout=30s get vmi -A \ + -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' \ + > "$out" 2>"$err"; then + info "kubectl get vmi failed: $(<"$err")" >&2 + rm -f "$out" "$err" + return 1 + fi + rm -f "$err" + while read -r ns name; do + [[ -z "$ns" || -z "$name" ]] && continue + if [[ "$ns" == "$prefix" || "$ns" == "${prefix}-"* ]]; then + printf '%s %s\n' "$ns" "$name" + fi + done < "$out" + rm -f "$out" + return 0 +} + +run_with_timeout() { + local seconds="$1" + shift + if command -v timeout >/dev/null; then + timeout -k 5s "${seconds}s" "$@" + else + "$@" + fi +} + +guest_ssh() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local ssh_timeout="$6" + local remote_cmd="$7" + + # virtctl ssh reads stdin (OpenSSH without -n) and would consume the VMI + # list from the caller's while-read loop. + run_with_timeout "$ssh_timeout" \ + "${virtctl_bin}" ssh \ + --namespace "$ns" \ + --identity-file "$identity" \ + --local-ssh-opts="-n" \ + --local-ssh-opts="-o StrictHostKeyChecking=no" \ + --local-ssh-opts="-o IdentitiesOnly=yes" \ + --local-ssh-opts="-o UserKnownHostsFile=/dev/null" \ + --local-ssh-opts="-o BatchMode=yes" \ + --local-ssh-opts="-o ConnectTimeout=30" \ + --username "$guest_user" \ + "vmi/${vmi}" \ + --command "$remote_cmd" \ + < /dev/null +} + +# seconds_left is the timeout for the next virtctl ssh. Collection stops +# on its own before the 10 minute post-test kill, which fails the job. +seconds_left() { + local cap="${VM_GUEST_JOURNAL_TIMEOUT:-90}" + local left=$((collect_deadline_at - SECONDS)) + if ((left < 1)); then + return 1 + fi + if ((left < cap)); then + printf '%s\n' "$left" + return 0 + fi + printf '%s\n' "$cap" +} + +# collect_roxagent_container_journal writes container stdout that RHEL 8 +# Podman stores in its own log, not the journal. Quadlet names that +# container systemd-roxagent. +collect_roxagent_container_journal() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local out_file="$6" + local ssh_timeout="$7" + local stderr_file + + stderr_file="$(mktemp)" + if ! guest_ssh "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$ssh_timeout" \ + "sudo podman logs --timestamps systemd-roxagent || true" \ + > "$out_file" 2>"$stderr_file"; then + { + echo "roxagent container journal collection failed for ${ns}/${vmi}" + echo "virtctl: ${virtctl_bin}" + echo "guest_user: ${guest_user}" + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + elif [[ -s "$stderr_file" ]]; then + { + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + fi + rm -f "$stderr_file" +} + +# collect_roxagent_journal is enough on RHEL 9 and 10, where Podman files +# container stdout on roxagent.service. On RHEL 8 that stdout is missing +# here, so the container journal is collected separately. +collect_roxagent_journal() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local out_file="$6" + local container_file="$7" + local ssh_timeout + + ssh_timeout="$(seconds_left)" || return 1 + + local stderr_file + stderr_file="$(mktemp)" + + if ! guest_ssh "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$ssh_timeout" \ + "sudo journalctl -u roxagent.service -u roxagent-serve.service -b --no-pager -o short-iso" \ + > "$out_file" 2>"$stderr_file"; then + { + echo "roxagent journal collection failed for ${ns}/${vmi}" + echo "virtctl: ${virtctl_bin}" + echo "guest_user: ${guest_user}" + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + fi + rm -f "$stderr_file" + ssh_timeout="$(seconds_left)" || return 1 + collect_roxagent_container_journal "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$container_file" "$ssh_timeout" || true +} + +collect_journals() { + local output_dir="$1" + local guest_user="${VM_GUEST_USER:-cloud-user}" + local virtctl_bin="" identity="" + virtctl_bin="$(resolve_virtctl)" || virtctl_bin="" + identity="$(resolve_ssh_identity)" || identity="" + + if [[ -n "$virtctl_bin" && -n "$identity" ]]; then + info ">>> Collecting roxagent journals into ${output_dir} <<<" + collect_deadline_at=$((SECONDS + ${VM_GUEST_COLLECT_DEADLINE:-540})) + local vmi_count=0 ns vmi out_file container_file vmi_list list_err + vmi_list="$(mktemp)" + list_err="$(mktemp)" + if ! list_vm_scan_vmis > "$vmi_list" 2>"$list_err"; then + info "Failed to list VMIs; writing collection-failed.txt" + { + echo "kubectl get vmi failed (not an empty match)" + cat "$list_err" + } > "${output_dir}/collection-failed.txt" + rm -f "$vmi_list" "$list_err" + return 0 + fi + rm -f "$list_err" + while read -r ns vmi; do + [[ -z "$ns" || -z "$vmi" ]] && continue + vmi_count=$((vmi_count + 1)) + out_file="${output_dir}/${ns}_${vmi}_roxagent.journal.log" + container_file="${output_dir}/${ns}_${vmi}_roxagent.container.journal.log" + info "Collecting roxagent journals for ${ns}/${vmi} -> ${out_file} ${container_file}" + if ! collect_roxagent_journal "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$out_file" "$container_file"; then + echo "stopped at ${ns}/${vmi}: collect deadline reached" \ + > "${output_dir}/collection-partial.txt" + break + fi + done < "$vmi_list" + rm -f "$vmi_list" + + if [[ "$vmi_count" -eq 0 ]]; then + info "No VMIs found in VM scan namespaces" + echo "no VMIs matched prefix ${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" \ + > "${output_dir}/collection-skipped.txt" + fi + return 0 + fi + + if [[ -z "$virtctl_bin" ]]; then + info "virtctl not found; skipping VM guest journal collection" + echo "virtctl not found" > "${output_dir}/collection-skipped.txt" + else + info "VM SSH identity not found; skipping VM guest journal collection" + echo "ssh identity not found at $(vm_scan_e2e_dir)/ssh-identity" \ + > "${output_dir}/collection-skipped.txt" + fi +} + +main() { + cd "$SCRIPTS_ROOT" + + if [[ $# -lt 1 ]]; then + usage + exit 1 + fi + + local output_dir="$1" + mkdir -p "$output_dir" + collect_journals "$output_dir" || true + exit 0 +} + +main "$@" diff --git a/scripts/ci/delete-vm-ns.sh b/scripts/ci/delete-vm-ns.sh new file mode 100755 index 0000000000000..a733d1b5e012e --- /dev/null +++ b/scripts/ci/delete-vm-ns.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPTS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" +# shellcheck source=../../scripts/ci/lib.sh +source "$SCRIPTS_ROOT/scripts/ci/lib.sh" + +usage() { + cat <>> Cleaning up VM scan e2e namespaces <<<" + ns_list="$(mktemp)" + if ! list_vm_scan_namespaces > "$ns_list"; then + info "Skipping namespace delete: kubectl get ns failed" + rm -f "$ns_list" + return 0 + fi + while IFS= read -r ns; do + [[ -z "$ns" ]] && continue + info "Deleting namespace ${ns}" + # 10s leaves room for the namespace list inside the 60s cleanup budget. + kubectl delete namespace "$ns" --wait=false --request-timeout=10s &1 || \ + info "Namespace delete for ${ns} failed or already removed" + done < "$ns_list" + rm -f "$ns_list" +} + +main() { + case "${1:-}" in + -h|--help) + usage + exit 0 + ;; + "") + ;; + *) + usage >&2 + exit 1 + ;; + esac + + cd "$SCRIPTS_ROOT" + delete_vm_scan_namespaces + exit 0 +} + +main "$@" diff --git a/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py b/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py index 695d9d77640fc..a77f458cb16b8 100755 --- a/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py +++ b/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py @@ -23,6 +23,9 @@ os.environ["SENSOR_HELM_MANAGED"] = "true" os.environ["INSTALL_CNV_OPERATOR"] = "true" os.environ["ROX_VIRTUAL_MACHINES"] = "true" +# Leave guests up so post-test can collect roxagent journals, then +# delete-vm-ns.sh deletes the test namespaces. +os.environ["VM_SCAN_SKIP_CLEANUP"] = "true" # Selectively enable vulnerability bundles to prevent timeouts of matcher not being ready in 40 minutes. # The rhel-vex bundle alone has ~3M records and can take >30 min to import. # Drops alpine, aws, debian, oracle, osv, photon, suse, ubuntu (unused for RHEL guests). @@ -36,10 +39,10 @@ class VMScanningPostTest(PostClusterTest): - """Extends standard post-test with CNV namespace logs.""" + """Extends standard post-test with CNV namespace logs and guest roxagent journals.""" def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(collect_vm_guest_logs=True, **kwargs) self.k8s_namespaces.extend([ "openshift-cnv", ]) diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index 5b6c0fd779515..7759df47cd625 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -2757,6 +2757,26 @@ _record_cluster_info() { set_ci_shared_export "cut_container_runtime_version" "$containerRuntimeVersion" } +# list_vm_scan_namespaces prints VM-scanning e2e namespace names, one per line. +# Returns 1 when kubectl cannot list namespaces, distinct from zero matches. +list_vm_scan_namespaces() { + local prefix="${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" + local out err + out="$(mktemp)" + err="$(mktemp)" + if ! kubectl --request-timeout=30s get ns \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \ + > "$out" 2>"$err"; then + info "kubectl get ns failed: $(<"$err")" >&2 + rm -f "$out" "$err" + return 1 + fi + rm -f "$err" + grep -E "^${prefix}(-|$)" "$out" || true + rm -f "$out" + return 0 +} + if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then if [[ "$#" -lt 1 ]]; then die "When invoked at the command line a method is required." diff --git a/tests/e2e/run-vm-scanning.sh b/tests/e2e/run-vm-scanning.sh index 2587aeb93f57f..e54198d332529 100755 --- a/tests/e2e/run-vm-scanning.sh +++ b/tests/e2e/run-vm-scanning.sh @@ -47,6 +47,8 @@ test_vm_scanning_e2e() { deploy_stackrox cd "$ROOT" + ensure_vm_scan_ssh_identity + persist_vm_scan_virtctl rm -f FAIL # Run the full VM scanning e2e suite. make -C tests TESTFLAGS="-race -p 1 -timeout 90m" vm-scanning-tests || touch FAIL diff --git a/tests/e2e/vm-scanning-lib.sh b/tests/e2e/vm-scanning-lib.sh index 58d771a00eb62..87d65f137746d 100755 --- a/tests/e2e/vm-scanning-lib.sh +++ b/tests/e2e/vm-scanning-lib.sh @@ -270,3 +270,79 @@ ensure_virtctl_binary_insecure() { is_CI || die "Secure virtctl download failed; refusing insecure curl -k fallback outside CI. Set VIRTCTL_PATH or fix cluster ingress trust material." _download_and_install_virtctl -k } + +# Host-side files shared between the Go suite (via exported env) and post-test +# guest journal collection. Kept out of test_outputs so the private key is not +# uploaded as a CI artifact. +vm_scan_e2e_dir() { + printf '%s\n' "${VM_SCAN_E2E_DIR:-/tmp/vm-scan-e2e}" +} + +# export_vm_scan_public_key fills VM_SSH_PUBLIC_KEY from identity when unset. +# LoadVMScanConfig requires both halves of the keypair. +export_vm_scan_public_key() { + local identity="$1" + if [[ -n "${VM_SSH_PUBLIC_KEY:-}" ]]; then + return 0 + fi + if [[ -f "${identity}.pub" ]]; then + VM_SSH_PUBLIC_KEY="$(<"${identity}.pub")" + export VM_SSH_PUBLIC_KEY + return 0 + fi + VM_SSH_PUBLIC_KEY="$(ssh-keygen -y -f "$identity")" + printf '%s\n' "$VM_SSH_PUBLIC_KEY" > "${identity}.pub" + export VM_SSH_PUBLIC_KEY +} + +# ensure_vm_scan_ssh_identity writes or reuses an SSH key under vm_scan_e2e_dir +# so the Go suite and post-test guest log collection share the same identity. +ensure_vm_scan_ssh_identity() { + local dir identity + dir="$(vm_scan_e2e_dir)" + mkdir -p "$dir" + identity="${dir}/ssh-identity" + + if [[ -n "${VM_SSH_PRIVATE_KEY:-}" ]]; then + printf '%s\n' "${VM_SSH_PRIVATE_KEY}" > "$identity" + chmod 600 "$identity" + export VM_SSH_PRIVATE_KEY_PATH="$identity" + export_vm_scan_public_key "$identity" + return 0 + fi + + if [[ -f "$identity" ]]; then + export VM_SSH_PRIVATE_KEY_PATH="$identity" + VM_SSH_PRIVATE_KEY="$(<"$identity")" + export VM_SSH_PRIVATE_KEY + export_vm_scan_public_key "$identity" + return 0 + fi + + ssh-keygen -t ed25519 -f "$identity" -N "" -C "stackrox-vm-scan-e2e" >/dev/null + chmod 600 "$identity" + VM_SSH_PRIVATE_KEY="$(<"$identity")" + VM_SSH_PUBLIC_KEY="$(<"${identity}.pub")" + export VM_SSH_PRIVATE_KEY VM_SSH_PUBLIC_KEY VM_SSH_PRIVATE_KEY_PATH="$identity" + info "Ephemeral VM scan SSH identity written to ${identity}" +} + +# persist_vm_scan_virtctl copies virtctl next to the SSH identity and records +# the resolved path so post-test can find it after the test process's PATH +# goes away. +persist_vm_scan_virtctl() { + local dir src dest + dir="$(vm_scan_e2e_dir)" + mkdir -p "$dir" + src="$(command -v virtctl)" || { + info "virtctl not on PATH; post-test guest journal collection will skip" + return 0 + } + printf '%s\n' "$src" > "${dir}/virtctl-path" + dest="${dir}/virtctl" + if cp "$src" "$dest" && chmod +x "$dest"; then + info "Persisted virtctl to ${dest} for post-test guest journal collection" + else + info "Could not copy virtctl to ${dest}; post-test will use ${src}" + fi +} diff --git a/tests/vm_scanning_suite_test.go b/tests/vm_scanning_suite_test.go index c2266513aa0cd..f529470ab7e9b 100644 --- a/tests/vm_scanning_suite_test.go +++ b/tests/vm_scanning_suite_test.go @@ -200,9 +200,9 @@ func (s *VMScanningSuite) TearDownSuite() { defer s.cancel() } - // When VM_SCAN_SKIP_CLEANUP is set, leave VMs and the namespace intact so a - // developer can SSH into the guests or inspect cluster state after a failure - // without having to re-provision from scratch. + // When VM_SCAN_SKIP_CLEANUP is set, skip VM/namespace deletion here so CI + // post-test can collect guest journals first; local runs use this to debug + // without re-provisioning. if s.cfg != nil && s.cfg.SkipCleanup { s.logf("teardown: VM_SCAN_SKIP_CLEANUP is set — skipping VM and namespace deletion (VMs and namespace left intact for debugging)") s.closeConn() From aa77803f713402dc1df32cd6a525f0fab8f01e85 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 1 Oct 2026 09:30:40 -0400 Subject: [PATCH 482/638] ROX-37354: Remove unique style from compliance scan schedule view (#23174) --- .../NotifierConfigurationView.tsx | 111 ------------------ .../Components/Reports/View/DeliveryView.tsx | 45 +------ .../View/DetailsDescriptionListGroups.tsx | 35 ++++++ .../Components/Reports/View/DetailsView.tsx | 28 ++--- ...fierConfigurationsDescriptionListGroup.tsx | 47 ++++++++ .../View/ScheduleDescriptionListGroup.tsx | 32 +++++ .../Schedules/ViewScanConfigDetail.tsx | 12 +- .../Schedules/Wizard/ReviewConfig.tsx | 44 ++----- .../compliance.scanConfigs.utils.tsx | 13 -- .../Schedules/components/ConfigDetails.tsx | 52 ++------ .../components/ScanConfigActivityView.tsx | 47 ++++++++ .../components/ScanConfigClustersView.tsx | 30 +++++ .../components/ScanConfigDeliveryView.tsx | 26 ++++ .../components/ScanConfigParametersView.tsx | 35 +----- 14 files changed, 255 insertions(+), 302 deletions(-) delete mode 100644 ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx create mode 100644 ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx create mode 100644 ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx create mode 100644 ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx create mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigActivityView.tsx create mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigClustersView.tsx create mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigDeliveryView.tsx diff --git a/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx b/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx deleted file mode 100644 index 9459a9416d830..0000000000000 --- a/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useState } from 'react'; -import type { ReactElement } from 'react'; -import { Button, Flex, FlexItem, Title } from '@patternfly/react-core'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; - -import { isDefaultEmailTemplate } from 'Components/EmailTemplate/EmailTemplate.utils'; -import EmailTemplateModal from 'Components/EmailTemplate/EmailTemplateModal'; -import type { TemplatePreviewArgs } from 'Components/EmailTemplate/EmailTemplateModal'; -import useIndexKey from 'hooks/useIndexKey'; -import type { NotifierConfiguration } from 'services/ReportsService.types'; - -export type NotifierConfigurationViewProps = { - headingLevel: 'h2' | 'h3'; - customBodyDefault: string; - customSubjectDefault: string; - notifierConfigurations: NotifierConfiguration[]; - renderTemplatePreview?: (args: TemplatePreviewArgs) => ReactElement; -}; - -function NotifierConfigurationView({ - headingLevel, - customBodyDefault, - customSubjectDefault, - notifierConfigurations, - renderTemplatePreview, -}: NotifierConfigurationViewProps): ReactElement { - const { keyFor } = useIndexKey(); - const [notifierConfigurationSelected, setNotifierConfigurationSelected] = - useState(null); - - return ( - <> - - - Delivery destinations - - - {notifierConfigurations.length === 0 ? ( - 'No delivery destinations' - ) : ( - - - - - - - - - - {notifierConfigurations.map((notifierConfiguration, index) => { - const { emailConfig, notifierName } = notifierConfiguration; - const { customBody, customSubject, mailingLists } = emailConfig; - const isDefaultEmailTemplateApplied = isDefaultEmailTemplate({ - customBody, - customSubject, - }); - return ( - - - - - - ); - })} - -
    Email notifierDistribution listEmail template
    {notifierName} - {mailingLists.join(', ')} - - -
    - )} -
    -
    - {notifierConfigurationSelected && ( - { - setNotifierConfigurationSelected(null); - }} - renderTemplatePreview={renderTemplatePreview} - title={ - isDefaultEmailTemplate({ - customBody: notifierConfigurationSelected.emailConfig.customBody, - customSubject: notifierConfigurationSelected.emailConfig.customSubject, - }) - ? 'Default template applied' - : 'Custom template applied' - } - /> - )} - - ); -} - -export default NotifierConfigurationView; diff --git a/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx b/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx index ea4548cf21741..3f88899f15278 100644 --- a/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx +++ b/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx @@ -1,20 +1,11 @@ import type { ReactElement } from 'react'; -import { - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Flex, - FlexItem, - Title, -} from '@patternfly/react-core'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; import type { BreakpointModifiers } from '@patternfly/react-core'; -import { formatRecurringSchedule } from 'utils/dateUtils'; - import type { DeliveryType } from '../reports.types'; -import NotifierConfigurationDescriptionList from './NotifierConfigurationDescriptionList'; +import NotifierConfigurationsDescriptionListGroup from './NotifierConfigurationsDescriptionListGroup'; +import ScheduleDescriptionListGroup from './ScheduleDescriptionListGroup'; export type DeliveryViewProps = { headingLevel: 'h2' | 'h3'; @@ -27,7 +18,6 @@ function DeliveryView({ horizontalTermWidthModifier, values, }: DeliveryViewProps): ReactElement { - /* eslint-disable react/no-array-index-key */ return ( @@ -39,37 +29,12 @@ function DeliveryView({ isHorizontal horizontalTermWidthModifier={horizontalTermWidthModifier} > - - Destinations - - {values.notifiers.length === 0 ? ( - '-' - ) : ( - - {values.notifiers.map((notifier, index) => ( - - ))} - - )} - - - - Schedule - - {values.schedule ? formatRecurringSchedule(values.schedule) : '-'} - - + + ); - /* eslint-enable react/no-array-index-key */ } export default DeliveryView; diff --git a/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx b/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx new file mode 100644 index 0000000000000..0d7e409dc218e --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx @@ -0,0 +1,35 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, +} from '@patternfly/react-core'; + +// Generic component for use cases: +// Parameters of compliance scan schedule configuration +// Details of vulnerability report configuration + +export type DetailsDescriptionListGroupsProps = { + description: string; + name: string; +}; + +function DetailsDescriptionListGroups({ + description, + name, +}: DetailsDescriptionListGroupsProps): ReactElement { + return ( + <> + + Name + {name || '-'} + + + Description + {description || '-'} + + + ); +} + +export default DetailsDescriptionListGroups; diff --git a/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx b/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx index 92d9f6b8e7b0a..5cde41afc9feb 100644 --- a/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx +++ b/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx @@ -1,17 +1,11 @@ import type { ReactElement } from 'react'; -import { - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Flex, - FlexItem, - Title, -} from '@patternfly/react-core'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; import type { BreakpointModifiers } from '@patternfly/react-core'; import type { DetailsType } from '../reports.types'; +import DetailsDescriptionListGroups from './DetailsDescriptionListGroups'; + export type DetailsViewProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; @@ -34,18 +28,10 @@ function DetailsView({ isHorizontal horizontalTermWidthModifier={horizontalTermWidthModifier} > - - Name - - {values.name || '-'} - - - - Description - - {values.description || '-'} - - +
    diff --git a/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx b/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx new file mode 100644 index 0000000000000..978195737cbb9 --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx @@ -0,0 +1,47 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Flex, +} from '@patternfly/react-core'; + +import type { NotifierConfiguration } from 'services/ReportsService.types'; + +import NotifierConfigurationDescriptionList from './NotifierConfigurationDescriptionList'; + +// Generic component for use cases: +// Delivery of compliance scan schedule configuration does not have schedules +// Delivery of vulnerability report configuration does have schedules + +export type NotifierConfigurationsDescriptionListGroupProps = { + notifiers: NotifierConfiguration[]; +}; + +function NotifierConfigurationsDescriptionListGroup({ + notifiers, +}: NotifierConfigurationsDescriptionListGroupProps): ReactElement { + /* eslint-disable react/no-array-index-key */ + return ( + + Destinations + + {notifiers.length === 0 ? ( + '-' + ) : ( + + {notifiers.map((notifier, index) => ( + + ))} + + )} + + + ); + /* eslint-enable react/no-array-index-key */ +} + +export default NotifierConfigurationsDescriptionListGroup; diff --git a/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx b/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx new file mode 100644 index 0000000000000..cc74e0ae9930d --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx @@ -0,0 +1,32 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, +} from '@patternfly/react-core'; + +import type { Schedule } from 'types/schedule.proto'; +import { formatRecurringSchedule } from 'utils/dateUtils'; + +// Generic component for use cases: +// Parameters of compliance scan schedule configuration +// Delivery of vulnerability report configuration + +export type ScheduleDescriptionListGroupProps = { + schedule: Schedule | null; +}; + +function ScheduleDescriptionListGroup({ + schedule, +}: ScheduleDescriptionListGroupProps): ReactElement { + return ( + + Schedule + + {schedule ? formatRecurringSchedule(schedule) : '-'} + + + ); +} + +export default ScheduleDescriptionListGroup; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx index 2eedb04bd92ba..097da5d7bd50b 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx @@ -5,8 +5,6 @@ import { AlertActionCloseButton, Breadcrumb, BreadcrumbItem, - Card, - CardBody, Flex, FlexItem, PageSection, @@ -211,15 +209,7 @@ function ViewScanConfigDetail({ {activeScanConfigTab === 'CONFIGURATION_DETAILS' && ( - - - - - + )} {activeScanConfigTab === 'ALL_REPORT_JOBS' && scanConfig?.id && ( diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx index b4c551c4d825b..d9f9d35ce75a1 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx @@ -1,26 +1,13 @@ import { useFormikContext } from 'formik'; import type { FormikContextType } from 'formik'; -import { - Alert, - Badge, - Divider, - Flex, - FlexItem, - List, - ListItem, - PageSection, - Title, -} from '@patternfly/react-core'; +import { Alert, Divider, Flex, FlexItem, PageSection, Title } from '@patternfly/react-core'; -import NotifierConfigurationView from 'Components/NotifierConfiguration/NotifierConfigurationView'; import type { ComplianceIntegration } from 'services/ComplianceIntegrationService'; -import { - convertFormikParametersToSchedule, - getBodyDefault, - getSubjectDefault, -} from '../compliance.scanConfigs.utils'; +import { convertFormikParametersToSchedule } from '../compliance.scanConfigs.utils'; import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; +import ScanConfigClustersView from '../components/ScanConfigClustersView'; +import ScanConfigDeliveryView from '../components/ScanConfigDeliveryView'; import ScanConfigParametersView from '../components/ScanConfigParametersView'; import ScanConfigProfilesView from '../components/ScanConfigProfilesView'; @@ -67,7 +54,7 @@ function ReviewConfig({ clusters, errorMessage }: ReviewConfigProps) { - - - Clusters - {selectedClusters.length} - - - {selectedClusters.map((cluster) => ( - {cluster.clusterName} - ))} - - + - + - - Last scanned - - {scanConfig.lastExecutedTime - ? getTimeWithHourMinuteFromISO8601(scanConfig.lastExecutedTime) - : 'Scan is in progress'} - - - - Last updated - - {getTimeWithHourMinuteFromISO8601(scanConfig.lastUpdatedTime)} - - - + /> + - ); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigActivityView.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigActivityView.tsx new file mode 100644 index 0000000000000..3ca82196f8056 --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigActivityView.tsx @@ -0,0 +1,47 @@ +import type { ReactElement } from 'react'; +import { + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Flex, + Title, +} from '@patternfly/react-core'; + +import type { ComplianceScanConfigurationStatus } from 'services/ComplianceScanConfigurationService'; + +import { getTimeWithHourMinuteFromISO8601 } from '../compliance.scanConfigs.utils'; + +type ScanConfigActivityViewProps = { + headingLevel: 'h2' | 'h3'; + scanConfig: ComplianceScanConfigurationStatus; +}; + +function ScanConfigActivityView({ + headingLevel, + scanConfig, +}: ScanConfigActivityViewProps): ReactElement { + return ( + + Activity + + + Last scanned + + {scanConfig.lastExecutedTime + ? getTimeWithHourMinuteFromISO8601(scanConfig.lastExecutedTime) + : 'Scan is in progress'} + + + + Last updated + + {getTimeWithHourMinuteFromISO8601(scanConfig.lastUpdatedTime)} + + + + + ); +} + +export default ScanConfigActivityView; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigClustersView.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigClustersView.tsx new file mode 100644 index 0000000000000..73a4c2c2df56d --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigClustersView.tsx @@ -0,0 +1,30 @@ +import type { ReactElement } from 'react'; +import { Badge, Flex, List, ListItem, Title } from '@patternfly/react-core'; + +import type { ComplianceIntegration } from 'services/ComplianceIntegrationService'; + +type ScanConfigClustersViewProps = { + headingLevel: 'h2' | 'h3'; + clusters: ComplianceIntegration[]; +}; + +function ScanConfigClustersView({ + headingLevel, + clusters, +}: ScanConfigClustersViewProps): ReactElement { + return ( + + + Clusters + {clusters.length} + + + {clusters.map((cluster) => ( + {cluster.clusterName} + ))} + + + ); +} + +export default ScanConfigClustersView; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigDeliveryView.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigDeliveryView.tsx new file mode 100644 index 0000000000000..071648d5a5105 --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigDeliveryView.tsx @@ -0,0 +1,26 @@ +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, Title } from '@patternfly/react-core'; + +import NotifierConfigurationsDescriptionListGroup from 'Components/Reports/View/NotifierConfigurationsDescriptionListGroup'; +import type { NotifierConfiguration } from 'services/ReportsService.types'; + +type ScanConfigDeliveryViewProps = { + headingLevel: 'h2' | 'h3'; + notifiers: NotifierConfiguration[]; +}; + +function ScanConfigDeliveryView({ + headingLevel, + notifiers, +}: ScanConfigDeliveryViewProps): ReactElement { + return ( + + Delivery + + + + + ); +} + +export default ScanConfigDeliveryView; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigParametersView.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigParametersView.tsx index 16339575878c4..9721427fadf67 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigParametersView.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/components/ScanConfigParametersView.tsx @@ -1,22 +1,15 @@ -import type { ReactElement, ReactNode } from 'react'; -import { - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Flex, - Title, -} from '@patternfly/react-core'; +import type { ReactElement } from 'react'; +import { DescriptionList, Flex, Title } from '@patternfly/react-core'; +import DetailsDescriptionListGroups from 'Components/Reports/View/DetailsDescriptionListGroups'; +import ScheduleDescriptionListGroup from 'Components/Reports/View/ScheduleDescriptionListGroup'; import type { Schedule } from 'types/schedule.proto'; -import { formatRecurringSchedule } from 'utils/dateUtils'; type ScanConfigParametersViewProps = { headingLevel: 'h2' | 'h3'; scanName: string; description?: string; scanSchedule: Schedule; - children?: ReactNode; }; function ScanConfigParametersView({ @@ -24,29 +17,13 @@ function ScanConfigParametersView({ headingLevel, scanName, scanSchedule, - children, }: ScanConfigParametersViewProps): ReactElement { return ( Parameters - - Name - {scanName} - - - Description - - {description || No description} - - - - Schedule - - {formatRecurringSchedule(scanSchedule)} - - - {children} + + ); From 7c28d788d3d797cc5201fb608a934aaed038bc6e Mon Sep 17 00:00:00 2001 From: rhacs-bot <148914812+rhacs-bot@users.noreply.github.com> Date: Thu, 1 Oct 2026 15:50:08 +0200 Subject: [PATCH 483/638] chore(release): Add 4.10.10 to scanner updater configuration (#23189) Co-authored-by: vladbologa --- scanner/updater/version/RELEASE_VERSION | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index 9461ad2816d53..cbb5c73927c9d 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -8,6 +8,7 @@ # offline bundle for that release. 4.10.0 4.10.1 +4.10.10 4.10.2 4.10.3 4.10.4 From e2079d08b9ceceaec269271d3c0955b347bdd558 Mon Sep 17 00:00:00 2001 From: David Shrewsberry <99685630+dashrews78@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:01:32 -0400 Subject: [PATCH 484/638] fix(test): one source of byodb flakes (#23190) --- tests/byodb/lib.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/byodb/lib.sh b/tests/byodb/lib.sh index 55fe4e0d82f90..38ebe7f5e7ed9 100644 --- a/tests/byodb/lib.sh +++ b/tests/byodb/lib.sh @@ -19,7 +19,8 @@ deploy_external_postgres() { kubectl create namespace database envsubst < ./tests/byodb/simple-postgres.yaml | kubectl apply -f - - kubectl wait --for=condition=Ready pod -l app=postgres -n database --timeout=180s + # The StatefulSet exists, but its pod may not have been created yet. + kubectl rollout status statefulset/postgres -n database --timeout=180s } preamble() { From 957a0083cdb74288d4a039907b1c47213dc149fd Mon Sep 17 00:00:00 2001 From: Yann Brillouet <91869377+rhybrillou@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:36:05 +0200 Subject: [PATCH 485/638] chore(deps): change github actionlint to a repo with active support (#23191) --- .github/workflows/cut-rc.yml | 2 ++ .github/workflows/release-ci.yaml | 4 ++++ Makefile | 2 +- tools/linters/go.mod | 12 ++++++------ tools/linters/go.sum | 21 ++++++++++----------- tools/linters/tools-import.go | 2 +- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cut-rc.yml b/.github/workflows/cut-rc.yml index 6baf9ba5875a9..5ee3dc680dc37 100644 --- a/.github/workflows/cut-rc.yml +++ b/.github/workflows/cut-rc.yml @@ -37,6 +37,8 @@ on: default: true type: boolean +cache-mode: read + env: main_branch: ${{github.event.repository.default_branch}} script_url: /repos/${{github.repository}}/contents/.github/workflows/scripts/common.sh?ref=${{ github.ref_name }} diff --git a/.github/workflows/release-ci.yaml b/.github/workflows/release-ci.yaml index 32c8cd7fb54c5..7a631fd36b3ad 100644 --- a/.github/workflows/release-ci.yaml +++ b/.github/workflows/release-ci.yaml @@ -16,6 +16,10 @@ jobs: echo "Ref: ${{ github.ref_name }}" >> "$GITHUB_STEP_SUMMARY" build: + permissions: + actions: read + contents: read + pull-requests: write name: Build uses: ./.github/workflows/build.yaml secrets: inherit diff --git a/Makefile b/Makefile index 7ab67e2cd9c67..e774457dc668f 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,7 @@ $(call go-tool, MOCKGEN_BIN, go.uber.org/mock/mockgen) $(call go-tool, GO_JUNIT_REPORT_BIN, github.com/jstemmer/go-junit-report/v2, tools/test) $(call go-tool, PROTOLOCK_BIN, github.com/nilslice/protolock/cmd/protolock, tools/linters) $(call go-tool, RATCHET_BIN, github.com/sethvargo/ratchet, tools/linters) -$(call go-tool, ACTIONLINT_BIN, github.com/rhysd/actionlint/cmd/actionlint, tools/linters) +$(call go-tool, ACTIONLINT_BIN, actionlint.kjanat.dev/cmd/actionlint, tools/linters) $(call go-tool, GOVULNCHECK_BIN, golang.org/x/vuln/cmd/govulncheck, tools/linters) $(call go-tool, IMAGE_PREFETCHER_DEPLOY_BIN, github.com/stackrox/image-prefetcher/deploy, tools/test) $(call go-tool, PROMETHEUS_METRIC_PARSER_BIN, github.com/stackrox/prometheus-metric-parser, tools/test) diff --git a/tools/linters/go.mod b/tools/linters/go.mod index 3c6174baa1a78..4d3dd838c74ef 100644 --- a/tools/linters/go.mod +++ b/tools/linters/go.mod @@ -1,11 +1,11 @@ module github.com/stackrox/stackrox/tools/linters -go 1.26.3 +go 1.26.7 require ( + actionlint.kjanat.dev v1.17.0 github.com/golangci/golangci-lint/v2 v2.13.2 github.com/nilslice/protolock v0.17.0 - github.com/rhysd/actionlint v1.7.12 github.com/sethvargo/ratchet v0.12.0 golang.org/x/vuln v1.8.0 ) @@ -146,9 +146,9 @@ require ( github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.24 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mattn/go-runewidth v0.0.28 // indirect + github.com/mattn/go-shellwords v1.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.15.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -228,7 +228,7 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/tools v0.50.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/tools/linters/go.sum b/tools/linters/go.sum index 1efaa36ce124b..3b5529ba3af82 100644 --- a/tools/linters/go.sum +++ b/tools/linters/go.sum @@ -2,6 +2,8 @@ 4d63.com/gocheckcompilerdirectives v1.4.0/go.mod h1:9ZOAiMOjqC/nRwci2fcUXVHUNLG/cH6r6rhUh+jTFtQ= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +actionlint.kjanat.dev v1.17.0 h1:wzkg6amflGqcF4QlkSERw0L1kdGSb/z2KZ7aidlgD74= +actionlint.kjanat.dev v1.17.0/go.mod h1:9BVNedMJ1sZTdAiszWaOl+H4IiKD9PfKrkwqpxeB+ao= charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ= charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -452,12 +454,12 @@ github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= -github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU= +github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= +github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= +github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= @@ -544,8 +546,6 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.3.0 h1:PM+XYvyxIj3bo+kobJfFTdTuU3Lmfu96mKDbyHDbRt8= github.com/raeperd/recvcheck v0.3.0/go.mod h1:PZNwG+HztFYMH2ZPq0Hu3QgkV2yiA6VrtNz9c1fXWJo= -github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= -github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -831,7 +831,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518 h1:F5BWKvW126NXR74uxkxuc1jQHhm/rwm/J3rSiFyuRs4= @@ -847,8 +846,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/tools/linters/tools-import.go b/tools/linters/tools-import.go index c25dd3d3ed84e..ba96ee84647f5 100644 --- a/tools/linters/tools-import.go +++ b/tools/linters/tools-import.go @@ -8,9 +8,9 @@ package tools import ( // Tool dependencies, not used anywhere in the code. + _ "actionlint.kjanat.dev/cmd/actionlint" _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" _ "github.com/nilslice/protolock" - _ "github.com/rhysd/actionlint/cmd/actionlint" _ "github.com/sethvargo/ratchet" _ "golang.org/x/vuln/cmd/govulncheck" ) From c47677e324073ebeb4de66f7dc8219ab76a3fc08 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 1 Oct 2026 14:06:51 -0400 Subject: [PATCH 486/638] ROX-37298: Make compliance scan wizard step name and title consistent (#23108) --- .../Schedules/Wizard/ReportConfiguration.tsx | 2 +- .../Schedules/Wizard/ScanConfigOptions.tsx | 3 --- .../Schedules/Wizard/ScanConfigWizardForm.tsx | 9 +++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx index 93093abad42ee..13ef9885a0edd 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx @@ -19,7 +19,7 @@ function ReportConfiguration(): ReactElement { - Report + Delivery Optionally configure e-mail delivery destinations for reports diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx index cbcaef265f594..ea400c613ecde 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx @@ -108,9 +108,6 @@ function ScanConfigOptions(): ReactElement { - - - diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx index 3949b3893004a..e67be6e7de4e8 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigWizardForm.tsx @@ -27,13 +27,14 @@ import useFormikScanConfig from './useFormikScanConfig'; import { convertFormikToScanConfig } from '../compliance.scanConfigs.utils'; import type { ScanConfigFormValues, SchedulePageAction } from '../compliance.scanConfigs.utils'; -const PARAMETERS = 'Set parameters'; +// Parameters (and so on) for step label are consistent with the corresponding step heading and view heading. +const PARAMETERS = 'Parameters'; const PARAMETERS_ID = 'parameters'; -const SELECT_CLUSTERS = 'Select clusters'; +const SELECT_CLUSTERS = 'Clusters'; const SELECT_CLUSTERS_ID = 'clusters'; -const SELECT_PROFILES = 'Select profiles'; +const SELECT_PROFILES = 'Profiles'; const SELECT_PROFILES_ID = 'profiles'; -const CONFIGURE_REPORT = 'Configure report'; +const CONFIGURE_REPORT = 'Delivery'; const CONFIGURE_REPORT_ID = 'report'; const REVIEW_CONFIG = 'Review'; const REVIEW_CONFIG_ID = 'review'; From e8a28befcac89bdfb0c608cf14a9a2c4cd572739 Mon Sep 17 00:00:00 2001 From: Saif Chaudhry Date: Thu, 1 Oct 2026 11:34:46 -0700 Subject: [PATCH 487/638] chore(ui): delete unread search autocomplete Redux (#22921) Signed-off-by: Saif Chaudhry --- .../Components/SearchInput/SearchInput.jsx | 4 -- .../URLSearchInputWithAutocomplete.jsx | 11 +--- ui/apps/platform/src/reducers/index.js | 4 -- .../src/reducers/searchAutocomplete.js | 57 ------------------- ui/apps/platform/src/sagas/index.js | 3 +- .../src/sagas/searchAutocompleteSagas.js | 24 -------- 6 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 ui/apps/platform/src/reducers/searchAutocomplete.js delete mode 100644 ui/apps/platform/src/sagas/searchAutocompleteSagas.js diff --git a/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx b/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx index fc8373d45b82d..99fc6726f6590 100644 --- a/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx +++ b/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx @@ -32,7 +32,6 @@ export const searchInputPropTypes = { sendAutoCompleteRequest: PropTypes.func, clearAutoComplete: PropTypes.func, autoCompleteCategories: PropTypes.arrayOf(PropTypes.string), - setAllSearchOptions: PropTypes.func, isDisabled: PropTypes.bool, prependAutocompleteQuery: PropTypes.arrayOf( PropTypes.shape({ @@ -53,7 +52,6 @@ export const searchInputDefaultProps = { sendAutoCompleteRequest: null, clearAutoComplete: null, autoCompleteCategories: [], - setAllSearchOptions: () => {}, isDisabled: false, prependAutocompleteQuery: [], }; @@ -108,8 +106,6 @@ class SearchInput extends Component { } sendAutoCompleteRequest = (searchOptions, input) => { - this.props.setAllSearchOptions(searchOptions); - // Don't populate autocomplete if the text box is totally empty, // since we want people to see just the chips in that case. if (!searchOptions.length && !input.length) { diff --git a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx index 642611d65ef5f..575335cd00c91 100644 --- a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx +++ b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx @@ -3,10 +3,8 @@ import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import PropTypes from 'prop-types'; import { components } from 'react-select'; import queryString from 'qs'; -import { connect } from 'react-redux'; import { Filter } from 'react-feather'; -import { actions as searchAutoCompleteActions } from 'reducers/searchAutocomplete'; import { Creatable } from 'Components/ReactSelect'; import searchOptionsToQuery from 'services/searchOptionsToQuery'; import searchContext from 'Containers/searchContext'; @@ -99,7 +97,6 @@ export const removeValuesForKey = (oldOptions, newOptions) => { const URLSearchInputWithAutocomplete = ({ autoCompleteResults, categoryOptions, - setAllSearchOptions, clearAutocomplete, fetchAutocomplete, placeholder, @@ -235,7 +232,6 @@ const URLSearchInputWithAutocomplete = ({ function updateAutocompleteState(searchOptions) { return (input) => { - setAllSearchOptions(searchOptions); if (searchOptions.length === 0) { if (clearAutocomplete) { clearAutocomplete(); @@ -311,7 +307,6 @@ URLSearchInputWithAutocomplete.propTypes = { autoCompleteResults: PropTypes.arrayOf(PropTypes.string), fetchAutocomplete: PropTypes.func, clearAutocomplete: PropTypes.func, - setAllSearchOptions: PropTypes.func.isRequired, prependAutocompleteQuery: PropTypes.arrayOf( PropTypes.shape({ value: PropTypes.string, @@ -328,8 +323,4 @@ URLSearchInputWithAutocomplete.defaultProps = { prependAutocompleteQuery: [], }; -const mapDispatchToProps = { - setAllSearchOptions: searchAutoCompleteActions.setAllSearchOptions, -}; - -export default connect(null, mapDispatchToProps)(URLSearchInputWithAutocomplete); +export default URLSearchInputWithAutocomplete; diff --git a/ui/apps/platform/src/reducers/index.js b/ui/apps/platform/src/reducers/index.js index 98a4f0139e04f..17724d264e4ab 100644 --- a/ui/apps/platform/src/reducers/index.js +++ b/ui/apps/platform/src/reducers/index.js @@ -7,7 +7,6 @@ import auth, { selectors as authSelectors } from './auth'; import invite, { selectors as inviteSelectors } from './invite'; import notifications, { selectors as notificationSelectors } from './notifications'; import roles, { selectors as roleSelectors } from './roles'; -import searchAutoComplete, { selectors as searchAutoCompleteSelectors } from './searchAutocomplete'; import serverResponseStatus, { selectors as serverResponseStatusSelectors, } from './serverResponseStatus'; @@ -25,7 +24,6 @@ const appReducer = combineReducers({ invite, notifications, roles, - searchAutoComplete, serverResponseStatus, loading, groups, @@ -49,7 +47,6 @@ const getAuth = (state) => getApp(state).auth; const getInvite = (state) => getApp(state).invite; const getNotifications = (state) => getApp(state).notifications; const getRoles = (state) => getApp(state).roles; -const getSearchAutocomplete = (state) => getApp(state).searchAutoComplete; const getServerResponseStatus = (state) => getApp(state).serverResponseStatus; const getLoadingStatus = (state) => getApp(state).loading; @@ -61,7 +58,6 @@ const boundSelectors = { ...bindSelectors(getInvite, inviteSelectors), ...bindSelectors(getNotifications, notificationSelectors), ...bindSelectors(getRoles, roleSelectors), - ...bindSelectors(getSearchAutocomplete, searchAutoCompleteSelectors), ...bindSelectors(getServerResponseStatus, serverResponseStatusSelectors), ...bindSelectors(getLoadingStatus, loadingSelectors), diff --git a/ui/apps/platform/src/reducers/searchAutocomplete.js b/ui/apps/platform/src/reducers/searchAutocomplete.js deleted file mode 100644 index 2f6196a4200eb..0000000000000 --- a/ui/apps/platform/src/reducers/searchAutocomplete.js +++ /dev/null @@ -1,57 +0,0 @@ -import { combineReducers } from 'redux'; -import isEqual from 'lodash/isEqual'; - -export const types = { - SEND_AUTOCOMPLETE_REQUEST: 'autocomplete/SEND_AUTOCOMPLETE_REQUEST', - RECORD_AUTOCOMPLETE_RESPONSE: 'autocomplete/RECORD_AUTOCOMPLETE_RESPONSE', - CLEAR_AUTOCOMPLETE: 'autocomplete/CLEAR_AUTOCOMPLETE', - SET_ALL_SEARCH_OPTIONS: 'search/SET_ALL_SEARCH_OPTIONS', -}; - -export const actions = { - sendAutoCompleteRequest: (request) => ({ - type: types.SEND_AUTOCOMPLETE_REQUEST, - ...request, - }), - recordAutoCompleteResponse: (autoCompleteResults) => ({ - type: types.RECORD_AUTOCOMPLETE_RESPONSE, - autoCompleteResults, - }), - clearAutoComplete: () => ({ type: types.CLEAR_AUTOCOMPLETE }), - setAllSearchOptions: (options) => ({ type: types.SET_ALL_SEARCH_OPTIONS, options }), -}; - -const autoCompleteResults = (state = [], action) => { - if (action.type === types.RECORD_AUTOCOMPLETE_RESPONSE) { - return action.autoCompleteResults; - } - if (action.type === types.CLEAR_AUTOCOMPLETE) { - return []; - } - return state; -}; - -const allSearchOptions = (state = [], action) => { - if (action.type === 'search/SET_ALL_SEARCH_OPTIONS') { - const { options } = action; - if (options.length % 2 === 0) { - return isEqual(options, state) ? state : options; - } - } - return state; -}; - -const reducer = combineReducers({ - autoCompleteResults, - allSearchOptions, -}); - -const getAutoCompleteResults = (state) => state.autoCompleteResults; -const getAllSearchOptions = (state) => state.allSearchOptions; - -export const selectors = { - getAutoCompleteResults, - getAllSearchOptions, -}; - -export default reducer; diff --git a/ui/apps/platform/src/sagas/index.js b/ui/apps/platform/src/sagas/index.js index 769f824e4a97c..96f5cfd2de5e9 100644 --- a/ui/apps/platform/src/sagas/index.js +++ b/ui/apps/platform/src/sagas/index.js @@ -2,10 +2,9 @@ import { all, fork } from 'redux-saga/effects'; import authProviders from './authSagas'; import roles from './roleSagas'; -import searchAutoComplete from './searchAutocompleteSagas'; import groups from './groupSagas'; export default function* root() { - yield all([fork(authProviders), fork(roles), fork(searchAutoComplete), fork(groups)]); + yield all([fork(authProviders), fork(roles), fork(groups)]); } diff --git a/ui/apps/platform/src/sagas/searchAutocompleteSagas.js b/ui/apps/platform/src/sagas/searchAutocompleteSagas.js deleted file mode 100644 index 6e1f507029d63..0000000000000 --- a/ui/apps/platform/src/sagas/searchAutocompleteSagas.js +++ /dev/null @@ -1,24 +0,0 @@ -import { call, fork, put, take } from 'redux-saga/effects'; - -import { fetchAutoCompleteResults as serviceFetchAutoCompleteResults } from 'services/SearchService'; -import { actions, types } from 'reducers/searchAutocomplete'; - -function* getAutoCompleteResults(request) { - try { - const result = yield call(serviceFetchAutoCompleteResults, request); - yield put(actions.recordAutoCompleteResponse(result)); - } catch { - yield put(actions.recordAutoCompleteResponse([])); - } -} - -function* watchFetchRequest() { - while (true) { - const request = yield take(types.SEND_AUTOCOMPLETE_REQUEST); - yield fork(getAutoCompleteResults, request); - } -} - -export default function* searchAutoComplete() { - yield fork(watchFetchRequest); -} From 19ae1838654af209015ca8e179746efedd16fefb Mon Sep 17 00:00:00 2001 From: Khushboo Sancheti <42253461+clickboo@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:46:35 -0700 Subject: [PATCH 488/638] ROX-37330,ROX-37331: Fix incorrect init container protos,CR generation (#23135) Co-authored-by: Cursor --- central/graphql/resolvers/generated.go | 19 +++ .../customresource/custom_resource_test.go | 3 + central/policy/customresource/policy.go | 20 ++- .../testdata/custom_resource.yaml | 3 + central/policy/service/validator.go | 17 +-- central/policy/service/validator_test.go | 23 ++- .../api/v1alpha1/policy_types.go | 14 +- .../api/v1alpha1/policy_types_test.go | 6 +- .../api/v1alpha1/zz_generated.deepcopy.go | 2 +- .../config.stackrox.io_securitypolicies.yaml | 7 +- generated/api/v1/alert_service.swagger.json | 18 +-- .../api/v1/detection_service.swagger.json | 10 +- generated/api/v1/policy_service.swagger.json | 18 +-- generated/storage/policy.pb.go | 133 ++++++++++++------ generated/storage/policy_vtproto.pb.go | 22 +-- .../config.stackrox.io_securitypolicies.yaml | 7 +- .../config.stackrox.io_securitypolicies.yaml | 7 +- pkg/booleanpolicy/filter/compile_test.go | 19 +-- pkg/booleanpolicy/filter/container_type.go | 12 +- pkg/detection/compiled_policy_test.go | 100 +++++++++++++ proto/storage/policy.proto | 10 +- proto/storage/proto.lock | 13 +- tests/init_container_test.go | 90 ++++++++++-- tests/policy_as_code_test.go | 4 +- .../config-as-code-helper/main.go | 6 + .../Policies/Detail/PolicyDetailContent.tsx | 1 + .../Policies/Detail/PolicyFiltersSection.tsx | 22 +-- .../Table/PolicyEvaluationFilterLabels.tsx | 2 +- .../Wizard/StepFilters/PolicyFiltersForm.tsx | 40 +++--- .../Policies/policies.utils.test.ts | 14 ++ .../src/Containers/Policies/policies.utils.ts | 12 ++ ui/apps/platform/src/types/policy.proto.ts | 4 +- 32 files changed, 497 insertions(+), 181 deletions(-) diff --git a/central/graphql/resolvers/generated.go b/central/graphql/resolvers/generated.go index 38b997da3faf7..9098714ce5e52 100644 --- a/central/graphql/resolvers/generated.go +++ b/central/graphql/resolvers/generated.go @@ -1448,6 +1448,7 @@ func registerGeneratedTypes(builder generator.SchemaBuilder) { "clusterName: String!", "namespaceName: String!", })) + generator.RegisterProtoEnum(builder, reflect.TypeOf(storage.SkipContainerType(0))) utils.Must(builder.AddType("SlimUser", []string{ "id: ID!", "name: String!", @@ -15629,6 +15630,24 @@ func (resolver *simpleAccessScope_Rules_NamespaceResolver) NamespaceName(ctx con return value } +func toSkipContainerType(value *string) storage.SkipContainerType { + if value != nil { + return storage.SkipContainerType(storage.SkipContainerType_value[*value]) + } + return storage.SkipContainerType(0) +} + +func toSkipContainerTypes(values *[]string) []storage.SkipContainerType { + if values == nil { + return nil + } + output := make([]storage.SkipContainerType, len(*values)) + for i, v := range *values { + output[i] = toSkipContainerType(&v) + } + return output +} + type slimUserResolver struct { ctx context.Context root *Resolver diff --git a/central/policy/customresource/custom_resource_test.go b/central/policy/customresource/custom_resource_test.go index 94cf6c6f9e545..a648494d355fe 100644 --- a/central/policy/customresource/custom_resource_test.go +++ b/central/policy/customresource/custom_resource_test.go @@ -78,6 +78,9 @@ func getTestPolicy() *storage.Policy { ClusterLabel: &storage.Scope_Label{Key: "env", Value: "prod"}, NamespaceLabel: &storage.Scope_Label{Key: "team", Value: "platform"}, }) + p.EvaluationFilter = &storage.EvaluationFilter{ + SkipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, + } return p } diff --git a/central/policy/customresource/policy.go b/central/policy/customresource/policy.go index 233b500a5c66c..9033f734c2ba6 100644 --- a/central/policy/customresource/policy.go +++ b/central/policy/customresource/policy.go @@ -111,6 +111,22 @@ func convertPolicySection(p *storage.PolicySection) *PolicySection { } } +// EvaluationFilter represents storage.EvaluationFilter in the Custom Resource. +type EvaluationFilter struct { + SkipContainerTypes []string `yaml:"skipContainerTypes,omitempty"` +} + +// convertEvaluationFilter Converts storage.EvaluationFilter to *EvaluationFilter +func convertEvaluationFilter(p *storage.EvaluationFilter) *EvaluationFilter { + if p == nil { + return nil + } + + return &EvaluationFilter{ + SkipContainerTypes: sliceutils.StringSlice(p.SkipContainerTypes...), + } +} + // Policy represents storage.Policy in the Custom Resource. type Policy struct { Name string `yaml:"policyName"` @@ -131,7 +147,7 @@ type Policy struct { CriteriaLocked bool `yaml:"criteriaLocked"` MitreVectorsLocked bool `yaml:"mitreVectorsLocked"` IsDefault bool `yaml:"isDefault"` - EvaluationFilter *storage.EvaluationFilter `yaml:"evaluationFilter,omitempty"` + EvaluationFilter *EvaluationFilter `yaml:"evaluationFilter,omitempty"` } // convertPolicy Converts storage.Policy to *Policy @@ -159,7 +175,7 @@ func convertPolicy(p *storage.Policy) *Policy { CriteriaLocked: p.CriteriaLocked, MitreVectorsLocked: p.MitreVectorsLocked, IsDefault: p.IsDefault, - EvaluationFilter: p.EvaluationFilter, + EvaluationFilter: convertEvaluationFilter(p.EvaluationFilter), } } diff --git a/central/policy/customresource/testdata/custom_resource.yaml b/central/policy/customresource/testdata/custom_resource.yaml index 4de610ec37a80..b70c12c23ebb5 100644 --- a/central/policy/customresource/testdata/custom_resource.yaml +++ b/central/policy/customresource/testdata/custom_resource.yaml @@ -147,3 +147,6 @@ spec: criteriaLocked: false mitreVectorsLocked: false isDefault: false + evaluationFilter: + skipContainerTypes: + - SKIP_INIT diff --git a/central/policy/service/validator.go b/central/policy/service/validator.go index 90f6dc87ddc97..f9ee90b0afdb4 100644 --- a/central/policy/service/validator.go +++ b/central/policy/service/validator.go @@ -510,14 +510,15 @@ func (s *policyValidator) validateEvaluationFilter(policy *storage.Policy) error if len(policy.GetEvaluationFilter().GetSkipContainerTypes()) == 0 { return nil } - if policies.AppliesAtBuildTime(policy) && !policies.AppliesAtDeployTime(policy) && !policies.AppliesAtRunTime(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to build-only policies") - } - if s.isAuditEventPolicy(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to audit log event policies") - } - if s.isNodeEventPolicy(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to node event policies") + if !containerTypeFilterApplicable(policy) { + return errors.New("container type filters apply only to deploy-time policies and runtime policies with a deployment event source") } return nil } + +func containerTypeFilterApplicable(policy *storage.Policy) bool { + if policies.AppliesAtDeployTime(policy) { + return true + } + return policies.AppliesAtRunTime(policy) && policy.GetEventSource() == storage.EventSource_DEPLOYMENT_EVENT +} diff --git a/central/policy/service/validator_test.go b/central/policy/service/validator_test.go index 1e317de9785e4..e842a42bec769 100644 --- a/central/policy/service/validator_test.go +++ b/central/policy/service/validator_test.go @@ -1379,48 +1379,45 @@ func (s *PolicyValidatorTestSuite) TestValidateEvaluationFilter() { tests := map[string]struct { lifecycleStages []storage.LifecycleStage eventSource storage.EventSource - skipContainerTypes []storage.ContainerType + skipContainerTypes []storage.SkipContainerType expectError bool }{ "build-only with skip init is rejected": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD}, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, expectError: true, }, "deploy with skip init is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, }, "build + deploy with skip init is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD, storage.LifecycleStage_DEPLOY}, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, - }, - "runtime with skip init is allowed": { - lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, }, "build-only with no filter is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD}, }, - "no lifecycle stages with skip init is allowed": { - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + "no lifecycle stages with skip init is rejected": { + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, + expectError: true, }, "audit log event with skip init is rejected": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, eventSource: storage.EventSource_AUDIT_LOG_EVENT, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, expectError: true, }, "node event with skip init is rejected": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, eventSource: storage.EventSource_NODE_EVENT, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, expectError: true, }, "deployment event with skip init is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, eventSource: storage.EventSource_DEPLOYMENT_EVENT, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, }, } diff --git a/config-controller/api/v1alpha1/policy_types.go b/config-controller/api/v1alpha1/policy_types.go index c21b47a620ea6..83694f97aedcc 100644 --- a/config-controller/api/v1alpha1/policy_types.go +++ b/config-controller/api/v1alpha1/policy_types.go @@ -152,11 +152,15 @@ type MitreAttackVectors struct { Techniques []string `json:"techniques,omitempty"` } -// +kubebuilder:validation:Enum=REGULAR;INIT -type ContainerType string +// SkipContainerType lists container kinds a policy may skip during evaluation. +// +kubebuilder:validation:Enum=SKIP_INIT +type SkipContainerType string +// EvaluationFilter pre-filters which entities a policy evaluates. type EvaluationFilter struct { - SkipContainerTypes []ContainerType `json:"skipContainerTypes,omitempty"` + // SkipContainerTypes lists container kinds to skip during evaluation. + // +optional + SkipContainerTypes []SkipContainerType `json:"skipContainerTypes,omitempty"` } type SecurityPolicyConditionType string @@ -433,9 +437,9 @@ func (p SecurityPolicySpec) ToProtobuf(caches map[CacheType]map[string]string) ( if p.EvaluationFilter != nil && len(p.EvaluationFilter.SkipContainerTypes) > 0 { protoFilter := &storage.EvaluationFilter{} for _, ct := range p.EvaluationFilter.SkipContainerTypes { - val, found := storage.ContainerType_value[string(ct)] + val, found := storage.SkipContainerType_value[string(ct)] if found { - protoFilter.SkipContainerTypes = append(protoFilter.SkipContainerTypes, storage.ContainerType(val)) + protoFilter.SkipContainerTypes = append(protoFilter.SkipContainerTypes, storage.SkipContainerType(val)) } } proto.EvaluationFilter = protoFilter diff --git a/config-controller/api/v1alpha1/policy_types_test.go b/config-controller/api/v1alpha1/policy_types_test.go index 2c69dabf34afd..31f5618cb145c 100644 --- a/config-controller/api/v1alpha1/policy_types_test.go +++ b/config-controller/api/v1alpha1/policy_types_test.go @@ -232,12 +232,12 @@ func TestConditionUpdates(t *testing.T) { func TestToProtobufEvaluationFilter(t *testing.T) { tests := map[string]struct { filter *EvaluationFilter - expectedTypes []storage.ContainerType + expectedTypes []storage.SkipContainerType expectNilProto bool }{ "skip init containers": { - filter: &EvaluationFilter{SkipContainerTypes: []ContainerType{"INIT"}}, - expectedTypes: []storage.ContainerType{storage.ContainerType_INIT}, + filter: &EvaluationFilter{SkipContainerTypes: []SkipContainerType{"SKIP_INIT"}}, + expectedTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, }, "nil filter": { filter: nil, diff --git a/config-controller/api/v1alpha1/zz_generated.deepcopy.go b/config-controller/api/v1alpha1/zz_generated.deepcopy.go index 89f899775ea59..dba0696cac6f8 100644 --- a/config-controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/config-controller/api/v1alpha1/zz_generated.deepcopy.go @@ -45,7 +45,7 @@ func (in *EvaluationFilter) DeepCopyInto(out *EvaluationFilter) { *out = *in if in.SkipContainerTypes != nil { in, out := &in.SkipContainerTypes, &out.SkipContainerTypes - *out = make([]ContainerType, len(*in)) + *out = make([]SkipContainerType, len(*in)) copy(*out, *in) } } diff --git a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml index 61a83b1baa76d..07dee8e82b5ab 100644 --- a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml +++ b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml @@ -82,10 +82,13 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: SkipContainerType lists container kinds a policy + may skip during evaluation. enum: - - REGULAR - - INIT + - SKIP_INIT type: string type: array type: object diff --git a/generated/api/v1/alert_service.swagger.json b/generated/api/v1/alert_service.swagger.json index 307efecd9bdcd..15fb7393232a1 100644 --- a/generated/api/v1/alert_service.swagger.json +++ b/generated/api/v1/alert_service.swagger.json @@ -1244,14 +1244,6 @@ }, "title": "Next tag: 13" }, - "storageContainerType": { - "type": "string", - "enum": [ - "REGULAR", - "INIT" - ], - "default": "REGULAR" - }, "storageEnforcementAction": { "type": "string", "enum": [ @@ -1273,7 +1265,7 @@ "skipContainerTypes": { "type": "array", "items": { - "$ref": "#/definitions/storageContainerType" + "$ref": "#/definitions/storageSkipContainerType" } } }, @@ -1871,6 +1863,14 @@ ], "default": "UNSET_SEVERITY" }, + "storageSkipContainerType": { + "type": "string", + "enum": [ + "SKIP_INIT" + ], + "default": "SKIP_INIT", + "description": "SkipContainerType lists container kinds a policy may skip during evaluation.\nThe set is independent of ContainerType and currently contains only init containers.\nAn empty skip_container_types list evaluates every container type." + }, "storageViolationState": { "type": "string", "enum": [ diff --git a/generated/api/v1/detection_service.swagger.json b/generated/api/v1/detection_service.swagger.json index 8be964b719432..a710052448a81 100644 --- a/generated/api/v1/detection_service.swagger.json +++ b/generated/api/v1/detection_service.swagger.json @@ -1071,7 +1071,7 @@ "skipContainerTypes": { "type": "array", "items": { - "$ref": "#/definitions/storageContainerType" + "$ref": "#/definitions/storageSkipContainerType" } } }, @@ -1694,6 +1694,14 @@ ], "default": "UNSET_SEVERITY" }, + "storageSkipContainerType": { + "type": "string", + "enum": [ + "SKIP_INIT" + ], + "default": "SKIP_INIT", + "description": "SkipContainerType lists container kinds a policy may skip during evaluation.\nThe set is independent of ContainerType and currently contains only init containers.\nAn empty skip_container_types list evaluates every container type." + }, "storageTaintEffect": { "type": "string", "enum": [ diff --git a/generated/api/v1/policy_service.swagger.json b/generated/api/v1/policy_service.swagger.json index e084db38a9d74..8221fab3a3c4f 100644 --- a/generated/api/v1/policy_service.swagger.json +++ b/generated/api/v1/policy_service.swagger.json @@ -847,14 +847,6 @@ ], "default": "OR" }, - "storageContainerType": { - "type": "string", - "enum": [ - "REGULAR", - "INIT" - ], - "default": "REGULAR" - }, "storageEnforcementAction": { "type": "string", "enum": [ @@ -876,7 +868,7 @@ "skipContainerTypes": { "type": "array", "items": { - "$ref": "#/definitions/storageContainerType" + "$ref": "#/definitions/storageSkipContainerType" } } }, @@ -1256,6 +1248,14 @@ ], "default": "UNSET_SEVERITY" }, + "storageSkipContainerType": { + "type": "string", + "enum": [ + "SKIP_INIT" + ], + "default": "SKIP_INIT", + "description": "SkipContainerType lists container kinds a policy may skip during evaluation.\nThe set is independent of ContainerType and currently contains only init containers.\nAn empty skip_container_types list evaluates every container type." + }, "v1AggregateBy": { "type": "object", "properties": { diff --git a/generated/storage/policy.pb.go b/generated/storage/policy.pb.go index 3f2a48f2c108c..446a7b4306160 100644 --- a/generated/storage/policy.pb.go +++ b/generated/storage/policy.pb.go @@ -392,6 +392,52 @@ func (Comparator) EnumDescriptor() ([]byte, []int) { return file_storage_policy_proto_rawDescGZIP(), []int{6} } +// SkipContainerType lists container kinds a policy may skip during evaluation. +// The set is independent of ContainerType and currently contains only init containers. +// An empty skip_container_types list evaluates every container type. +type SkipContainerType int32 + +const ( + SkipContainerType_SKIP_INIT SkipContainerType = 0 +) + +// Enum value maps for SkipContainerType. +var ( + SkipContainerType_name = map[int32]string{ + 0: "SKIP_INIT", + } + SkipContainerType_value = map[string]int32{ + "SKIP_INIT": 0, + } +) + +func (x SkipContainerType) Enum() *SkipContainerType { + p := new(SkipContainerType) + *p = x + return p +} + +func (x SkipContainerType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SkipContainerType) Descriptor() protoreflect.EnumDescriptor { + return file_storage_policy_proto_enumTypes[7].Descriptor() +} + +func (SkipContainerType) Type() protoreflect.EnumType { + return &file_storage_policy_proto_enumTypes[7] +} + +func (x SkipContainerType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SkipContainerType.Descriptor instead. +func (SkipContainerType) EnumDescriptor() ([]byte, []int) { + return file_storage_policy_proto_rawDescGZIP(), []int{7} +} + // Next tag: 29 type Policy struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1008,7 +1054,7 @@ func (x *ListPolicy) GetEvaluationFilter() *EvaluationFilter { // EvaluationFilter pre-filters which entities a policy evaluates. type EvaluationFilter struct { state protoimpl.MessageState `protogen:"open.v1"` - SkipContainerTypes []ContainerType `protobuf:"varint,1,rep,packed,name=skip_container_types,json=skipContainerTypes,proto3,enum=storage.ContainerType" json:"skip_container_types,omitempty"` + SkipContainerTypes []SkipContainerType `protobuf:"varint,1,rep,packed,name=skip_container_types,json=skipContainerTypes,proto3,enum=storage.SkipContainerType" json:"skip_container_types,omitempty" crYaml:"skipContainerTypes,omitempty,stringer"` // @gotags: crYaml:"skipContainerTypes,omitempty,stringer" unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1043,7 +1089,7 @@ func (*EvaluationFilter) Descriptor() ([]byte, []int) { return file_storage_policy_proto_rawDescGZIP(), []int{6} } -func (x *EvaluationFilter) GetSkipContainerTypes() []ContainerType { +func (x *EvaluationFilter) GetSkipContainerTypes() []SkipContainerType { if x != nil { return x.SkipContainerTypes } @@ -1360,7 +1406,7 @@ var File_storage_policy_proto protoreflect.FileDescriptor const file_storage_policy_proto_rawDesc = "" + "\n" + - "\x14storage/policy.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x18storage/deployment.proto\x1a\x13storage/image.proto\x1a\x13storage/scope.proto\"\xfc\t\n" + + "\x14storage/policy.proto\x12\astorage\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x13storage/image.proto\x1a\x13storage/scope.proto\"\xfc\t\n" + "\x06Policy\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + @@ -1428,9 +1474,9 @@ const file_storage_policy_proto_rawDesc = "" + "is_default\x18\n" + " \x01(\bR\tisDefault\x12-\n" + "\x06source\x18\v \x01(\x0e2\x15.storage.PolicySourceR\x06source\x12F\n" + - "\x11evaluation_filter\x18\f \x01(\v2\x19.storage.EvaluationFilterR\x10evaluationFilter\"\\\n" + - "\x10EvaluationFilter\x12H\n" + - "\x14skip_container_types\x18\x01 \x03(\x0e2\x16.storage.ContainerTypeR\x12skipContainerTypes\"\xf5\x02\n" + + "\x11evaluation_filter\x18\f \x01(\v2\x19.storage.EvaluationFilterR\x10evaluationFilter\"`\n" + + "\x10EvaluationFilter\x12L\n" + + "\x14skip_container_types\x18\x01 \x03(\x0e2\x1a.storage.SkipContainerTypeR\x12skipContainerTypes\"\xf5\x02\n" + "\tExclusion\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12=\n" + "\n" + @@ -1491,7 +1537,9 @@ const file_storage_policy_proto_rawDesc = "" + "\n" + "\x06EQUALS\x10\x02\x12\x1a\n" + "\x16GREATER_THAN_OR_EQUALS\x10\x03\x12\x10\n" + - "\fGREATER_THAN\x10\x04B.\n" + + "\fGREATER_THAN\x10\x04*\"\n" + + "\x11SkipContainerType\x12\r\n" + + "\tSKIP_INIT\x10\x00B.\n" + "\x19io.stackrox.proto.storageZ\x11./storage;storageb\x06proto3" var ( @@ -1506,7 +1554,7 @@ func file_storage_policy_proto_rawDescGZIP() []byte { return file_storage_policy_proto_rawDescData } -var file_storage_policy_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_storage_policy_proto_enumTypes = make([]protoimpl.EnumInfo, 8) var file_storage_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_storage_policy_proto_goTypes = []any{ (PolicySource)(0), // 0: storage.PolicySource @@ -1516,53 +1564,53 @@ var file_storage_policy_proto_goTypes = []any{ (Severity)(0), // 4: storage.Severity (LifecycleStage)(0), // 5: storage.LifecycleStage (Comparator)(0), // 6: storage.Comparator - (*Policy)(nil), // 7: storage.Policy - (*PolicySection)(nil), // 8: storage.PolicySection - (*PolicyGroup)(nil), // 9: storage.PolicyGroup - (*PolicyValue)(nil), // 10: storage.PolicyValue - (*PolicyList)(nil), // 11: storage.PolicyList - (*ListPolicy)(nil), // 12: storage.ListPolicy - (*EvaluationFilter)(nil), // 13: storage.EvaluationFilter - (*Exclusion)(nil), // 14: storage.Exclusion - (*ExportPoliciesResponse)(nil), // 15: storage.ExportPoliciesResponse - (*Policy_MitreAttackVectors)(nil), // 16: storage.Policy.MitreAttackVectors - (*Exclusion_Container)(nil), // 17: storage.Exclusion.Container - (*Exclusion_Deployment)(nil), // 18: storage.Exclusion.Deployment - (*Exclusion_Image)(nil), // 19: storage.Exclusion.Image - (*Scope)(nil), // 20: storage.Scope - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp - (ContainerType)(0), // 22: storage.ContainerType + (SkipContainerType)(0), // 7: storage.SkipContainerType + (*Policy)(nil), // 8: storage.Policy + (*PolicySection)(nil), // 9: storage.PolicySection + (*PolicyGroup)(nil), // 10: storage.PolicyGroup + (*PolicyValue)(nil), // 11: storage.PolicyValue + (*PolicyList)(nil), // 12: storage.PolicyList + (*ListPolicy)(nil), // 13: storage.ListPolicy + (*EvaluationFilter)(nil), // 14: storage.EvaluationFilter + (*Exclusion)(nil), // 15: storage.Exclusion + (*ExportPoliciesResponse)(nil), // 16: storage.ExportPoliciesResponse + (*Policy_MitreAttackVectors)(nil), // 17: storage.Policy.MitreAttackVectors + (*Exclusion_Container)(nil), // 18: storage.Exclusion.Container + (*Exclusion_Deployment)(nil), // 19: storage.Exclusion.Deployment + (*Exclusion_Image)(nil), // 20: storage.Exclusion.Image + (*Scope)(nil), // 21: storage.Scope + (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp (*ImageName)(nil), // 23: storage.ImageName } var file_storage_policy_proto_depIdxs = []int32{ 5, // 0: storage.Policy.lifecycle_stages:type_name -> storage.LifecycleStage 1, // 1: storage.Policy.event_source:type_name -> storage.EventSource - 14, // 2: storage.Policy.exclusions:type_name -> storage.Exclusion - 20, // 3: storage.Policy.scope:type_name -> storage.Scope + 15, // 2: storage.Policy.exclusions:type_name -> storage.Exclusion + 21, // 3: storage.Policy.scope:type_name -> storage.Scope 4, // 4: storage.Policy.severity:type_name -> storage.Severity 3, // 5: storage.Policy.enforcement_actions:type_name -> storage.EnforcementAction - 21, // 6: storage.Policy.last_updated:type_name -> google.protobuf.Timestamp - 8, // 7: storage.Policy.policy_sections:type_name -> storage.PolicySection - 16, // 8: storage.Policy.mitre_attack_vectors:type_name -> storage.Policy.MitreAttackVectors + 22, // 6: storage.Policy.last_updated:type_name -> google.protobuf.Timestamp + 9, // 7: storage.Policy.policy_sections:type_name -> storage.PolicySection + 17, // 8: storage.Policy.mitre_attack_vectors:type_name -> storage.Policy.MitreAttackVectors 0, // 9: storage.Policy.source:type_name -> storage.PolicySource - 13, // 10: storage.Policy.evaluation_filter:type_name -> storage.EvaluationFilter - 9, // 11: storage.PolicySection.policy_groups:type_name -> storage.PolicyGroup + 14, // 10: storage.Policy.evaluation_filter:type_name -> storage.EvaluationFilter + 10, // 11: storage.PolicySection.policy_groups:type_name -> storage.PolicyGroup 2, // 12: storage.PolicyGroup.boolean_operator:type_name -> storage.BooleanOperator - 10, // 13: storage.PolicyGroup.values:type_name -> storage.PolicyValue - 7, // 14: storage.PolicyList.policies:type_name -> storage.Policy + 11, // 13: storage.PolicyGroup.values:type_name -> storage.PolicyValue + 8, // 14: storage.PolicyList.policies:type_name -> storage.Policy 4, // 15: storage.ListPolicy.severity:type_name -> storage.Severity 5, // 16: storage.ListPolicy.lifecycle_stages:type_name -> storage.LifecycleStage - 21, // 17: storage.ListPolicy.last_updated:type_name -> google.protobuf.Timestamp + 22, // 17: storage.ListPolicy.last_updated:type_name -> google.protobuf.Timestamp 1, // 18: storage.ListPolicy.event_source:type_name -> storage.EventSource 0, // 19: storage.ListPolicy.source:type_name -> storage.PolicySource - 13, // 20: storage.ListPolicy.evaluation_filter:type_name -> storage.EvaluationFilter - 22, // 21: storage.EvaluationFilter.skip_container_types:type_name -> storage.ContainerType - 18, // 22: storage.Exclusion.deployment:type_name -> storage.Exclusion.Deployment - 19, // 23: storage.Exclusion.image:type_name -> storage.Exclusion.Image - 21, // 24: storage.Exclusion.expiration:type_name -> google.protobuf.Timestamp - 7, // 25: storage.ExportPoliciesResponse.policies:type_name -> storage.Policy + 14, // 20: storage.ListPolicy.evaluation_filter:type_name -> storage.EvaluationFilter + 7, // 21: storage.EvaluationFilter.skip_container_types:type_name -> storage.SkipContainerType + 19, // 22: storage.Exclusion.deployment:type_name -> storage.Exclusion.Deployment + 20, // 23: storage.Exclusion.image:type_name -> storage.Exclusion.Image + 22, // 24: storage.Exclusion.expiration:type_name -> google.protobuf.Timestamp + 8, // 25: storage.ExportPoliciesResponse.policies:type_name -> storage.Policy 23, // 26: storage.Exclusion.Container.image_name:type_name -> storage.ImageName - 20, // 27: storage.Exclusion.Deployment.scope:type_name -> storage.Scope + 21, // 27: storage.Exclusion.Deployment.scope:type_name -> storage.Scope 28, // [28:28] is the sub-list for method output_type 28, // [28:28] is the sub-list for method input_type 28, // [28:28] is the sub-list for extension type_name @@ -1575,7 +1623,6 @@ func file_storage_policy_proto_init() { if File_storage_policy_proto != nil { return } - file_storage_deployment_proto_init() file_storage_image_proto_init() file_storage_scope_proto_init() type x struct{} @@ -1583,7 +1630,7 @@ func file_storage_policy_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_storage_policy_proto_rawDesc), len(file_storage_policy_proto_rawDesc)), - NumEnums: 7, + NumEnums: 8, NumMessages: 13, NumExtensions: 0, NumServices: 0, diff --git a/generated/storage/policy_vtproto.pb.go b/generated/storage/policy_vtproto.pb.go index e24289130948a..58afbb4560ff4 100644 --- a/generated/storage/policy_vtproto.pb.go +++ b/generated/storage/policy_vtproto.pb.go @@ -258,7 +258,7 @@ func (m *EvaluationFilter) CloneVT() *EvaluationFilter { } r := new(EvaluationFilter) if rhs := m.SkipContainerTypes; rhs != nil { - tmpContainer := make([]ContainerType, len(rhs)) + tmpContainer := make([]SkipContainerType, len(rhs)) copy(tmpContainer, rhs) r.SkipContainerTypes = tmpContainer } @@ -4173,7 +4173,7 @@ func (m *EvaluationFilter) UnmarshalVT(dAtA []byte) error { switch fieldNum { case 1: if wireType == 0 { - var v ContainerType + var v SkipContainerType for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4183,7 +4183,7 @@ func (m *EvaluationFilter) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= ContainerType(b&0x7F) << shift + v |= SkipContainerType(b&0x7F) << shift if b < 0x80 { break } @@ -4217,10 +4217,10 @@ func (m *EvaluationFilter) UnmarshalVT(dAtA []byte) error { } var elementCount int if elementCount != 0 && len(m.SkipContainerTypes) == 0 { - m.SkipContainerTypes = make([]ContainerType, 0, elementCount) + m.SkipContainerTypes = make([]SkipContainerType, 0, elementCount) } for iNdEx < postIndex { - var v ContainerType + var v SkipContainerType for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4230,7 +4230,7 @@ func (m *EvaluationFilter) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= ContainerType(b&0x7F) << shift + v |= SkipContainerType(b&0x7F) << shift if b < 0x80 { break } @@ -6781,7 +6781,7 @@ func (m *EvaluationFilter) UnmarshalVTUnsafe(dAtA []byte) error { switch fieldNum { case 1: if wireType == 0 { - var v ContainerType + var v SkipContainerType for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6791,7 +6791,7 @@ func (m *EvaluationFilter) UnmarshalVTUnsafe(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= ContainerType(b&0x7F) << shift + v |= SkipContainerType(b&0x7F) << shift if b < 0x80 { break } @@ -6825,10 +6825,10 @@ func (m *EvaluationFilter) UnmarshalVTUnsafe(dAtA []byte) error { } var elementCount int if elementCount != 0 && len(m.SkipContainerTypes) == 0 { - m.SkipContainerTypes = make([]ContainerType, 0, elementCount) + m.SkipContainerTypes = make([]SkipContainerType, 0, elementCount) } for iNdEx < postIndex { - var v ContainerType + var v SkipContainerType for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6838,7 +6838,7 @@ func (m *EvaluationFilter) UnmarshalVTUnsafe(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= ContainerType(b&0x7F) << shift + v |= SkipContainerType(b&0x7F) << shift if b < 0x80 { break } diff --git a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml index 61a83b1baa76d..07dee8e82b5ab 100644 --- a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml +++ b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml @@ -82,10 +82,13 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: SkipContainerType lists container kinds a policy + may skip during evaluation. enum: - - REGULAR - - INIT + - SKIP_INIT type: string type: array type: object diff --git a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml index 3b94d5f785b32..1e876ef79ac8b 100644 --- a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml +++ b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml @@ -83,10 +83,13 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: SkipContainerType lists container kinds a policy + may skip during evaluation. enum: - - REGULAR - - INIT + - SKIP_INIT type: string type: array type: object diff --git a/pkg/booleanpolicy/filter/compile_test.go b/pkg/booleanpolicy/filter/compile_test.go index b4a6b5f55c64a..90d9fa1db3ad7 100644 --- a/pkg/booleanpolicy/filter/compile_test.go +++ b/pkg/booleanpolicy/filter/compile_test.go @@ -36,7 +36,7 @@ func TestCompileEvaluationFilter_NoSkipTypes_ReturnsNil(t *testing.T) { func TestCompileEvaluationFilter_ContainerTypeFilter(t *testing.T) { tests := map[string]struct { - skipTypes []storage.ContainerType + skipTypes []storage.SkipContainerType containers []*storage.Container images []*storage.Image expectedContainers []string @@ -44,7 +44,7 @@ func TestCompileEvaluationFilter_ContainerTypeFilter(t *testing.T) { expectSamePointer bool }{ "skip init containers": { - skipTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, containers: []*storage.Container{ {Name: "init-setup", Type: storage.ContainerType_INIT}, {Name: "app", Type: storage.ContainerType_REGULAR}, @@ -61,26 +61,13 @@ func TestCompileEvaluationFilter_ContainerTypeFilter(t *testing.T) { expectedImages: []string{"app-img", "sidecar-img"}, }, "skip init but none present returns original": { - skipTypes: []storage.ContainerType{storage.ContainerType_INIT}, + skipTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, containers: []*storage.Container{ {Name: "app", Type: storage.ContainerType_REGULAR}, }, images: []*storage.Image{{Id: "app-img"}}, expectSamePointer: true, }, - "skip regular containers": { - skipTypes: []storage.ContainerType{storage.ContainerType_REGULAR}, - containers: []*storage.Container{ - {Name: "init-setup", Type: storage.ContainerType_INIT}, - {Name: "app", Type: storage.ContainerType_REGULAR}, - }, - images: []*storage.Image{ - {Id: "init-img"}, - {Id: "app-img"}, - }, - expectedContainers: []string{"init-setup"}, - expectedImages: []string{"init-img"}, - }, } for name, tc := range tests { diff --git a/pkg/booleanpolicy/filter/container_type.go b/pkg/booleanpolicy/filter/container_type.go index 86da323e1c7ca..c10bbf6e06529 100644 --- a/pkg/booleanpolicy/filter/container_type.go +++ b/pkg/booleanpolicy/filter/container_type.go @@ -5,11 +5,17 @@ import ( "github.com/stackrox/rox/pkg/set" ) -func newContainerTypeFilter(skipTypes []storage.ContainerType) *EvaluationFilter { - if len(skipTypes) == 0 { +func newContainerTypeFilter(skipTypes []storage.SkipContainerType) *EvaluationFilter { + skip := set.NewSet[storage.ContainerType]() + for _, skipType := range skipTypes { + switch skipType { + case storage.SkipContainerType_SKIP_INIT: + skip.Add(storage.ContainerType_INIT) + } + } + if skip.IsEmpty() { return nil } - skip := set.NewSet(skipTypes...) return &EvaluationFilter{ isNonDefault: func() bool { return true }, apply: func(dep *storage.Deployment, imgs []*storage.Image) (*storage.Deployment, []*storage.Image) { diff --git a/pkg/detection/compiled_policy_test.go b/pkg/detection/compiled_policy_test.go index f50720961ee0a..db16cf30375cf 100644 --- a/pkg/detection/compiled_policy_test.go +++ b/pkg/detection/compiled_policy_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/booleanpolicy" "github.com/stackrox/rox/pkg/booleanpolicy/fieldnames" "github.com/stackrox/rox/pkg/booleanpolicy/policyversion" "github.com/stackrox/rox/pkg/features" @@ -276,3 +277,102 @@ func TestProcessAndFileAccessMatchers(t *testing.T) { }) } } + +func initAndRegularDeployment() (*storage.Deployment, []*storage.Image) { + dep := &storage.Deployment{ + Id: "dep", + Name: "dep", + Namespace: "ns", + Containers: []*storage.Container{ + { + Name: "init-setup", + Type: storage.ContainerType_INIT, + SecurityContext: &storage.SecurityContext{Privileged: true}, + Image: &storage.ContainerImage{Id: "init-img", Name: &storage.ImageName{FullName: "busybox:latest"}}, + }, + { + Name: "app", + Type: storage.ContainerType_REGULAR, + SecurityContext: &storage.SecurityContext{}, + Image: &storage.ContainerImage{Id: "app-img", Name: &storage.ImageName{FullName: "nginx:1.25"}}, + }, + }, + } + images := []*storage.Image{ + {Id: "init-img", Name: &storage.ImageName{FullName: "busybox:latest"}}, + {Id: "app-img", Name: &storage.ImageName{FullName: "nginx:1.25"}}, + } + return dep, images +} + +func TestEvaluationFilterSkipsInitContainers(t *testing.T) { + t.Setenv(features.EvaluationFilter.EnvVar(), "true") + + dep, images := initAndRegularDeployment() + ed := booleanpolicy.EnhancedDeployment{Deployment: dep, Images: images} + + skipInit := &storage.EvaluationFilter{ + SkipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, + } + privilegedPolicy := &storage.Policy{ + PolicyVersion: policyversion.CurrentVersion().String(), + Name: "privileged", + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{{ + FieldName: fieldnames.PrivilegedContainer, + Values: []*storage.PolicyValue{{Value: "true"}}, + }}, + }}, + EvaluationFilter: skipInit, + } + + compiled, err := CompilePolicy(privilegedPolicy, nil, nil) + require.NoError(t, err) + + violations, err := compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + assert.Empty(t, violations.AlertViolations, "privileged init container is skipped") + + dep.Containers[0].SecurityContext.Privileged = false + dep.Containers[1].SecurityContext.Privileged = true + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + require.Len(t, violations.AlertViolations, 1) + assert.Contains(t, violations.AlertViolations[0].GetMessage(), "app") + + unfiltered := privilegedPolicy.CloneVT() + unfiltered.EvaluationFilter = nil + dep.Containers[0].SecurityContext.Privileged = true + dep.Containers[1].SecurityContext.Privileged = false + compiled, err = CompilePolicy(unfiltered, nil, nil) + require.NoError(t, err) + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + require.NotEmpty(t, violations.AlertViolations) + assert.Contains(t, violations.AlertViolations[0].GetMessage(), "init-setup") + + processPolicy := &storage.Policy{ + PolicyVersion: policyversion.CurrentVersion().String(), + Name: "process", + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, + EventSource: storage.EventSource_DEPLOYMENT_EVENT, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{{ + FieldName: fieldnames.ProcessName, + Values: []*storage.PolicyValue{{Value: "bash"}}, + }}, + }}, + EvaluationFilter: skipInit, + } + compiled, err = CompilePolicy(processPolicy, nil, nil) + require.NoError(t, err) + + appProcess := &storage.ProcessIndicator{ + ContainerName: "app", + Signal: &storage.ProcessSignal{Name: "bash", ExecFilePath: "/bin/bash"}, + } + violations, err = compiled.MatchAgainstDeploymentAndProcess(nil, ed, appProcess, true) + require.NoError(t, err) + assert.NotNil(t, violations.ProcessViolation) +} diff --git a/proto/storage/policy.proto b/proto/storage/policy.proto index c07c52d6152e2..a575374deeb92 100644 --- a/proto/storage/policy.proto +++ b/proto/storage/policy.proto @@ -3,7 +3,6 @@ syntax = "proto3"; package storage; import "google/protobuf/timestamp.proto"; -import "storage/deployment.proto"; import "storage/image.proto"; import "storage/scope.proto"; @@ -178,9 +177,16 @@ enum Comparator { GREATER_THAN = 4; } +// SkipContainerType lists container kinds a policy may skip during evaluation. +// The set is independent of ContainerType and currently contains only init containers. +// An empty skip_container_types list evaluates every container type. +enum SkipContainerType { + SKIP_INIT = 0; +} + // EvaluationFilter pre-filters which entities a policy evaluates. message EvaluationFilter { - repeated ContainerType skip_container_types = 1; + repeated SkipContainerType skip_container_types = 1; // @gotags: crYaml:"skipContainerTypes,omitempty,stringer" } message Exclusion { diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index ada9094986dcf..002dd08086cb7 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -14054,6 +14054,14 @@ "integer": 4 } ] + }, + { + "name": "SkipContainerType", + "enum_fields": [ + { + "name": "SKIP_INIT" + } + ] } ], "messages": [ @@ -14358,7 +14366,7 @@ { "id": 1, "name": "skip_container_types", - "type": "ContainerType", + "type": "SkipContainerType", "is_repeated": true } ] @@ -14444,9 +14452,6 @@ { "path": "google/protobuf/timestamp.proto" }, - { - "path": "storage/deployment.proto" - }, { "path": "storage/image.proto" }, diff --git a/tests/init_container_test.go b/tests/init_container_test.go index ebf7a61e4b431..5d096238fe7f3 100644 --- a/tests/init_container_test.go +++ b/tests/init_container_test.go @@ -180,12 +180,38 @@ func (s *InitContainerSuite) createPolicyWithCleanup(policy *storage.Policy) *st } func (s *InitContainerSuite) waitForViolationAlert(deploymentName, policyName string, expectedCount int) { + waitForAlert(s.T(), s.alertService, s.violationAlertRequest(deploymentName, policyName), expectedCount) +} + +// activeViolationText waits until one active alert exists and returns its violation messages. +func (s *InitContainerSuite) activeViolationText(deploymentName, policyName string) string { + s.waitForViolationAlert(deploymentName, policyName, 1) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + listResp, err := s.alertService.ListAlerts(ctx, s.violationAlertRequest(deploymentName, policyName)) + cancel() + s.Require().NoError(err) + s.Require().Len(listResp.GetAlerts(), 1) + + ctx, cancel = context.WithTimeout(context.Background(), time.Minute) + alert, err := s.alertService.GetAlert(ctx, &v1.ResourceByID{Id: listResp.GetAlerts()[0].GetId()}) + cancel() + s.Require().NoError(err) + s.Require().NotEmpty(alert.GetViolations()) + + messages := make([]string, 0, len(alert.GetViolations())) + for _, violation := range alert.GetViolations() { + messages = append(messages, violation.GetMessage()) + } + return strings.Join(messages, "\n") +} + +func (s *InitContainerSuite) violationAlertRequest(deploymentName, policyName string) *v1.ListAlertsRequest { query := search.NewQueryBuilder(). AddStrings(search.DeploymentName, deploymentName). AddStrings(search.PolicyName, policyName). AddStrings(search.ViolationState, storage.ViolationState_ACTIVE.String()) - - waitForAlert(s.T(), s.alertService, &v1.ListAlertsRequest{Query: query.Query()}, expectedCount) + return &v1.ListAlertsRequest{Query: query.Query()} } func (s *InitContainerSuite) TestInitContainerExtraction() { @@ -387,22 +413,62 @@ func (s *InitContainerSuite) TestEvaluationFilterSkipsInitContainers() { createNamespaceWithLabels(t, ns, nil) defer deleteNamespace(t, ns) - policy := s.newLatestTagPolicy( + unfiltered := s.createPolicyWithCleanup(s.newLatestTagPolicy( + fmt.Sprintf("Test - Evaluate Init %d", rand.IntN(10000)), ns, + )) + + filteredPolicy := s.newLatestTagPolicy( fmt.Sprintf("Test - Skip Init %d", rand.IntN(10000)), ns, ) - policy.EvaluationFilter = &storage.EvaluationFilter{ - SkipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + filteredPolicy.EvaluationFilter = &storage.EvaluationFilter{ + SkipContainerTypes: []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, } - createdPolicy := s.createPolicyWithCleanup(policy) + filtered := s.createPolicyWithCleanup(filteredPolicy) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + storedPolicy, err := s.policyService.GetPolicy(ctx, &v1.ResourceByID{Id: filtered.GetId()}) + cancel() + s.Require().NoError(err) + s.Require().Equal( + []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, + storedPolicy.GetEvaluationFilter().GetSkipContainerTypes(), + ) + s.Require().Empty(unfiltered.GetEvaluationFilter().GetSkipContainerTypes()) - // Init uses :latest (would violate), regular uses tagged image (no violation). - // With skip-init filter, the only violating container is skipped — expect 0 alerts. + // Both containers use :latest. The unfiltered policy must name both containers. + // The filtered policy must name the regular container and omit the init container. deployName := fmt.Sprintf("init-filter-skip-%d", rand.IntN(10000)) - s.createDeploymentWithInitContainers(deployName, ns, []string{busyboxLatest}, nginxTagged) + s.createDeploymentWithInitContainers(deployName, ns, []string{busyboxLatest}, nginxLatest) defer teardownDeploymentWithoutCheck(t, deployName, ns) - s.waitForDeploymentWithContainers(deployName, 2) + dep := s.waitForDeploymentWithContainers(deployName, 2) + var initContainer, regularContainer *storage.Container + for _, c := range dep.GetContainers() { + switch c.GetType() { + case storage.ContainerType_INIT: + initContainer = c + case storage.ContainerType_REGULAR: + regularContainer = c + } + } + s.Require().NotNil(initContainer, "expected an init container") + s.Require().NotNil(regularContainer, "expected a regular container") + s.Equal("init-0", initContainer.GetName()) + s.Equal(deployName, regularContainer.GetName()) + s.Contains(initContainer.GetImage().GetName().GetFullName(), "busybox") + s.Contains(regularContainer.GetImage().GetName().GetFullName(), "nginx") - s.waitForViolationAlert(deployName, createdPolicy.GetName(), 0) - t.Logf("Verified: policy with skip init filter produced no violations") + regularContainerRef := fmt.Sprintf("Container '%s'", deployName) + unfilteredText := s.activeViolationText(deployName, unfiltered.GetName()) + s.Require().Contains(unfilteredText, regularContainerRef) + s.Require().Contains(unfilteredText, "Container 'init-0'") + s.Require().Contains(unfilteredText, "tag 'latest'") + t.Logf("Verified unfiltered alert names both containers: %s", unfilteredText) + + filteredText := s.activeViolationText(deployName, filtered.GetName()) + s.Require().Contains(filteredText, regularContainerRef) + s.Require().Contains(filteredText, "tag 'latest'") + s.Require().NotContains(filteredText, "init-0") + s.Require().NotContains(filteredText, "busybox") + t.Logf("Verified skip-init alert names %q and omits the init container: %s", deployName, filteredText) } diff --git a/tests/policy_as_code_test.go b/tests/policy_as_code_test.go index 6f55908167678..f1ccd11d9ff8f 100644 --- a/tests/policy_as_code_test.go +++ b/tests/policy_as_code_test.go @@ -475,7 +475,7 @@ func (pc *PolicyAsCodeSuite) createCRAndObserveInCentral(policyCR *v1alpha1.Secu func (pc *PolicyAsCodeSuite) TestCRWithEvaluationFilter() { k8sPolicy := createBasePolicyStruct("test-eval-filter-cr") k8sPolicy.Spec.EvaluationFilter = &v1alpha1.EvaluationFilter{ - SkipContainerTypes: []v1alpha1.ContainerType{"INIT"}, + SkipContainerTypes: []v1alpha1.SkipContainerType{"SKIP_INIT"}, } id := pc.createCRAndObserveInCentral(k8sPolicy) @@ -487,7 +487,7 @@ func (pc *PolicyAsCodeSuite) TestCRWithEvaluationFilter() { pc.Require().NotNil(policy.GetEvaluationFilter()) pc.Require().Equal( - []storage.ContainerType{storage.ContainerType_INIT}, + []storage.SkipContainerType{storage.SkipContainerType_SKIP_INIT}, policy.GetEvaluationFilter().GetSkipContainerTypes(), ) } diff --git a/tools/generate-helpers/config-as-code-helper/main.go b/tools/generate-helpers/config-as-code-helper/main.go index cba3d849424c0..f5e73ad3665fa 100644 --- a/tools/generate-helpers/config-as-code-helper/main.go +++ b/tools/generate-helpers/config-as-code-helper/main.go @@ -96,6 +96,12 @@ func generateTemplateData(t reflect.Type, visited set.StringSet) []ConvertType { fieldType = utils.IfThenElse(isSlice, "[]string", "string") } + // A stringer or timestamp field is rewritten as a string. A message that + // contains only those fields still needs its own converted type, otherwise + // the parent embeds the proto and YAML uses the Go field name and wire value. + if isStringer || isTimeStamp { + needUpdate = true + } needConversion := !isStringer && !isTimeStamp && checkForConversion(field) if needConversion { if !visited.Contains(field.Name) { diff --git a/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetailContent.tsx b/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetailContent.tsx index f6ee0599c5979..3c5fd80a6c2bc 100644 --- a/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetailContent.tsx +++ b/ui/apps/platform/src/Containers/Policies/Detail/PolicyDetailContent.tsx @@ -69,6 +69,7 @@ function PolicyDetailContent({ policy, isReview = false }: PolicyDetailContentPr Policy resources diff --git a/ui/apps/platform/src/Containers/Policies/Detail/PolicyFiltersSection.tsx b/ui/apps/platform/src/Containers/Policies/Detail/PolicyFiltersSection.tsx index d349ec2fc1534..eea99fe43e8dc 100644 --- a/ui/apps/platform/src/Containers/Policies/Detail/PolicyFiltersSection.tsx +++ b/ui/apps/platform/src/Containers/Policies/Detail/PolicyFiltersSection.tsx @@ -1,39 +1,43 @@ import { Card, CardBody, DescriptionList, Stack, Title } from '@patternfly/react-core'; import useFeatureFlags from 'hooks/useFeatureFlags'; -import type { EvaluationFilter, LifecycleStage } from 'types/policy.proto'; +import type { EvaluationFilter, LifecycleStage, PolicyEventSource } from 'types/policy.proto'; import DescriptionListItem from 'Components/DescriptionListItem'; +import { containerTypeFilterApplies } from '../policies.utils'; type PolicyFiltersSectionProps = { evaluationFilter: EvaluationFilter | null; lifecycleStages: LifecycleStage[]; + eventSource: PolicyEventSource; }; function getContainerTypeLabel( evaluationFilter: EvaluationFilter | null, - lifecycleStages: LifecycleStage[] + lifecycleStages: LifecycleStage[], + eventSource: PolicyEventSource ): string | null { - const hasDeployOrRuntime = - lifecycleStages.includes('DEPLOY') || lifecycleStages.includes('RUNTIME'); - - if (!hasDeployOrRuntime) { + if (!containerTypeFilterApplies(lifecycleStages, eventSource)) { return null; } const skipped = evaluationFilter?.skipContainerTypes ?? []; - if (skipped.includes('INIT')) { + if (skipped.includes('SKIP_INIT')) { return 'Skip init containers'; } return null; } -function PolicyFiltersSection({ evaluationFilter, lifecycleStages }: PolicyFiltersSectionProps) { +function PolicyFiltersSection({ + evaluationFilter, + lifecycleStages, + eventSource, +}: PolicyFiltersSectionProps) { const { isFeatureFlagEnabled } = useFeatureFlags(); const containerTypeLabel = isFeatureFlagEnabled('ROX_EVALUATION_FILTER') && isFeatureFlagEnabled('ROX_INIT_CONTAINER_SUPPORT') - ? getContainerTypeLabel(evaluationFilter, lifecycleStages) + ? getContainerTypeLabel(evaluationFilter, lifecycleStages, eventSource) : null; if (!containerTypeLabel) { diff --git a/ui/apps/platform/src/Containers/Policies/Table/PolicyEvaluationFilterLabels.tsx b/ui/apps/platform/src/Containers/Policies/Table/PolicyEvaluationFilterLabels.tsx index fa21a5cf7fa16..86f08820d161a 100644 --- a/ui/apps/platform/src/Containers/Policies/Table/PolicyEvaluationFilterLabels.tsx +++ b/ui/apps/platform/src/Containers/Policies/Table/PolicyEvaluationFilterLabels.tsx @@ -25,7 +25,7 @@ function PolicyEvaluationFilterLabels({ if ( isFeatureFlagEnabled('ROX_INIT_CONTAINER_SUPPORT') && - evaluationFilter.skipContainerTypes?.includes('INIT') + evaluationFilter.skipContainerTypes?.includes('SKIP_INIT') ) { labels.push({ text: 'Skips init', color: 'blue' }); } diff --git a/ui/apps/platform/src/Containers/Policies/Wizard/StepFilters/PolicyFiltersForm.tsx b/ui/apps/platform/src/Containers/Policies/Wizard/StepFilters/PolicyFiltersForm.tsx index a5dbb2146a812..57c88660a6b72 100644 --- a/ui/apps/platform/src/Containers/Policies/Wizard/StepFilters/PolicyFiltersForm.tsx +++ b/ui/apps/platform/src/Containers/Policies/Wizard/StepFilters/PolicyFiltersForm.tsx @@ -16,6 +16,10 @@ import { useFormikContext } from 'formik'; import type { ClientPolicy } from 'types/policy.proto'; import useFeatureFlags from 'hooks/useFeatureFlags'; import { toggleItemInArray } from 'utils/arrayUtils'; +import { containerTypeFilterApplies } from '../../policies.utils'; + +const containerTypeFilterMessage = + 'Container type filters apply to deploy-time policies and to runtime policies with a deployment event source.'; function PolicyFiltersForm() { const { values, setFieldValue } = useFormikContext(); @@ -27,10 +31,10 @@ function PolicyFiltersForm() { const skipContainerTypes = values.evaluationFilter?.skipContainerTypes ?? []; - const hasDeployOrRuntime = - values.lifecycleStages.includes('DEPLOY') || values.lifecycleStages.includes('RUNTIME'); - - const containerFilterDisabled = !hasDeployOrRuntime; + const containerFilterDisabled = !containerTypeFilterApplies( + values.lifecycleStages, + values.eventSource + ); useEffect(() => { if (containerFilterDisabled && skipContainerTypes.length > 0) { @@ -38,7 +42,7 @@ function PolicyFiltersForm() { } }, [containerFilterDisabled, skipContainerTypes.length, setFieldValue]); - const skipInit = skipContainerTypes.includes('INIT'); + const skipInit = skipContainerTypes.includes('SKIP_INIT'); const containerTypeDescription = skipInit ? 'Policy will skip init containers.' @@ -47,7 +51,7 @@ function PolicyFiltersForm() { function handleSkipInitChange() { setFieldValue( 'evaluationFilter.skipContainerTypes', - toggleItemInArray(skipContainerTypes, 'INIT') + toggleItemInArray(skipContainerTypes, 'SKIP_INIT') ); } @@ -57,7 +61,7 @@ function PolicyFiltersForm() { Filters - Control which parts of an image or workload this policy evaluates. + Control which parts of a workload this policy evaluates. @@ -74,7 +78,7 @@ function PolicyFiltersForm() { )} @@ -88,17 +92,15 @@ function PolicyFiltersForm() { isDisabled={containerFilterDisabled} onChange={handleSkipInitChange} /> - + {!containerFilterDisabled && ( + + )}
    diff --git a/ui/apps/platform/src/Containers/Policies/policies.utils.test.ts b/ui/apps/platform/src/Containers/Policies/policies.utils.test.ts index 5486eb100dd70..b081f25c24a2f 100644 --- a/ui/apps/platform/src/Containers/Policies/policies.utils.test.ts +++ b/ui/apps/platform/src/Containers/Policies/policies.utils.test.ts @@ -1,5 +1,6 @@ import type { ClientPolicy, Policy } from 'types/policy.proto'; import { + containerTypeFilterApplies, getClientWizardPolicy, getLifeCyclesUpdates, getPolicyOriginLabel, @@ -885,4 +886,17 @@ describe('policies.utils', () => { }); }); }); + + describe('containerTypeFilterApplies', () => { + it('applies to deploy-time policies and runtime policies with a deployment event source', () => { + expect(containerTypeFilterApplies(['DEPLOY'], 'NOT_APPLICABLE')).toBe(true); + expect(containerTypeFilterApplies(['BUILD', 'DEPLOY'], 'NOT_APPLICABLE')).toBe(true); + expect(containerTypeFilterApplies(['RUNTIME'], 'DEPLOYMENT_EVENT')).toBe(true); + expect(containerTypeFilterApplies(['BUILD'], 'NOT_APPLICABLE')).toBe(false); + expect(containerTypeFilterApplies(['RUNTIME'], 'NOT_APPLICABLE')).toBe(false); + expect(containerTypeFilterApplies(['RUNTIME'], 'AUDIT_LOG_EVENT')).toBe(false); + expect(containerTypeFilterApplies(['RUNTIME'], 'NODE_EVENT')).toBe(false); + expect(containerTypeFilterApplies([], 'NOT_APPLICABLE')).toBe(false); + }); + }); }); diff --git a/ui/apps/platform/src/Containers/Policies/policies.utils.ts b/ui/apps/platform/src/Containers/Policies/policies.utils.ts index aa56679a45f5f..20564da4ed15e 100644 --- a/ui/apps/platform/src/Containers/Policies/policies.utils.ts +++ b/ui/apps/platform/src/Containers/Policies/policies.utils.ts @@ -82,6 +82,18 @@ export const initialPolicy: ClientPolicy = { }, }; +// Container type filters apply to deploy-time policies and to runtime policies +// whose event source is deployment events. +export function containerTypeFilterApplies( + lifecycleStages: LifecycleStage[], + eventSource: PolicyEventSource +): boolean { + return ( + lifecycleStages.includes('DEPLOY') || + (lifecycleStages.includes('RUNTIME') && eventSource === 'DEPLOYMENT_EVENT') + ); +} + export type PoliciesSearch = { pageAction?: ExtendedPageAction; searchFilter?: SearchFilter; diff --git a/ui/apps/platform/src/types/policy.proto.ts b/ui/apps/platform/src/types/policy.proto.ts index df7f4ea7f8dfe..e7e31df869c54 100644 --- a/ui/apps/platform/src/types/policy.proto.ts +++ b/ui/apps/platform/src/types/policy.proto.ts @@ -159,12 +159,12 @@ export type PolicyMitreAttackVector = { techniques: string[]; // technique ids }; -export type ContainerType = 'INIT'; +export type SkipContainerType = 'SKIP_INIT'; export type SkipImageLayers = 'SKIP_NONE' | 'SKIP_BASE' | 'SKIP_APP'; export type EvaluationFilter = { - skipContainerTypes: ContainerType[]; + skipContainerTypes: SkipContainerType[]; // TODO: reimplement once backend support is available - demo code was deleted at the same time this comment was added // skipImageLayers: SkipImageLayers; }; From b3a3d3cbb1317dee7f651d102ffc0def6a17654a Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 1 Oct 2026 17:16:52 -0400 Subject: [PATCH 489/638] ROX-37384: Add no-import-from-Containers-in-Components to linited (#23194) --- .../platform/eslint-plugins/pluginLimited.js | 32 +++++++++++++++++++ ui/apps/platform/eslint.config.js | 24 ++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/ui/apps/platform/eslint-plugins/pluginLimited.js b/ui/apps/platform/eslint-plugins/pluginLimited.js index f5697047dcc81..031212372b678 100644 --- a/ui/apps/platform/eslint-plugins/pluginLimited.js +++ b/ui/apps/platform/eslint-plugins/pluginLimited.js @@ -379,6 +379,38 @@ const rules = { }; }, }, + 'no-import-from-Containers-in-Components': { + // Prevent soft circular dependency: + // * A generic file in Components has import from a specific file in Containers. + // * Another specific file in Containers might have import of that generic file from Components. + // Although the rule cannot know for sure, Components exists for import into Containers. + meta: { + type: 'problem', + docs: { + description: + 'Fix soft circular dependency between specific Containers and generic Components', + }, + schema: [], + }, + create(context) { + return { + ImportDeclaration(node) { + const { filename } = context; + if ( + filename.includes('ui/apps/platform/src/Components/') && + typeof node.source?.value === 'string' && + node.source.value.startsWith('Containers/') + ) { + context.report({ + node, + message: + 'Fix soft circular dependency between specific Containers and generic Components', + }); + } + }, + }; + }, + }, 'no-logical-or-preceding-array-or-object': { // Consistently write more precise nullish coalescing operator. meta: { diff --git a/ui/apps/platform/eslint.config.js b/ui/apps/platform/eslint.config.js index a6f13c2547d36..0369af92346c0 100644 --- a/ui/apps/platform/eslint.config.js +++ b/ui/apps/platform/eslint.config.js @@ -839,6 +839,30 @@ module.exports = [ 'limited/no-relative-path-to-src-in-import': 'error', }, }, + { + files: ['src/*/**/*.{js,jsx,ts,tsx}'], + ignores: [ + 'src/Components/CompoundSearchFilter/attributes/imageCVE.ts', // refactor: originDisplayNames + 'src/Components/CompoundSearchFilter/attributes/imageComponent.ts', // refactor: sourceTypeLabels, sourceTypes + 'src/Components/CompoundSearchFilter/attributes/virtualMachine.ts', // refactor: sourceTypeLabels, sourceTypes + 'src/Components/EmailNotifier/EmailNotifierForm.tsx', // refactor + 'src/Components/EmailNotifier/EmailNotifierModal.tsx', // refactor + 'src/Components/RelatedEntity.jsx', // deprecated + 'src/Components/RelatedEntityListCount.jsx', // deprecated + 'src/Components/URLSearchInputWithAutocomplete.jsx', // deprecated + 'src/Components/workflow/EntitiesMenu/EntitiesMenu.jsx', // deprecated + ], + + // languageOptions from previous configuration object + + // Key of plugin is namespace of its rules. + plugins: { + limited: pluginLimited, + }, + rules: { + 'limited/no-import-from-Containers-in-Components': 'error', + }, + }, { files: ['src/*/**/*.{js,jsx,ts,tsx}'], ignores: [ From 4e53bd35006556e1458000f85d7ca9b79fd92bf7 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:47:26 +0000 Subject: [PATCH 490/638] chore(deps): update quay.io/rhacs-eng/konflux-tasks:latest docker digest to 3e1c9a2 (#23184) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/basic-component-pipeline.yaml | 6 +++--- .tekton/checks.yaml | 2 +- .tekton/create-custom-snapshot.yaml | 8 ++++---- .tekton/main-pipeline.yaml | 8 ++++---- .tekton/operator-bundle-pipeline.yaml | 8 ++++---- .tekton/operator-index-build.yaml | 8 ++++---- .tekton/retag-pipeline.yaml | 10 +++++----- .tekton/scanner-v4-pipeline.yaml | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 412e2460acc16..f8334178d093b 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 0a32b36258dc3..41deea26e24a7 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index 6fc004f6e642f..51e6e0203cbe9 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 787b6c7506c9e..ca11a38827478 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 08cf42b987678..5da508abb7e4a 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index 55794f741a56c..e621f6a449efc 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 63b3467b416a7..449d29713e6ea 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index bc8c0f64addec..845c9c76dcea7 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:3e1c9a2162a92a99e33ff414dcf07e87400b3ceff855bcb85bfaad4e846afbe3 - name: kind value: task resolver: bundles From 1db7e2415e6aa8924e5808ee4d0ecbe95f1f233c Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 06:59:24 +0000 Subject: [PATCH 491/638] chore(deps): refresh rpm lockfiles (#23201) Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- rpms.lock.yaml | 88 +++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/rpms.lock.yaml b/rpms.lock.yaml index e139cae85bea8..5f5d434ed7581 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.54.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2925601 - checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 + size: 2927565 + checksum: sha256:c950319c04fc9048501cd59da158858b9e4b2d173b98b3d8f56a97b56be89ca9 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.54.1.el9_8 + sourcerpm: kernel-5.14.0-687.54.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -1286,12 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.54.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152530566 + checksum: sha256:7ac2368f99e5e93164155197fa925061e352e80c3874c614b38e43317ae93881 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.54.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/f310ddcbea4a32af8415a78c2ccf7b7eb34ebf5fe763be2bfe0ae728654c1449-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/a6b607cc40e32eab035ab2c62096f0a17886a1b9f8e31f96caed2fcafd19f8e9-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms size: 68974 - checksum: sha256:f310ddcbea4a32af8415a78c2ccf7b7eb34ebf5fe763be2bfe0ae728654c1449 + checksum: sha256:a6b607cc40e32eab035ab2c62096f0a17886a1b9f8e31f96caed2fcafd19f8e9 - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1711,13 +1711,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.54.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2949645 - checksum: sha256:7318493a0814cea968050892155214dc61c91549fbea2f66aa4008be389ace7c + size: 2951357 + checksum: sha256:69203b9637bca61d1aafb9f54d9b8240b7a8f0df14a6d9c2df022ca25707b46f name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.54.1.el9_8 + sourcerpm: kernel-5.14.0-687.54.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -2930,12 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.54.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152530566 + checksum: sha256:7ac2368f99e5e93164155197fa925061e352e80c3874c614b38e43317ae93881 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.54.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/a2ba371afa8702da112fc35262a64188c67b41c55df301eb20b44e2dff3a6b7d-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/bfbbbd6158a7cd8ec47b37ca511772061d503dad22c5dd04d0e3716b979383c3-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms size: 70407 - checksum: sha256:a2ba371afa8702da112fc35262a64188c67b41c55df301eb20b44e2dff3a6b7d + checksum: sha256:bfbbbd6158a7cd8ec47b37ca511772061d503dad22c5dd04d0e3716b979383c3 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3361,13 +3361,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.54.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2955557 - checksum: sha256:9b1a97d19e52dfb5387cae5fd1a7888ce3e1f794b17117ef4cc1eeb1daca8aeb + size: 2957557 + checksum: sha256:24326fc5b7ef83ff374467b4ee2b832c5318d5b1e4c20541f4c81d66879c8427 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.54.1.el9_8 + sourcerpm: kernel-5.14.0-687.54.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -4567,12 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.54.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152530566 + checksum: sha256:7ac2368f99e5e93164155197fa925061e352e80c3874c614b38e43317ae93881 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.54.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/532ca027fab74fd8ae42adae8ec472c1ddf8940c704f3ad3d36114c91c0d210b-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/8dd2dfa4a7b7937527363654d59eff376826c98b2cf2b7d1bf8f1013d80092f7-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms size: 65587 - checksum: sha256:532ca027fab74fd8ae42adae8ec472c1ddf8940c704f3ad3d36114c91c0d210b + checksum: sha256:8dd2dfa4a7b7937527363654d59eff376826c98b2cf2b7d1bf8f1013d80092f7 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4999,13 +4999,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.54.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2965145 - checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 + size: 2966865 + checksum: sha256:33789c61f2d74ef1d0fd5b6a43e802d28e1b38a605f200b156050d0a7934f232 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.54.1.el9_8 + sourcerpm: kernel-5.14.0-687.54.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -6190,12 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.54.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152530566 + checksum: sha256:7ac2368f99e5e93164155197fa925061e352e80c3874c614b38e43317ae93881 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.54.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/d4f5a831afd58619b36a4842a572502fc730e04eefb3e8acca4679a59be2eeff-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/4c5bb657ad15e7a103d42251cdbe830ab5c39145966be9e94ddf04653bdf62af-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms size: 72271 - checksum: sha256:d4f5a831afd58619b36a4842a572502fc730e04eefb3e8acca4679a59be2eeff + checksum: sha256:4c5bb657ad15e7a103d42251cdbe830ab5c39145966be9e94ddf04653bdf62af From 7c966919090a94c114bcdcd9781c81dccf403d4b Mon Sep 17 00:00:00 2001 From: Piotr Rygielski <114479+vikin91@users.noreply.github.com> Date: Fri, 2 Oct 2026 09:16:35 +0200 Subject: [PATCH 492/638] ROX-37206, ROX-37208: delete pods before namespace cleanup (#23167) --- .../orchestratormanager/Kubernetes.groovy | 88 ++++++++++++++++++- .../groovy/AdmissionControllerTest.groovy | 36 ++++---- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy index 14a7f0f7fac89..7d44e2751784b 100644 --- a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy +++ b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy @@ -2681,10 +2681,90 @@ class Kubernetes { client.namespaces().withName(ns).delete() } if (waitForDeletion && !waitForNamespaceDeletion(ns)) { - // Fail loudly: a namespace stuck Terminating leaks into later tests - // (e.g. NamespaceTest's ACS/orchestrator comparison) instead of being - // silently ignored. - throw new OrchestratorManagerException("Timed out waiting for namespace ${ns} to be deleted") + // A namespace that is still listed is a failed cleanup, not a soft warning. + throw new OrchestratorManagerException( + "Timed out waiting for namespace ${ns} to be deleted${namespaceDeletionDiagnostics(ns)}") + } + } + + // deleteManagedWorkloads removes deployments, replica sets, and pods so + // namespace deletion is not held by a pod's termination. A second delete + // with grace period 0 shortens a termination already in progress. + @CompileDynamic + void deleteManagedWorkloads(String ns) { + Timer t = new Timer(20, 1) + boolean sawNoPods = false + while (t.IsValid()) { + if (namespaceGone(ns)) { + return + } + deleteCollectionQuietly { client.apps().deployments().inNamespace(ns).delete() } + deleteCollectionQuietly { client.apps().replicaSets().inNamespace(ns).delete() } + deleteCollectionQuietly { client.pods().inNamespace(ns).withGracePeriod(0L).delete() } + if (listPodsQuietly(ns).isEmpty()) { + // A second empty read covers a replica set that creates one + // more pod after it is told to delete. + if (sawNoPods) { + return + } + sawNoPods = true + continue + } + sawNoPods = false + } + def stuck = listPodsQuietly(ns).collect { + "${it.metadata?.name} phase=${it.status?.phase} finalizers=${it.metadata?.finalizers}" + } + log.info "Workloads still present in ${ns} before namespace deletion: ${stuck}" + } + + @CompileDynamic + private boolean namespaceGone(String ns) { + try { + return client.namespaces().withName(ns).get() == null + } catch (KubernetesClientException e) { + return e.code == 404 + } + } + + @CompileDynamic + private void deleteCollectionQuietly(Closure deletion) { + try { + deletion.call() + } catch (KubernetesClientException e) { + if (e.code != 404) { + log.warn "Resource delete during namespace cleanup failed: ${e.message}" + } + } + } + + @CompileDynamic + private List listPodsQuietly(String ns) { + try { + return client.pods().inNamespace(ns).list()?.items ?: [] + } catch (KubernetesClientException e) { + if (e.code == 404) { + return [] + } + throw e + } + } + + @CompileDynamic + private String namespaceDeletionDiagnostics(String ns) { + try { + def remaining = client.namespaces().withName(ns).get() + if (remaining == null) { + return "" + } + def pods = listPodsQuietly(ns).collect { + "${it.metadata?.name}(phase=${it.status?.phase}, finalizers=${it.metadata?.finalizers})" + } + return " (phase=${remaining.status?.phase}," + + " deletionTimestamp=${remaining.metadata?.deletionTimestamp}," + + " finalizers=${remaining.metadata?.finalizers}, pods=${pods})" + } catch (Exception e) { + return " (failed to collect namespace diagnostics: ${e.message})" } } diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy index 766e3b4e3482a..5c674a7b3b03b 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy @@ -10,6 +10,7 @@ import io.stackrox.proto.storage.ScopeOuterClass import objects.Deployment import services.ClusterService import services.ImageService +import services.NamespaceService import services.PolicyService import util.Timer @@ -132,6 +133,9 @@ class AdmissionControllerTest extends BaseSpecification { } def cleanupSpec() { + // A pod can still be listed after its deployment object is gone, and + // that holds this shared namespace in Terminating. + orchestrator.deleteManagedWorkloads(TEST_NAMESPACE) orchestrator.deleteNamespace(TEST_NAMESPACE) try { @@ -244,6 +248,20 @@ class AdmissionControllerTest extends BaseSpecification { true | true } + // cleanupLabelTestNamespace removes workloads before the namespace. A pod + // from an allowed deployment keeps the namespace past the delete wait, and + // Central can still list it after the API object is gone (ROX-36941). + private void cleanupLabelTestNamespace(String testNs) { + if (!testNs) { + return + } + orchestrator.deleteManagedWorkloads(testNs) + orchestrator.deleteNamespace(testNs) + withRetry(30, 1) { + assert NamespaceService.getNamespaces().every { it.metadata.name != testNs } + } + } + def deleteDeploymentWithCaution(Deployment deployment) { orchestrator.deleteDeployment(deployment) def timer = new Timer(30, 1) @@ -345,16 +363,9 @@ class AdmissionControllerTest extends BaseSpecification { assert created == !blocked cleanup: - // Delete policy first to avoid enforcement blocking cleanup, - // and to prevent leftover policy from impacting later tests. def policyDeletionFailure = deletePolicyWithCaution(policyId) - if (created) { - deleteDeploymentWithCaution(deployment) - } try { - // Wait for full namespace deletion; a namespace left in Terminating state - // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). - orchestrator.deleteNamespace(testNs) + cleanupLabelTestNamespace(testNs) } catch (Exception e) { log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" throw e @@ -452,16 +463,9 @@ class AdmissionControllerTest extends BaseSpecification { assert created2 cleanup: - // Delete policy first to avoid enforcement blocking cleanup, - // and to prevent leftover policy from impacting later tests. def policyDeletionFailure = deletePolicyWithCaution(policyId) - if (created2) { - deleteDeploymentWithCaution(deployment2) - } try { - // Wait for full namespace deletion; a namespace left in Terminating state - // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). - orchestrator.deleteNamespace(testNs) + cleanupLabelTestNamespace(testNs) } catch (Exception e) { log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" throw e From e92a1d9fad2d3a451ed12ee82ff6c4f2e3be0c86 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Fri, 2 Oct 2026 10:42:20 +0200 Subject: [PATCH 493/638] ROX-22443: Cleanup of obsolete PLOP pruning methods (#23156) --- .../datastore/datastore.go | 3 - .../datastore/datastore_impl.go | 126 ------- .../datastore/datastore_impl_test.go | 342 ------------------ .../datastore/mocks/datastore.go | 41 --- .../datastore/pruning.go | 22 -- central/pruning/pruning.go | 30 +- central/pruning/pruning_test.go | 25 -- 7 files changed, 8 insertions(+), 581 deletions(-) diff --git a/central/processlisteningonport/datastore/datastore.go b/central/processlisteningonport/datastore/datastore.go index d7744f88b2b32..f7308a5a418ed 100644 --- a/central/processlisteningonport/datastore/datastore.go +++ b/central/processlisteningonport/datastore/datastore.go @@ -29,12 +29,9 @@ type DataStore interface { deployment string, ) ([]*storage.ProcessListeningOnPort, error) WalkAll(ctx context.Context, fn WalkFn) error - RemoveProcessListeningOnPort(ctx context.Context, ids []string) error RemovePlopsByPod(ctx context.Context, id string) error PruneOrphanedPLOPs(ctx context.Context, orphanWindow time.Duration) int64 - PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx context.Context) (int64, error) - RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) } // New creates a data store object to access the database. Since some diff --git a/central/processlisteningonport/datastore/datastore_impl.go b/central/processlisteningonport/datastore/datastore_impl.go index cd8219320a726..3783a97139f7b 100644 --- a/central/processlisteningonport/datastore/datastore_impl.go +++ b/central/processlisteningonport/datastore/datastore_impl.go @@ -306,25 +306,6 @@ func (ds *datastoreImpl) WalkAll(ctx context.Context, fn WalkFn) error { return ds.storage.Walk(ctx, fn) } -func (ds *datastoreImpl) RemoveProcessListeningOnPort(ctx context.Context, ids []string) error { - if ok, err := plopSAC.WriteAllowed(ctx); err != nil { - return err - } else if !ok { - return sac.ErrResourceAccessDenied - } - - return ds.removePLOP(ctx, ids) -} - -func (ds *datastoreImpl) removePLOP(ctx context.Context, ids []string) error { - - if len(ids) == 0 { - return nil - } - - return ds.storage.DeleteMany(ctx, ids) -} - func (ds *datastoreImpl) fetchExistingPLOPs( ctx context.Context, indicatorIds []string, @@ -596,27 +577,6 @@ func (ds *datastoreImpl) PruneOrphanedPLOPs(ctx context.Context, orphanWindow ti return commandTag.RowsAffected() } -// PruneOrphanedPLOPsByProcessIndicators prunes PLOPs that match process indicators without pods -func (ds *datastoreImpl) PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) { - // TODO(ROX-22443): Once it is guaranteed that all listening endpoints have PodUIDs, remove this function - ds.mutex.Lock() - defer ds.mutex.Unlock() - - // Delete processes listening on ports orphaned because process indicators are orphaned due to - // missing deployments - query := fmt.Sprintf(deleteOrphanedPLOPDeploymentsAndPI, int(orphanWindow.Minutes())) - if _, err := ds.pool.Exec(ctx, query); err != nil { - log.Errorf("failed to prune process listening on ports by deployment: %v", err) - } - - // Delete processes listening on ports orphaned because process indicators are orphaned due to - // missing pods. - query = fmt.Sprintf(deleteOrphanedPLOPPods, int(orphanWindow.Minutes())) - if _, err := ds.pool.Exec(ctx, query); err != nil { - log.Errorf("failed to prune process listening on ports by pods: %v", err) - } -} - func (ds *datastoreImpl) readRowsToFindPLOPsWithNoProcessInformation(rows pgx.Rows) ([]string, error) { var ids []string @@ -672,89 +632,3 @@ func (ds *datastoreImpl) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx con return int64(len(plopsToDelete)), nil } - -// Removes PLOPs without poduids between a range of ids. -func (ds *datastoreImpl) removePLOPsWithoutPodUIDOnePage(ctx context.Context, prevId string, nextId string) (int64, error) { - ds.mutex.Lock() - defer ds.mutex.Unlock() - - query := fmt.Sprintf(deletePLOPsWithoutPoduidInPage, prevId, nextId) - commandTag, err := ds.pool.Exec(ctx, query) - - if err != nil { - return 0, err - } - - return commandTag.RowsAffected(), nil -} - -// Given a set of rows with ids, returns the id of the last row. This is useful for pagination. -func (ds *datastoreImpl) getLastIdFromRows(ctx context.Context, rows pgx.Rows) (string, error) { - id := "" - - for rows.Next() { - if err := rows.Scan(&id); err != nil { - return "", pgutils.ErrNilIfNoRows(err) - } - } - - return id, rows.Err() -} - -// Given an id and a limit, returns the id a limit number of rows after the given id. This is useful -// for efficient pagination. -func (ds *datastoreImpl) getNextPageId(ctx context.Context, prevId string, limit int) (string, error) { - ds.mutex.Lock() - defer ds.mutex.Unlock() - - query := fmt.Sprintf(getLastIdFromPage, prevId, limit) - rows, err := ds.pool.Query(ctx, query) - - if err != nil { - // Do not be alarmed if the error is simply NoRows - err = pgutils.ErrNilIfNoRows(err) - if err != nil { - log.Warnf("%s: %s", query, err) - } - return "", err - } - defer rows.Close() - - nextId, err := ds.getLastIdFromRows(ctx, rows) - - if err != nil { - return "", err - } - - return nextId, nil -} - -func (ds *datastoreImpl) retryableRemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - limit := 10000 - totalRows := int64(0) - - prevId := "00000000-0000-0000-0000-000000000000" - for { - nextId, err := ds.getNextPageId(ctx, prevId, limit) - if err != nil { - return totalRows, err - } - if nextId == "" { - break - } - nrows, err := ds.removePLOPsWithoutPodUIDOnePage(ctx, prevId, nextId) - if err != nil { - return totalRows, err - } - totalRows += nrows - prevId = nextId - } - - return totalRows, nil -} - -func (ds *datastoreImpl) RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - return pgutils.Retry2(ctx, func() (int64, error) { - return ds.retryableRemovePLOPsWithoutPodUID(ctx) - }) -} diff --git a/central/processlisteningonport/datastore/datastore_impl_test.go b/central/processlisteningonport/datastore/datastore_impl_test.go index 6acfb31cf64af..66d5619a04dac 100644 --- a/central/processlisteningonport/datastore/datastore_impl_test.go +++ b/central/processlisteningonport/datastore/datastore_impl_test.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "math/rand" - "slices" "testing" "time" @@ -2500,174 +2499,6 @@ func (suite *PLOPDataStoreTestSuite) TestRemoveOrphanedPLOPs() { } } -func newIndicatorWithDeployment(id string, age time.Duration, deploymentID string) *storage.ProcessIndicator { - return &storage.ProcessIndicator{ - Id: id, - DeploymentId: deploymentID, - ContainerName: "", - PodId: "", - Signal: &storage.ProcessSignal{ - Time: protoconv.NowMinus(age), - }, - } -} - -func newIndicatorWithDeploymentAndPod(id string, age time.Duration, deploymentID, podUID string) *storage.ProcessIndicator { - indicator := newIndicatorWithDeployment(id, age, deploymentID) - indicator.PodUid = podUID - return indicator -} - -func (suite *PLOPDataStoreTestSuite) TestRemoveOrphanedPLOPsByProcesses() { - orphanWindow := 30 * time.Minute - - cases := []struct { - name string - initialProcesses []*storage.ProcessIndicator - initialPlops []*storage.ProcessListeningOnPortStorage - deployments set.FrozenStringSet - pods set.FrozenStringSet - expectedPlopDeletions []string - }{ - { - name: "no deployments nor pods - remove all old indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - fixtures.GetPlopStorage4(), - fixtures.GetPlopStorage5(), - fixtures.GetPlopStorage6(), - }, - deployments: set.NewFrozenStringSet(), - pods: set.NewFrozenStringSet(), - expectedPlopDeletions: []string{ - fixtureconsts.PlopUID1, - fixtureconsts.PlopUID2, - fixtureconsts.PlopUID3, - fixtureconsts.PlopUID4, - fixtureconsts.PlopUID5, - fixtureconsts.PlopUID6, - }, - }, - { - name: "no deployments nor pods - remove no new orphaned indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 20*time.Minute, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 20*time.Minute, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(), - pods: set.NewFrozenStringSet(), - expectedPlopDeletions: nil, - }, - { - name: "all pods separate deployments - remove no indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6, fixtureconsts.Deployment5, fixtureconsts.Deployment3), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1, fixtureconsts.PodUID2, fixtureconsts.PodUID3), - expectedPlopDeletions: nil, - }, - { - name: "all pods same deployment - remove no indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID3), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1, fixtureconsts.PodUID2, fixtureconsts.PodUID3), - expectedPlopDeletions: nil, - }, - { - name: "some pods separate deployments - remove some indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment3), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID3), - expectedPlopDeletions: []string{fixtureconsts.PlopUID1}, - }, - { - name: "some pods same deployment - remove some indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment6, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID3), - expectedPlopDeletions: []string{fixtureconsts.PlopUID1}, - }, - } - for _, c := range cases { - suite.T().Run(c.name, func(t *testing.T) { - suite.SetupTest() - // Add deployments if necessary - deploymentDS, err := deploymentStore.GetTestPostgresDataStore(suite.T(), suite.postgres.DB) - suite.Nil(err) - for deploymentID := range c.deployments.All() { - suite.NoError(deploymentDS.UpsertDeployment(suite.hasAllCtx, &storage.Deployment{Id: deploymentID, ClusterId: fixtureconsts.Cluster1})) - } - - for podID := range c.pods.All() { - insertPod := fmt.Sprintf("INSERT INTO pods (id, clusterid) VALUES ('%s', '%s')", podID, fixtureconsts.Cluster1) - _, err := suite.postgres.DB.Exec(suite.hasWriteCtx, insertPod) - suite.Nil(err) - } - - suite.NoError(suite.indicatorDataStore.AddProcessIndicators(suite.hasWriteCtx, c.initialProcesses...)) - countFromDB, err := suite.indicatorDataStore.Count(suite.hasAllCtx, nil) - suite.NoError(err) - suite.Equal(len(c.initialProcesses), countFromDB) - - err = suite.store.UpsertMany(suite.hasWriteCtx, c.initialPlops) - suite.NoError(err) - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(c.initialPlops), plopCount) - - suite.datastore.PruneOrphanedPLOPsByProcessIndicators(suite.hasAllCtx, orphanWindow) - - plopCount, err = suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(c.initialPlops)-len(c.expectedPlopDeletions), plopCount) - - }) - } -} - // RemovePLOPsWithoutProcessIndicatorOrProcessInfo Adds a PLOP with a matching process indicator. // The process indicator is then deleted. This means that the PLOP has no matching // process indicator and does not have any process information. A pruning function @@ -2702,176 +2533,3 @@ func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutProcessIndicatorOrProcess newPlopsFromDB := suite.getPlopsFromDB() suite.Len(newPlopsFromDB, 0) } - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUID() { - initialPlops := []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - fixtures.GetPlopStorage4(), - fixtures.GetPlopStorage5(), - fixtures.GetPlopStorage6(), - } - expectedPlopDeletions := []string{fixtureconsts.PlopUID1, fixtureconsts.PlopUID2, fixtureconsts.PlopUID3} - - err := suite.store.UpsertMany(suite.hasWriteCtx, initialPlops) - suite.NoError(err) - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(initialPlops), plopCount) - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - suite.Equal(int64(3), prunedCount) - - plopCount, err = suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(initialPlops)-len(expectedPlopDeletions), plopCount) - - ids, err := suite.store.GetIDs(suite.hasReadCtx) - suite.NoError(err) - for id := range ids { - suite.NotContains(expectedPlopDeletions, id) - } -} - -func (suite *PLOPDataStoreTestSuite) addTooMany(plops []*storage.ProcessListeningOnPortFromSensor) error { - batchSize := 30000 - - for plopBatch := range slices.Chunk(plops, batchSize) { - // Per-batch deadline so ContextWithTimeoutIfNotExists skips the - // 60-second default without sharing one budget across all batches. - ctx, cancel := context.WithTimeout(suite.hasWriteCtx, 5*time.Minute) - err := suite.datastore.AddProcessListeningOnPort(ctx, fixtureconsts.Cluster1, plopBatch...) - cancel() - if err != nil { - return err - } - } - return nil -} - -func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutPodUIDScale(nport int, nprocess int, npod int) { - plopObjects := makeRandomPlops(nport, nprocess, npod, fixtureconsts.Deployment1) - - plopsWithoutPodUids := 0 - for _, plop := range plopObjects { - p := rand.Float32() - if p < 0.5 { - plop.PodUid = "" - plopsWithoutPodUids++ - } - } - - // Add the PLOPs - suite.Require().NoError(suite.addTooMany(plopObjects)) - - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.Equal(plopCount, 2*nport*nprocess*npod) - suite.NoError(err) - - start := time.Now() - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.Equal(int64(plopsWithoutPodUids), prunedCount) - duration := time.Since(start) - suite.NoError(err) - log.Infof("Pruning %d plops took %s", prunedCount, duration) - _, err = suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScale27K() { - nport := 30 - nprocess := 30 - npod := 30 - - suite.RemovePLOPsWithoutPodUIDScale(nport, nprocess, npod) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScale125K() { - nport := 50 - nprocess := 50 - npod := 50 - - suite.RemovePLOPsWithoutPodUIDScale(nport, nprocess, npod) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScaleRaceCondition() { - var mutex sync.RWMutex - var wg sync.WaitGroup - wg.Add(1) - - running := true - plopsWithoutPodUids := 0 - go func() { - defer wg.Done() - iterations := 3 - for range iterations { - nport := 30 - nprocess := 30 - npod := 30 - plopObjects := makeRandomPlops(nport, nprocess, npod, fixtureconsts.Deployment1) - - for _, plop := range plopObjects { - p := rand.Float32() - if p < 0.5 { - plop.PodUid = "" - plopsWithoutPodUids += 1 - } else { - plop.PodUid = uuid.NewV4().String() - } - } - - // Add the open PLOPs - suite.NoError(suite.addTooMany(plopObjects)) - - // Close the PLOPs - // This is so that UpsertMany will trigger deletes - for _, plop := range plopObjects { - plop.CloseTimestamp = protoconv.ConvertTimeToTimestamp(time.Now()) - } - - // Add the closed PLOPs - suite.NoError(suite.addTooMany(plopObjects)) - } - }() - - totalPrunedCount := 0 - - var wgPrune sync.WaitGroup - wgPrune.Go(func() { - for { - mutex.Lock() - stop := !running - mutex.Unlock() - - if stop { - break - } - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - totalPrunedCount += int(prunedCount) - } - }) - - wg.Wait() - - mutex.Lock() - running = false - mutex.Unlock() - - wgPrune.Wait() - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - totalPrunedCount += int(prunedCount) - - // Each PLOP is opened and closed. If a PLOP is opened then pruned, then closed, - // and pruned again, then it will be pruned twice. If a PLOP is opened, then closed, - // and pruned, then it will be pruned once. Therefore the number of rows pruned will - // be between the number of PLOPs that don't have poduids that were added and twice - // that number. - suite.GreaterOrEqual(plopsWithoutPodUids, totalPrunedCount/2) - suite.LessOrEqual(plopsWithoutPodUids, totalPrunedCount) -} diff --git a/central/processlisteningonport/datastore/mocks/datastore.go b/central/processlisteningonport/datastore/mocks/datastore.go index bc71a23f3157e..053977e9f8ed7 100644 --- a/central/processlisteningonport/datastore/mocks/datastore.go +++ b/central/processlisteningonport/datastore/mocks/datastore.go @@ -91,33 +91,6 @@ func (mr *MockDataStoreMockRecorder) PruneOrphanedPLOPs(ctx, orphanWindow any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneOrphanedPLOPs", reflect.TypeOf((*MockDataStore)(nil).PruneOrphanedPLOPs), ctx, orphanWindow) } -// PruneOrphanedPLOPsByProcessIndicators mocks base method. -func (m *MockDataStore) PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "PruneOrphanedPLOPsByProcessIndicators", ctx, orphanWindow) -} - -// PruneOrphanedPLOPsByProcessIndicators indicates an expected call of PruneOrphanedPLOPsByProcessIndicators. -func (mr *MockDataStoreMockRecorder) PruneOrphanedPLOPsByProcessIndicators(ctx, orphanWindow any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneOrphanedPLOPsByProcessIndicators", reflect.TypeOf((*MockDataStore)(nil).PruneOrphanedPLOPsByProcessIndicators), ctx, orphanWindow) -} - -// RemovePLOPsWithoutPodUID mocks base method. -func (m *MockDataStore) RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemovePLOPsWithoutPodUID", ctx) - ret0, _ := ret[0].(int64) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// RemovePLOPsWithoutPodUID indicates an expected call of RemovePLOPsWithoutPodUID. -func (mr *MockDataStoreMockRecorder) RemovePLOPsWithoutPodUID(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePLOPsWithoutPodUID", reflect.TypeOf((*MockDataStore)(nil).RemovePLOPsWithoutPodUID), ctx) -} - // RemovePLOPsWithoutProcessIndicatorOrProcessInfo mocks base method. func (m *MockDataStore) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx context.Context) (int64, error) { m.ctrl.T.Helper() @@ -147,20 +120,6 @@ func (mr *MockDataStoreMockRecorder) RemovePlopsByPod(ctx, id any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePlopsByPod", reflect.TypeOf((*MockDataStore)(nil).RemovePlopsByPod), ctx, id) } -// RemoveProcessListeningOnPort mocks base method. -func (m *MockDataStore) RemoveProcessListeningOnPort(ctx context.Context, ids []string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveProcessListeningOnPort", ctx, ids) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveProcessListeningOnPort indicates an expected call of RemoveProcessListeningOnPort. -func (mr *MockDataStoreMockRecorder) RemoveProcessListeningOnPort(ctx, ids any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProcessListeningOnPort", reflect.TypeOf((*MockDataStore)(nil).RemoveProcessListeningOnPort), ctx, ids) -} - // WalkAll mocks base method. func (m *MockDataStore) WalkAll(ctx context.Context, fn datastore.WalkFn) error { m.ctrl.T.Helper() diff --git a/central/processlisteningonport/datastore/pruning.go b/central/processlisteningonport/datastore/pruning.go index 1d4da65fcbc26..0b9a484590e9e 100644 --- a/central/processlisteningonport/datastore/pruning.go +++ b/central/processlisteningonport/datastore/pruning.go @@ -1,15 +1,6 @@ package datastore const ( - // Explain Analyze indicated that 2 statements for PLOP is faster than one. - deleteOrphanedPLOPDeploymentsAndPI = `DELETE FROM listening_endpoints WHERE processindicatorid in (SELECT id from process_indicators pi WHERE NOT EXISTS - (SELECT 1 FROM deployments WHERE pi.deploymentid = deployments.Id) AND - (signal_time < now() at time zone 'utc' - INTERVAL '%d MINUTES' OR signal_time is NULL))` - - deleteOrphanedPLOPPods = `DELETE FROM listening_endpoints WHERE processindicatorid in (SELECT id from process_indicators pi WHERE NOT EXISTS - (SELECT 1 FROM pods WHERE pi.poduid = pods.Id) AND - (signal_time < now() at time zone 'utc' - INTERVAL '%d MINUTES' OR signal_time is NULL))` - // Unfortunately if a listening endpoint is marked as being open there is no indication of how old it is. // This leads to a possible race condition where a listening endpoint reaches the database before the deployment, // and the pruning job happens to run before the deployment information arrives in the database. @@ -31,17 +22,4 @@ const ( // if the serialized data has process information. PLOPs without process information are then deleted. getPotentiallyOrphanedPLOPs = `SELECT plop.serialized FROM listening_endpoints plop where NOT EXISTS (select 1 FROM process_indicators proc where plop.processindicatorid = proc.id)` - - // This is used to make pagination more efficient compared to using offset. The ids obtained using this query are used for deleting - // PLOPs without poduids in batches. See the query below this query. - getLastIdFromPage = `WITH tmp as ( - SELECT id FROM listening_endpoints WHERE id > '%s' ORDER BY id LIMIT %d - ) - SELECT id FROM tmp ORDER BY id DESC LIMIT 1` - - // Deletes PLOPs without poduids in batches according to id, which is more efficient than using offset. - // It is possible that new rows may be inserted between the starting and ending ids, after the two ids - // are obtained. Meaning that the number of rows in the page may be different than what is expected for - // the page. This should not cause a problem. - deletePLOPsWithoutPoduidInPage = "DELETE FROM listening_endpoints WHERE poduid is null AND id >= '%s' AND id <= '%s'" ) diff --git a/central/pruning/pruning.go b/central/pruning/pruning.go index dbae515c7df27..a1b82548091de 100644 --- a/central/pruning/pruning.go +++ b/central/pruning/pruning.go @@ -61,8 +61,7 @@ const ( logImbueGCFreq = 24 * time.Hour logImbueWindow = 24 * 7 * time.Hour - alertQueryTimeout = 10 * time.Minute - alertDeleteBatchSize = 5000 + alertQueryTimeout = 10 * time.Minute flowsSemaphoreWeight = 5 @@ -74,14 +73,13 @@ const ( ) var ( - log = logging.LoggerForModule() - pruningCtx = sac.WithAllAccess(context.Background()) - lastClusterPruneTime time.Time - lastLogImbuePruneTime time.Time - lastV1ImagePruneTime time.Time - lastPrunedV1ImageID string - pruningTimeout = env.PostgresDefaultPruningStatementTimeout.DurationSetting() - prunedPLOPsWithoutPodUIDs = false + log = logging.LoggerForModule() + pruningCtx = sac.WithAllAccess(context.Background()) + lastClusterPruneTime time.Time + lastLogImbuePruneTime time.Time + lastV1ImagePruneTime time.Time + lastPrunedV1ImageID string + pruningTimeout = env.PostgresDefaultPruningStatementTimeout.DurationSetting() pruneInterval = env.PruneInterval.DurationSetting() orphanWindow = env.PruneOrphanedWindow.DurationSetting() @@ -671,9 +669,6 @@ func clusterIDsToNegationQuery(clusterIDSet set.FrozenStringSet) *v1.Query { func (g *garbageCollectorImpl) removeOrphanedProcesses() { defer metrics.SetPruningDuration(time.Now(), "Processes") - g.plops.PruneOrphanedPLOPsByProcessIndicators(pruningCtx, orphanWindow) - - log.Info("[PLOP pruning by processes] Pruning of orphaned PLOPs by processes complete") // Prune processes in chunks. First get the ones orphaned by deployments and then go back and // do the same for those orphaned by pod @@ -790,15 +785,6 @@ func (g *garbageCollectorImpl) removeOrphanedPLOPs() { log.Errorf("error removing PLOPs with no matching process indicator or process information: %v", err) } log.Infof("[PLOP pruning] Pruning of %d orphaned PLOPs with no matching process indicator or process information complete", prunedCount) - - // Only run once since we don't expect any new PLOPs without poduids. - if !prunedPLOPsWithoutPodUIDs { - prunedCount, err = g.plops.RemovePLOPsWithoutPodUID(pruningCtx) - if err != nil { - log.Errorf("error removing PLOPs without poduid: %v", err) - } - log.Infof("[PLOP pruning] Prunned %d orphaned PLOPs with no poduid", prunedCount) - } } func (g *garbageCollectorImpl) removeExpiredAdministrationEvents(config *storage.PrivateConfig) { diff --git a/central/pruning/pruning_test.go b/central/pruning/pruning_test.go index a61060837fb1b..7810806dee076 100644 --- a/central/pruning/pruning_test.go +++ b/central/pruning/pruning_test.go @@ -1683,30 +1683,6 @@ func (s *PruningTestSuite) TestRemoveOrphanedPLOPs() { pods: set.NewFrozenStringSet(fixtureconsts.PodUID1), expectedDeletions: []string{}, }, - { - name: "Plop does not have a poduid so it is removed", - initialPlops: []*storage.ProcessListeningOnPortStorage{ - { - Id: plopID1, - Port: 1234, - Protocol: storage.L4Protocol_L4_PROTOCOL_TCP, - CloseTimestamp: nil, - ProcessIndicatorId: fixtureconsts.ProcessIndicatorID1, - Closed: false, - Process: &storage.ProcessIndicatorUniqueKey{ - PodId: fixtureconsts.PodUID1, - ContainerName: "test_container1", - ProcessName: "test_process1", - ProcessArgs: "test_arguments1", - ProcessExecFilePath: "test_path1", - }, - DeploymentId: fixtureconsts.Deployment1, - }, - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment1), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1), - expectedDeletions: []string{plopID1}, - }, } for _, c := range cases { @@ -1718,7 +1694,6 @@ func (s *PruningTestSuite) TestRemoveOrphanedPLOPs() { postgres: db, plops: plopDS, } - prunedPLOPsWithoutPodUIDs = false // Populate some actual data so the query returns what needs deleted deploymentDS, err := deploymentDatastore.GetTestPostgresDataStore(t, db.DB) From 04ad3ae45ee0ffa6966c315b07d14058c4387e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:21:37 +0200 Subject: [PATCH 494/638] chore(deps): bump github.com/segmentio/analytics-go/v3 from 3.3.0 to 3.4.0 (#23206) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 +--- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3dbb7f19cfbbd..8730a77a29fd6 100644 --- a/go.mod +++ b/go.mod @@ -117,7 +117,7 @@ require ( github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 github.com/russellhaering/gosaml2 v0.12.0 github.com/russellhaering/goxmldsig v1.6.1 - github.com/segmentio/analytics-go/v3 v3.3.0 + github.com/segmentio/analytics-go/v3 v3.4.0 github.com/sergi/go-diff v1.4.0 github.com/sigstore/cosign/v3 v3.1.3 github.com/sigstore/rekor v1.5.4 @@ -232,7 +232,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/clipperhouse/displaywidth v0.10.0 // indirect @@ -404,7 +403,6 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sassoftware/relic/v8 v8.2.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect - github.com/segmentio/backo-go v1.0.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect diff --git a/go.sum b/go.sum index 54bcba05a120b..a6307ca6fc894 100644 --- a/go.sum +++ b/go.sum @@ -275,8 +275,6 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -1217,10 +1215,8 @@ github.com/sassoftware/relic/v8 v8.2.0/go.mod h1:pZy7hLT9WCOKPonV8G/fplvtBLOZd6/ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= -github.com/segmentio/analytics-go/v3 v3.3.0 h1:8VOMaVGBW03pdBrj1CMFfY9o/rnjJC+1wyQHlVxjw5o= -github.com/segmentio/analytics-go/v3 v3.3.0/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE= -github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= -github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= +github.com/segmentio/analytics-go/v3 v3.4.0 h1:8ACTF5ggK3W70b+kHId5SyxyWGYZ1sdgd/e8tb8jMRM= +github.com/segmentio/analytics-go/v3 v3.4.0/go.mod h1:l0qTADX69i/7AuXvN3tg0QMfcW4Oe6YN5VgsgqGaAPI= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= From 726a1bc1dd5a2b28e9338ca64b951d2036b70c32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:44:01 +0200 Subject: [PATCH 495/638] chore(deps): bump github.com/adhocore/gronx from 1.20.4 to 1.20.5 (#23204) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8730a77a29fd6..b27ae1cd889d1 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/RoaringBitmap/roaring/v2 v2.29.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 - github.com/adhocore/gronx v1.20.4 + github.com/adhocore/gronx v1.20.5 github.com/andygrunwald/go-jira v1.17.0 github.com/aws/aws-sdk-go-v2 v1.47.1 github.com/aws/aws-sdk-go-v2/config v1.33.6 diff --git a/go.sum b/go.sum index a6307ca6fc894..6e90f581aa861 100644 --- a/go.sum +++ b/go.sum @@ -182,8 +182,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= -github.com/adhocore/gronx v1.20.4 h1:vE2UKHC3mRH4DF+tKiq/kOnENTuKaEois2aD0McyHiU= -github.com/adhocore/gronx v1.20.4/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.20.5 h1:UgXvkD97Hovm7yA1TTRTd309o9F1h7C8gX04rktaxnE= +github.com/adhocore/gronx v1.20.5/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= From 078b0591fa78f8ba5e4da93aa60cd2127139d3b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:20:00 +0200 Subject: [PATCH 496/638] chore(deps): bump github.com/prometheus/common from 0.71.0 to 0.72.0 (#23205) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b27ae1cd889d1..2598452d66714 100644 --- a/go.mod +++ b/go.mod @@ -110,7 +110,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.3 - github.com/prometheus/common v0.71.0 + github.com/prometheus/common v0.72.0 github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815 github.com/quay/claircore/toolkit v1.7.0 github.com/quay/zlog/v2 v2.1.1 diff --git a/go.sum b/go.sum index 6e90f581aa861..1ef94600e1504 100644 --- a/go.sum +++ b/go.sum @@ -1156,8 +1156,8 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= -github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= +github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4= +github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= From cf4ed53233d591c913ee13d7496f90f1f149ffa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:30:08 +0200 Subject: [PATCH 497/638] chore(deps): bump cloud.google.com/go/compute/metadata from 0.9.1 to 0.10.0 (#23203) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2598452d66714..0b7f76c6842a3 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.7 require ( cloud.google.com/go/artifactregistry v1.27.0 - cloud.google.com/go/compute/metadata v0.9.1 + cloud.google.com/go/compute/metadata v0.10.0 cloud.google.com/go/containeranalysis v0.20.0 cloud.google.com/go/securitycenter v1.47.0 cloud.google.com/go/storage v1.68.0 diff --git a/go.sum b/go.sum index 1ef94600e1504..91941176411df 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= -cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= +cloud.google.com/go/compute/metadata v0.10.0 h1:pyKMUQSwchgkIBBJGdILqQbs/BNJXqwSA7Ej6LAvvtY= +cloud.google.com/go/compute/metadata v0.10.0/go.mod h1:rGFHRrIif570kSibjFTMbt6/4/tzgJWFGI/HVol4GIk= cloud.google.com/go/containeranalysis v0.20.0 h1:5tko84zYirVH+FEU6JPuT/xGBvSagsllYxH7m8ytjyI= cloud.google.com/go/containeranalysis v0.20.0/go.mod h1:cJzgbuNqFTWswsVcT0eemqdqtOEq4dJEFvAH3cgzkyc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= From 65992843ad76547b0b365abd0a0dc745ef12b391 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:43:34 +0200 Subject: [PATCH 498/638] chore(deps): bump github.com/golangci/golangci-lint/v2 from 2.13.2 to 2.14.0 in /tools/linters (#22571) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yann Brillouet --- .../datastore/store/postgres/store_test.go | 6 +- .../migration_test.go | 12 +- tools/linters/go.mod | 46 ++++---- tools/linters/go.sum | 110 +++++++++--------- 4 files changed, 86 insertions(+), 88 deletions(-) diff --git a/central/node/datastore/store/postgres/store_test.go b/central/node/datastore/store/postgres/store_test.go index 07ff3e3018783..8c3ca0de4cc41 100644 --- a/central/node/datastore/store/postgres/store_test.go +++ b/central/node/datastore/store/postgres/store_test.go @@ -284,7 +284,7 @@ func (s *NodesStoreSuite) TestStore_OrphanedCVEs() { s.NoError(err) s.NotEmpty(orphanedCVEs) for _, cve := range orphanedCVEs { - s.NotNil(cve.OrphanedTime) + s.NotNil(cve.GetOrphanedTime()) s.True(vulnNames.Contains(cve.GetCveBaseInfo().GetCve())) } @@ -311,8 +311,8 @@ func (s *NodesStoreSuite) TestStore_OrphanedCVEs() { s.NoError(err) s.NotEmpty(nodeCVEs) for _, cve := range nodeCVEs { - s.False(cve.Orphaned) - s.Nil(cve.OrphanedTime) + s.False(cve.GetOrphaned()) + s.Nil(cve.GetOrphanedTime()) val, ok := orphanedCveIDToCve[cve.GetId()] s.True(ok) s.Equal(val.GetCveBaseInfo().GetCreatedAt(), cve.GetCveBaseInfo().GetCreatedAt()) diff --git a/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go b/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go index 1f3d1e59d47de..7bfbd94cdfe51 100644 --- a/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go +++ b/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go @@ -63,17 +63,17 @@ func (s *migrationTestSuite) TestMigration() { search.NewQueryBuilder().AddExactMatches(search.Cluster, clusters[0].GetName()).ProtoQuery()) assert.NoError(s.T(), err) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), false) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), false) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), false) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), false) result, err = store.GetByQuery(s.ctx, search.NewQueryBuilder().AddExactMatches(search.Cluster, clusters[1].GetName()).ProtoQuery()) assert.NoError(s.T(), err) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetScanInline(), true) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetEnabled(), true) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetEnforceOnUpdates(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), true) } diff --git a/tools/linters/go.mod b/tools/linters/go.mod index 4d3dd838c74ef..83ba756a9d4ee 100644 --- a/tools/linters/go.mod +++ b/tools/linters/go.mod @@ -4,7 +4,7 @@ go 1.26.7 require ( actionlint.kjanat.dev v1.17.0 - github.com/golangci/golangci-lint/v2 v2.13.2 + github.com/golangci/golangci-lint/v2 v2.14.0 github.com/nilslice/protolock v0.17.0 github.com/sethvargo/ratchet v0.12.0 golang.org/x/vuln v1.8.0 @@ -14,12 +14,12 @@ require ( 4d63.com/gocheckcompilerdirectives v1.4.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect charm.land/lipgloss/v2 v2.0.6 // indirect - codeberg.org/chavacava/garif v0.2.0 // indirect + codeberg.org/chavacava/garif v0.2.1 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/exhaustruct/v5 v5.0.3 // indirect + dev.gaijin.team/go/exhaustruct/v5 v5.2.0 // indirect dev.gaijin.team/go/golib v0.8.1 // indirect - github.com/4meepo/tagalign v1.4.3 // indirect + github.com/4meepo/tagalign v1.4.4 // indirect github.com/Abirdcfly/dupword v0.1.8 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect github.com/AlwxSin/noinlineerr v1.0.6 // indirect @@ -33,7 +33,7 @@ require ( github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect github.com/alecthomas/chroma/v2 v2.27.0 // indirect - github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.1.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect @@ -79,8 +79,8 @@ require ( github.com/firefart/nonamedreturns v1.0.8 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.21 // indirect - github.com/go-critic/go-critic v0.14.4 // indirect + github.com/ghostiam/protogetter v1.0.1 // indirect + github.com/go-critic/go-critic v0.15.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -91,8 +91,8 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/godoc-lint/godoc-lint v0.11.2 // indirect - github.com/gofrs/flock v0.13.0 // indirect + github.com/godoc-lint/godoc-lint v0.11.4 // indirect + github.com/gofrs/flock v0.13.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 // indirect @@ -131,10 +131,10 @@ require ( github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/ldez/exptostd v0.4.5 // indirect - github.com/ldez/gomoddirectives v0.9.0 // indirect + github.com/ldez/gomoddirectives v0.10.0 // indirect github.com/ldez/grignotin v0.10.1 // indirect github.com/ldez/structtags v0.6.1 // indirect - github.com/ldez/tagliatelle v0.7.2 // indirect + github.com/ldez/tagliatelle v0.8.0 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lucasb-eyer/go-colorful v1.4.1 // indirect @@ -150,13 +150,13 @@ require ( github.com/mattn/go-runewidth v0.0.28 // indirect github.com/mattn/go-shellwords v1.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.15.0 // indirect + github.com/mgechev/revive v1.17.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/exhaustive v0.13.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.24.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -172,7 +172,7 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.3.0 // indirect + github.com/raeperd/recvcheck v0.3.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.16.0 // indirect @@ -183,11 +183,11 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.28.0 // indirect - github.com/sirupsen/logrus v1.10.1 // indirect + github.com/securego/gosec/v2 v2.29.0 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.5.1 // indirect - github.com/sourcegraph/go-diff v0.8.0 // indirect + github.com/sourcegraph/go-diff v0.9.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -200,8 +200,8 @@ require ( github.com/stretchr/testify v1.12.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.6 // indirect - github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect - github.com/timonwong/loggercheck v0.11.0 // indirect + github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c // indirect + github.com/timonwong/loggercheck v0.12.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect @@ -209,7 +209,7 @@ require ( github.com/uudashr/gocognit v1.2.1 // indirect github.com/uudashr/iface v1.5.1 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/xo/terminfo v1.0.0 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -217,7 +217,7 @@ require ( go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.12.0 // indirect go.augendre.info/arangolint v0.4.0 // indirect - go.augendre.info/fatcontext v0.10.0 // indirect + go.augendre.info/fatcontext v0.10.1 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -230,11 +230,11 @@ require ( golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/tools v0.50.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/protobuf v1.36.12 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.8.1 // indirect - mvdan.cc/gofumpt v0.11.0 // indirect + mvdan.cc/gofumpt v0.12.0 // indirect mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 // indirect ) diff --git a/tools/linters/go.sum b/tools/linters/go.sum index 3b5529ba3af82..9e857582744b5 100644 --- a/tools/linters/go.sum +++ b/tools/linters/go.sum @@ -38,19 +38,19 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +codeberg.org/chavacava/garif v0.2.1 h1:K9oYxSlvlXrHXyW26Z4q61bTpJDo1wbvXcYKar/F/LM= +codeberg.org/chavacava/garif v0.2.1/go.mod h1:oHnDSmc0f9K1MeE+MQD/yjkiIB5Xsn5y3S9Dg96Xk84= codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/exhaustruct/v5 v5.0.3 h1:yOeA7DNjlT8y4yfmN6nWWYYggA13N523YAj9/TXbuTM= -dev.gaijin.team/go/exhaustruct/v5 v5.0.3/go.mod h1:KwtBsX8nHHH1YxhxkpiBq6bfsmw5WnazWpNvJPHgY9Y= +dev.gaijin.team/go/exhaustruct/v5 v5.2.0 h1:HZB49Fshn8qHJCi9VzYtrcnR1jbLteRHXfnQz8Adams= +dev.gaijin.team/go/exhaustruct/v5 v5.2.0/go.mod h1:5IshpqhgGdKIrF8lBmi3M6YKmI7T3hMzAL9nrflko88= dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E= dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= -github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= +github.com/4meepo/tagalign v1.4.4 h1:052JDGj2pBORc6NkD30dDXpsUpoYszDeUwr9/5YZuVA= +github.com/4meepo/tagalign v1.4.4/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.8 h1:SrhcUuGsROBuChFxHALRYzyyPODWn9zwghmzPvD9Cd8= github.com/Abirdcfly/dupword v0.1.8/go.mod h1:XZrhVnI7YGpsTiWZANSQaBJ4QpL/Tq5vIEdKJJAs9WI= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= @@ -81,8 +81,8 @@ github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8v github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= -github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= -github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606 h1:dXPJ+uOJLWb8rjlAuLX7JPiRJtZKw/53Ol+WxdDJ1Eo= +github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606/go.mod h1:ZH31IJyNpxpStxP2ayCcPQz+3T2G/OnxIQqe4LWvSDw= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -172,9 +172,8 @@ github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEy github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= @@ -205,10 +204,10 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.21 h1:EeWTGvL/Eyosp653hiWb6Byx4b69iJC4/E+za7vQHoI= -github.com/ghostiam/protogetter v0.3.21/go.mod h1:iAKSpyoHwYzay+OpjoWgwzRtPFthEfuUvmlomTThck0= -github.com/go-critic/go-critic v0.14.4 h1:dSX4C3pWSeuMVxvQh6yG8U0ReSf3YOmKi4nwX5q7n/8= -github.com/go-critic/go-critic v0.14.4/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= +github.com/ghostiam/protogetter v1.0.1 h1:q4LLTx2KJHJpxrfO3BniUDWoFkoqWD/UlHhClBlUlLM= +github.com/ghostiam/protogetter v1.0.1/go.mod h1:50Fh5yZfs6SFGrd1FlmT04NcguyEOxyjNK5zGD6/EvM= +github.com/go-critic/go-critic v0.15.0 h1:MDUZIQKytIl+vPEc0Igw51m4uLTdi1ZGz2jatEqNs1g= +github.com/go-critic/go-critic v0.15.0/go.mod h1:3BPrw8nHATGm3o2n37cssOmJzpySGKCdY8JWkCzxgKQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -218,8 +217,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -250,10 +249,10 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= -github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/godoc-lint/godoc-lint v0.11.4 h1:N9zUYVJ/OkznBbrBsU/zODvua64EUplMBLQWStO3+1M= +github.com/godoc-lint/godoc-lint v0.11.4/go.mod h1:7usTXoxS0QYIi5usTSCPwR0w/6xLjcz450shXjLn3t4= +github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= +github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -294,8 +293,8 @@ github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarog github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg= github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA= -github.com/golangci/golangci-lint/v2 v2.13.2 h1:bCyq3E4vo9qwzifjpzJqYndEt7Ncva80qkk8G2B4TXU= -github.com/golangci/golangci-lint/v2 v2.13.2/go.mod h1:5xaMd1kAxV7GSBPEyngw8gnnjoRocqW9UVKdBR6627w= +github.com/golangci/golangci-lint/v2 v2.14.0 h1:ot8QffRa4LzAAEgtvNYrVs9esxQ7xoTwoAv6uhp4ngA= +github.com/golangci/golangci-lint/v2 v2.14.0/go.mod h1:amHimCzNDP1GfVno3hIiNssjvqXmuW+CPZAHOdR4cIc= github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= @@ -344,8 +343,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -422,14 +421,14 @@ github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJ github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= -github.com/ldez/gomoddirectives v0.9.0 h1:2YV/EX7nVlWL4jySusYTzBKHuE3D2fgcRsQuMa3yIoo= -github.com/ldez/gomoddirectives v0.9.0/go.mod h1:DdZzfm9MdXCjn2/UGYXCFfo+tzrp2Ib4iD2Q0kIJkwE= +github.com/ldez/gomoddirectives v0.10.0 h1:Zb23rfMMkG21B6pZ9OfESzemByP5Qr7YvWcXQZE/S3Q= +github.com/ldez/gomoddirectives v0.10.0/go.mod h1:W9uh0721Vc7aaVoiMXTpKIveLmtHC9uaOSiHM4OK5a8= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= -github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= -github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= +github.com/ldez/tagliatelle v0.8.0 h1:QczqVPa22JdT40p3m5JTTVmPMv5wDORqdomJn6/Pnn0= +github.com/ldez/tagliatelle v0.8.0/go.mod h1:K9n83Wv0wyVq3JYF9WI28BX8xXhDBVP3thHJ0ENuiEs= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= @@ -462,8 +461,8 @@ github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= -github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= +github.com/mgechev/revive v1.17.0 h1:hwTTVC3svSv9X2FA2hiYT17Uws+lfxDWhPxzBZZCREg= +github.com/mgechev/revive v1.17.0/go.mod h1:uiVNUF8ruVh4WHm/aPs7kKEeuo+m6oO3tuOfniyzP/g= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -483,14 +482,14 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nilslice/protolock v0.17.0 h1:sYvcukABl62tZX77H6NuV+jtlwTIfQbn0ln0ixTqr4A= github.com/nilslice/protolock v0.17.0/go.mod h1:DYFqop7QlHjmBCaJKfcVO1Mw5b8JejJZgMvmFng/N9Y= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/exhaustive v0.13.0 h1:Rof0axOSNQaaPaPmLK5RxcRrJV2Vcv+vfUmuRy+2EAM= +github.com/nishanths/exhaustive v0.13.0/go.mod h1:3/adQjJgvD/UFHHSYBjpK9Wbz8DOvf0FBtzx+zI0Rlo= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8= github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c= -github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= -github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -544,8 +543,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.3.0 h1:PM+XYvyxIj3bo+kobJfFTdTuU3Lmfu96mKDbyHDbRt8= -github.com/raeperd/recvcheck v0.3.0/go.mod h1:PZNwG+HztFYMH2ZPq0Hu3QgkV2yiA6VrtNz9c1fXWJo= +github.com/raeperd/recvcheck v0.3.1 h1:p7nZMxiCWkdUtY2i3o+pv1h4fJCR5gpvqAXr9326hEY= +github.com/raeperd/recvcheck v0.3.1/go.mod h1:CbE7iRocQ4uc71JwpW/CPSh4BShnkre4A9Gb508RypU= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -568,8 +567,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.28.0 h1:ZsSdiDb0AtTpLFVol5z91gbMei9ZiLEPG/pZjZujp7c= -github.com/securego/gosec/v2 v2.28.0/go.mod h1:lb4/9AHe+lJy/kjWmWRWWsEipvbwGKuxf+tY1Pmjdnk= +github.com/securego/gosec/v2 v2.29.0 h1:pF2HSLcnY5voqpxQumEe0O5YAAbvF4Syu4qeIewpLyc= +github.com/securego/gosec/v2 v2.29.0/go.mod h1:47C7ej+L2bC1NOxteU2aCGnCi4cZaoKzRt5y9plIvuY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/ratchet v0.12.0 h1:UY1sEevYHi28ryw9c8eHOLonc6oePZDUXHbzx0ESQqw= @@ -577,14 +576,14 @@ github.com/sethvargo/ratchet v0.12.0/go.mod h1:Unu+6zOcsnJj5x/hKgiaRKbGKPZkysoYk github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= -github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= -github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY= -github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= +github.com/sourcegraph/go-diff v0.9.0 h1:7xcVsayWHhvxZGrqt1o89vvBApCa4nyCgO7tNxDHJ+Q= +github.com/sourcegraph/go-diff v0.9.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -620,10 +619,10 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= -github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= -github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= -github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c h1:d/FUP8GD4QiY/rdFzZmBu3KqL9eBNVzMu7W/5pGkaMM= +github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= +github.com/timonwong/loggercheck v0.12.0 h1:7Cb9XGLJZSWYNjgL459DIhpSiEHupD+MLQ6vaN2Pl3g= +github.com/timonwong/loggercheck v0.12.0/go.mod h1:Wa6Y+SJHQR01mViZlLLY5GAZVOWOLNMbkXddMjFWQCs= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= @@ -638,8 +637,8 @@ github.com/uudashr/iface v1.5.1 h1:BS3yrgaT55s3dAtJoxuTnsZNbsejq/mVuzp49YDyfBA= github.com/uudashr/iface v1.5.1/go.mod h1:5UWoT6SvTdTww/KToRj6clO+n4Kg3/NrAQvgOB+5Ggw= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY= +github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= @@ -662,8 +661,8 @@ go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= -go.augendre.info/fatcontext v0.10.0 h1:HhFopmivh8U1+AU7f0kuwUeg2eiIns7YsGQOMHwSJ90= -go.augendre.info/fatcontext v0.10.0/go.mod h1:pqpGvA9GlrXy+aXkp8L2dKz12Zp4g2FhzcAtwToU+2w= +go.augendre.info/fatcontext v0.10.1 h1:HCS/+eob32cM0mEtmZM6NiXdqvS5KiZmsR+fCubTpbc= +go.augendre.info/fatcontext v0.10.1/go.mod h1:pqpGvA9GlrXy+aXkp8L2dKz12Zp4g2FhzcAtwToU+2w= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -985,14 +984,13 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1016,8 +1014,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w= honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= -mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc= -mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo= +mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho= +mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA= mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew= mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8/go.mod h1:MrS/+zJ1M2xvGXhKktiHbNQeQPyg3Qel+KkzT+f7/i4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 21230a6b546de186dbde0fc7292668a97af3c25a Mon Sep 17 00:00:00 2001 From: Saif Chaudhry Date: Fri, 2 Oct 2026 07:28:18 -0700 Subject: [PATCH 499/638] ROX-37321: Use workflow-provided tag for Konflux E2E tests (#23136) Signed-off-by: Saif Chaudhry --- qa-tests-backend/scripts/run-part-1.sh | 3 --- tests/e2e/lib.sh | 31 -------------------------- 2 files changed, 34 deletions(-) diff --git a/qa-tests-backend/scripts/run-part-1.sh b/qa-tests-backend/scripts/run-part-1.sh index 90a8bf9d3ec3e..d05a0f7a3cd16 100755 --- a/qa-tests-backend/scripts/run-part-1.sh +++ b/qa-tests-backend/scripts/run-part-1.sh @@ -76,9 +76,6 @@ EOF if [[ "${USE_KONFLUX_IMAGES:-false}" == "true" ]]; then info "Environment contains USE_KONFLUX_IMAGES=true, will be using Konflux-built images for deploying StackRox" patch_yaml "$config_file" ".roxie.konfluxImages = true" - elif pr_has_label test-konflux-images; then - info "PR label 'test-konflux-images' detected, will be using Konflux-built images for deploying StackRox" - patch_yaml "$config_file" ".roxie.konfluxImages = true" fi deploy_stackrox_with_roxie_compat "$config_file" setup_client_TLS_certs "$ROOT/$DEPLOY_DIR/client_TLS_certs" diff --git a/tests/e2e/lib.sh b/tests/e2e/lib.sh index a3e31a2da973b..8f3f484b202e8 100755 --- a/tests/e2e/lib.sh +++ b/tests/e2e/lib.sh @@ -157,8 +157,6 @@ deploy_stackrox_with_roxie() { ROX_ADMIN_PASSWORD="$(gen_admin_password)" export ROX_ADMIN_PASSWORD # Let roxie pick it up automatically. - prepare_for_konflux "$config_file" - workaround_label_length_limitation "$config_file" # Print out the config file in use for transparency. @@ -212,35 +210,6 @@ deploy_stackrox_with_roxie() { info "╚═════════════════════╝" } -prepare_for_konflux() { - local config_file="$1" - local use_konflux - use_konflux=$(yq eval ".roxie.konfluxImages" "$config_file") - local main_image_tag - main_image_tag=$(yq eval ".roxie.version" "$config_file") - if [[ "$use_konflux" == "true" ]]; then - # We need to be able to pull operator bundle images. - registry_ro_login "quay.io/rhacs-eng" - - info "Checking if ACS main image tag needs to be patched for Konflux usage: current tag is ${main_image_tag}" - if is_CI; then - # get_branch_name() may only be called in CI context. - local branch_name - branch_name="$(get_branch_name)" - if [[ "$branch_name" =~ ^release- ]]; then - info "On release branch (${branch_name}), skipping main image tag patching for Konflux usage" - return - fi - fi - info "Patching main image tag for Konflux usage: using ${main_image_tag}" - if [[ "$main_image_tag" != *-fast ]]; then - main_image_tag="${main_image_tag}-fast" - patch_yaml "$config_file" ".roxie.version = \"${main_image_tag}\"" - info "Main image tag patched for Konflux usage: ${main_image_tag}" - fi - fi -} - # When deploying Konflux-built images, we might get an additional "-fast" suffix on the main image version, # which can easily cause the Helm chart labels to exceed the 63 character limit. To work around this, we # use shorter labels for the the roxie-deployed resources. From 7fdaba163cc0cb24f009a70ba8ff0bd7d90cb56a Mon Sep 17 00:00:00 2001 From: Saif Chaudhry Date: Fri, 2 Oct 2026 09:24:59 -0700 Subject: [PATCH 500/638] ROX-37315: Keep deploy checks with image exclusions (#23107) Signed-off-by: Saif Chaudhry --- CHANGELOG.md | 1 + central/policy/matcher/cluster.go | 4 + central/policy/matcher/deployment.go | 4 + central/policy/matcher/matcher.go | 7 + central/policy/matcher/matcher_test.go | 38 +++++ central/policy/matcher/namespace.go | 2 +- pkg/detection/compiled_policy.go | 11 +- pkg/detection/compiled_policy_test.go | 11 ++ .../deploytime/detector_impl_test.go | 138 ++++++++++++++++++ pkg/detection/exclusion_test.go | 22 +++ 10 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 central/policy/matcher/matcher_test.go create mode 100644 pkg/detection/deploytime/detector_impl_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2a12a8afb7e..b67d569ffd14c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-36534: roxctl binaries in the Central image are now stored as `.tar.gz` archives. Central extracts and streams the binary on download, so the user-facing download behavior is unchanged. - ROX-35137: Central now prunes deployments whose cluster no longer exists in the database. - ROX-37048: Central writes its version in `Rh-Central-Version` gRPC response header to authenticated clients. `roxctl` compares this version with its own version and reports a warning to stderr if the version skew is outside of a supported range. +- ROX-37315: Image exclusions no longer turn off deploy-time checks. Previously, an image exclusion on a policy with Build and Deploy stages stopped that policy from raising deploy-time violations or blocking any deployment. Image exclusions now apply only at Build, so after upgrading, affected policies raise deploy-time violations again and block deployments if enforcement is on. To skip apps at deploy time, use deployment exclusions. ## [5.0.0] diff --git a/central/policy/matcher/cluster.go b/central/policy/matcher/cluster.go index b1b57f1bf151a..623a74c2b7a5c 100644 --- a/central/policy/matcher/cluster.go +++ b/central/policy/matcher/cluster.go @@ -48,6 +48,10 @@ func (m *clusterMatcher) anyExclusionMatches(exclusions []*storage.Exclusion) bo } func (m *clusterMatcher) exclusionMatches(exclusion *storage.Exclusion) bool { + if !appliesToDeployments(exclusion) { + return false + } + cs, err := scopecomp.CompileScope(exclusion.GetDeployment().GetScope(), nil, nil) if err != nil { utils.Should(errors.Wrap(err, "could not compile excluded scopes")) diff --git a/central/policy/matcher/deployment.go b/central/policy/matcher/deployment.go index 6058438bc79e8..2098b4090e8ce 100644 --- a/central/policy/matcher/deployment.go +++ b/central/policy/matcher/deployment.go @@ -56,6 +56,10 @@ func (m *deploymentMatcher) anyExclusionMatches(ctx context.Context, exclusions } func (m *deploymentMatcher) exclusionMatches(ctx context.Context, exclusion *storage.Exclusion) bool { + if !appliesToDeployments(exclusion) { + return false + } + // If excluded scope does not match the deployment then no need to check for deployment name if !m.scopeMatches(ctx, exclusion.GetDeployment().GetScope()) { return false diff --git a/central/policy/matcher/matcher.go b/central/policy/matcher/matcher.go index a80cd6c64ad54..4ecf17b5645f1 100644 --- a/central/policy/matcher/matcher.go +++ b/central/policy/matcher/matcher.go @@ -11,3 +11,10 @@ type Matcher interface { FilterApplicablePolicies(ctx context.Context, policies []*storage.Policy) (applicable []*storage.Policy, notApplicable []*storage.Policy) IsPolicyApplicable(ctx context.Context, policy *storage.Policy) bool } + +// appliesToDeployments reports whether the exclusion has a deployment part. Image-only exclusions +// are applied to images, so they must not exclude deployments, namespaces, or clusters. Without this +// check, the nil deployment scope matches everything and the policy looks inapplicable everywhere. +func appliesToDeployments(exclusion *storage.Exclusion) bool { + return exclusion.GetDeployment() != nil +} diff --git a/central/policy/matcher/matcher_test.go b/central/policy/matcher/matcher_test.go new file mode 100644 index 0000000000000..f142435bbbc00 --- /dev/null +++ b/central/policy/matcher/matcher_test.go @@ -0,0 +1,38 @@ +package matcher + +import ( + "context" + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stretchr/testify/assert" +) + +// TestImageOnlyExclusionDoesNotExcludeEntities checks that an image-only exclusion leaves the policy +// applicable to every deployment, namespace, and cluster, while a deployment exclusion still excludes. +func TestImageOnlyExclusionDoesNotExcludeEntities(t *testing.T) { + ctx := context.Background() + deployment := &storage.Deployment{Name: "payments", ClusterId: "cluster1", Namespace: "shop"} + namespace := &storage.NamespaceMetadata{Name: "shop", ClusterId: "cluster1"} + cluster := &storage.Cluster{Id: "cluster1"} + + matchers := map[string]Matcher{ + "deployment": NewDeploymentMatcher(deployment, nil, nil), + "namespace": NewNamespaceMatcher(namespace), + "cluster": NewClusterMatcher(cluster, []*storage.NamespaceMetadata{namespace}), + } + + imageOnly := &storage.Policy{Exclusions: []*storage.Exclusion{ + {Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}}, + }} + clusterExclusion := &storage.Policy{Exclusions: []*storage.Exclusion{ + {Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Cluster: "cluster1"}}}, + }} + + for name, m := range matchers { + t.Run(name, func(t *testing.T) { + assert.True(t, m.IsPolicyApplicable(ctx, imageOnly), "image-only exclusion should not exclude") + assert.False(t, m.IsPolicyApplicable(ctx, clusterExclusion), "cluster-scoped exclusion should exclude") + }) + } +} diff --git a/central/policy/matcher/namespace.go b/central/policy/matcher/namespace.go index 1cf53532e9f72..4d4f182056dde 100644 --- a/central/policy/matcher/namespace.go +++ b/central/policy/matcher/namespace.go @@ -46,7 +46,7 @@ func (m *namespaceMatcher) anyExclusionMatches(exclusions []*storage.Exclusion) } func (m *namespaceMatcher) exclusionMatches(exclusion *storage.Exclusion) bool { - return m.scopeMatches(exclusion.GetDeployment().GetScope()) + return appliesToDeployments(exclusion) && m.scopeMatches(exclusion.GetDeployment().GetScope()) } func (m *namespaceMatcher) anyScopeMatches(scopes []*storage.Scope) bool { diff --git a/pkg/detection/compiled_policy.go b/pkg/detection/compiled_policy.go index 9fedc810495f5..5f64900b4f543 100644 --- a/pkg/detection/compiled_policy.go +++ b/pkg/detection/compiled_policy.go @@ -514,8 +514,15 @@ func newCompiledExclusion(exclusion *storage.Exclusion) (*compiledExclusion, err return cx, nil } +// appliesToDeployments reports whether the exclusion has a deployment part. Image-only exclusions are +// applied to images, so they must not match deployments or audit events. Without this check, the nil +// deployment scope matches everything and the exclusion turns the policy off for every deployment. +func (cw *compiledExclusion) appliesToDeployments() bool { + return cw.exclusion.GetDeployment() != nil +} + func (cw *compiledExclusion) MatchesDeployment(ctx context.Context, deployment *storage.Deployment) bool { - if exclusionIsExpired(cw.exclusion) { + if !cw.appliesToDeployments() || exclusionIsExpired(cw.exclusion) { return false } @@ -527,7 +534,7 @@ func (cw *compiledExclusion) MatchesDeployment(ctx context.Context, deployment * } func (cw *compiledExclusion) MatchesAuditEvent(ctx context.Context, auditEvent *storage.KubernetesEvent) bool { - if exclusionIsExpired(cw.exclusion) { + if !cw.appliesToDeployments() || exclusionIsExpired(cw.exclusion) { return false } if !cw.cs.MatchesAuditEvent(ctx, auditEvent) { diff --git a/pkg/detection/compiled_policy_test.go b/pkg/detection/compiled_policy_test.go index db16cf30375cf..8b61fc306e9b6 100644 --- a/pkg/detection/compiled_policy_test.go +++ b/pkg/detection/compiled_policy_test.go @@ -68,6 +68,17 @@ func TestCompiledPolicyScopesAndExclusions(t *testing.T) { exclusions: []*storage.Exclusion{{Deployment: &storage.Exclusion_Deployment{Scope: appStackRoxScope}}}, shouldApplyTo: []*storage.Deployment{stackRoxNSDep}, }, + { + desc: "image-only exclusion does not exclude any deployment", + exclusions: []*storage.Exclusion{{Image: &storage.Exclusion_Image{Name: "docker.io/library/unrelated"}}}, + shouldApplyTo: []*storage.Deployment{stackRoxNSDep, defaultNSDep, appStackRoxDep}, + }, + { + desc: "only stackrox ns, with an image-only exclusion", + scopes: []*storage.Scope{stackRoxNSScope}, + exclusions: []*storage.Exclusion{{Image: &storage.Exclusion_Image{Name: "docker.io/library/unrelated"}}}, + shouldApplyTo: []*storage.Deployment{stackRoxNSDep, appStackRoxDep}, + }, { desc: "only default ns", scopes: []*storage.Scope{defaultNSScope}, diff --git a/pkg/detection/deploytime/detector_impl_test.go b/pkg/detection/deploytime/detector_impl_test.go new file mode 100644 index 0000000000000..4a8d896e7c550 --- /dev/null +++ b/pkg/detection/deploytime/detector_impl_test.go @@ -0,0 +1,138 @@ +package deploytime + +import ( + "context" + "testing" + "time" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/booleanpolicy" + "github.com/stackrox/rox/pkg/booleanpolicy/fieldnames" + "github.com/stackrox/rox/pkg/booleanpolicy/policyversion" + "github.com/stackrox/rox/pkg/detection" + "github.com/stackrox/rox/pkg/protocompat" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + excludedImage = "registry.example.com/team/legacy-app:1.0" + vulnerableImage = "registry.example.com/team/payments:2.3" +) + +// oldCriticalCVEPolicy mirrors a "Block Critical CVE older than 180 days" policy that runs at +// both the build and deploy stages and excludes a single image. +func oldCriticalCVEPolicy(exclusions ...*storage.Exclusion) *storage.Policy { + return &storage.Policy{ + Id: "old-critical-cve", + Name: "Block Critical CVE older than 180 days", + PolicyVersion: policyversion.CurrentVersion().String(), + Severity: storage.Severity_CRITICAL_SEVERITY, + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD, storage.LifecycleStage_DEPLOY}, + Exclusions: exclusions, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{ + {FieldName: fieldnames.Severity, Values: []*storage.PolicyValue{{Value: "CRITICAL"}}}, + {FieldName: fieldnames.DaysSinceImageFirstDiscovered, Values: []*storage.PolicyValue{{Value: "180"}}}, + }, + }}, + } +} + +func imageExclusion(fullName string) *storage.Exclusion { + return &storage.Exclusion{Image: &storage.Exclusion_Image{Name: fullName}} +} + +func imageWithOldCriticalCVE(t *testing.T, fullName string) *storage.Image { + firstSeen, err := protocompat.ConvertTimeToTimestampOrError(time.Now().AddDate(0, 0, -365)) + require.NoError(t, err) + return &storage.Image{ + Id: fullName + "-sha", + Name: &storage.ImageName{FullName: fullName}, + Scan: &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{{ + Name: "openssl", + Version: "1.0.1", + Vulns: []*storage.EmbeddedVulnerability{{ + Cve: "CVE-2014-0160", + Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, + FirstImageOccurrence: firstSeen, + }}, + }}, + }, + } +} + +func deploymentRunning(name string, image *storage.Image) booleanpolicy.EnhancedDeployment { + return booleanpolicy.EnhancedDeployment{ + Deployment: &storage.Deployment{ + Id: name + "-id", + Name: name, + Namespace: "shop", + ClusterId: "cluster-1", + Containers: []*storage.Container{{ + Name: name, + Image: &storage.ContainerImage{Id: image.GetId(), Name: image.GetName()}, + }}, + }, + Images: []*storage.Image{image}, + } +} + +func detectorFor(t *testing.T, policy *storage.Policy) Detector { + policySet := detection.NewPolicySet(nil, nil) + require.NoError(t, policySet.UpsertPolicy(policy)) + return NewDetector(policySet) +} + +// TestImageExclusionDoesNotDisableDeployTimeDetection reproduces the customer report: adding an +// image exclusion to a build and deploy policy stopped the policy from alerting on every deployment, +// including deployments that do not run the excluded image. +func TestImageExclusionDoesNotDisableDeployTimeDetection(t *testing.T) { + ctx := context.Background() + payments := deploymentRunning("payments", imageWithOldCriticalCVE(t, vulnerableImage)) + + t.Run("without an exclusion, the vulnerable deployment alerts", func(t *testing.T) { + alerts, err := detectorFor(t, oldCriticalCVEPolicy()).Detect(ctx, payments) + require.NoError(t, err) + assert.Len(t, alerts, 1) + }) + + t.Run("with an image exclusion, a deployment that does not run the excluded image still alerts", func(t *testing.T) { + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + alerts, err := detectorFor(t, policy).Detect(ctx, payments) + require.NoError(t, err) + require.Len(t, alerts, 1) + assert.Equal(t, "payments", alerts[0].GetDeployment().GetName()) + }) + + t.Run("with an image exclusion, a deployment running the excluded image still alerts", func(t *testing.T) { + // Image exclusions only apply at build time, so a deployment running the excluded image is + // still evaluated at deploy time. + legacy := deploymentRunning("legacy-app", imageWithOldCriticalCVE(t, excludedImage)) + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + alerts, err := detectorFor(t, policy).Detect(ctx, legacy) + require.NoError(t, err) + require.Len(t, alerts, 1) + assert.Equal(t, "legacy-app", alerts[0].GetDeployment().GetName()) + }) + + t.Run("a deployment exclusion still skips the named deployment", func(t *testing.T) { + policy := oldCriticalCVEPolicy(&storage.Exclusion{Deployment: &storage.Exclusion_Deployment{Name: "payments"}}) + alerts, err := detectorFor(t, policy).Detect(ctx, payments) + require.NoError(t, err) + assert.Empty(t, alerts) + }) +} + +// TestImageExclusionStillAppliesAtBuildTime checks that the fix does not change build-time +// behavior: the excluded image is skipped, other images are still checked. +func TestImageExclusionStillAppliesAtBuildTime(t *testing.T) { + ctx := context.Background() + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + compiled, err := detection.CompilePolicy(policy, nil, nil) + require.NoError(t, err) + + assert.False(t, compiled.AppliesTo(ctx, imageWithOldCriticalCVE(t, excludedImage))) + assert.True(t, compiled.AppliesTo(ctx, imageWithOldCriticalCVE(t, vulnerableImage))) +} diff --git a/pkg/detection/exclusion_test.go b/pkg/detection/exclusion_test.go index f075901acf537..0844f5e17f527 100644 --- a/pkg/detection/exclusion_test.go +++ b/pkg/detection/exclusion_test.go @@ -125,6 +125,18 @@ func TestMatchesDeploymentExclusion(t *testing.T) { }, shouldMatch: false, }, + { + name: "Image-only exclusion does not match deployments", + deployment: fixtures.GetDeployment(), + policy: &storage.Policy{ + Exclusions: []*storage.Exclusion{ + { + Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}, + }, + }, + }, + shouldMatch: false, + }, { name: "Scoped excluded scope, but different name", deployment: fixtures.GetDeployment(), @@ -248,3 +260,13 @@ func TestMatchesImageExclusion(t *testing.T) { }) } } + +func TestImageOnlyExclusionDoesNotMatchAuditEvent(t *testing.T) { + cx, err := newCompiledExclusion(&storage.Exclusion{Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}}) + require.NoError(t, err) + + auditEvent := &storage.KubernetesEvent{ + Object: &storage.KubernetesEvent_Object{Name: "my-secret", Namespace: "default", ClusterId: "cluster-1"}, + } + assert.False(t, auditEventMatchesExclusions(context.Background(), auditEvent, []*compiledExclusion{cx})) +} From 21188e6c80c28ea13b7c7c4f31b1b1517ebbd9e3 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Fri, 2 Oct 2026 19:54:39 +0200 Subject: [PATCH 501/638] ROX-37361: Fix test race between Never and main test (#23209) --- central/cluster/lifecycle/gate_test.go | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/central/cluster/lifecycle/gate_test.go b/central/cluster/lifecycle/gate_test.go index 7cd8e67424006..c1b00b69e7069 100644 --- a/central/cluster/lifecycle/gate_test.go +++ b/central/cluster/lifecycle/gate_test.go @@ -1,6 +1,7 @@ package lifecycle import ( + "runtime" "testing" "time" @@ -8,12 +9,14 @@ import ( ) func TestGateBlocksDeletionUntilActiveWriteCompletes(t *testing.T) { + clusterID := "cluster-id" + gate := New() writeStarted := make(chan struct{}) releaseWrite := make(chan struct{}) writeFinished := make(chan struct{}) go func() { - active, release := gate.Enter("cluster-id") + active, release := gate.Enter(clusterID) if !active { t.Error("active write was rejected") return @@ -25,23 +28,27 @@ func TestGateBlocksDeletionUntilActiveWriteCompletes(t *testing.T) { }() <-writeStarted - deletionStarted := make(chan func()) - go func() { deletionStarted <- gate.BeginDeletion("cluster-id") }() - require.Never(t, func() bool { - select { - case <-deletionStarted: - return true - default: - return false + deletionStarted := make(chan func(), 1) + go func() { deletionStarted <- gate.BeginDeletion(clusterID) }() + require.Eventually(t, func() bool { + active, release := gate.Enter(clusterID) + if release != nil { + release() } - }, 20*time.Millisecond, time.Millisecond) + + return !active + }, time.Second, 5*time.Millisecond) + + // yield -> to give additional cycles to BeginDeletion. + runtime.Gosched() + require.Empty(t, deletionStarted) close(releaseWrite) <-writeFinished releaseDeletion := <-deletionStarted releaseDeletion() - active, release := gate.Enter("cluster-id") + active, release := gate.Enter(clusterID) require.True(t, active) release() } From 7de5427c0982b6bdf524ced63584faeb7c820c3c Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 2 Oct 2026 21:44:56 -0400 Subject: [PATCH 502/638] ROX-37354: Remove unique style from compliance scan schedule view (#23217) --- .../Schedules/CreateScanConfigPage.tsx | 20 +- .../Schedules/EditScanConfigDetail.tsx | 69 ++-- .../Schedules/Wizard/ProfileSelection.tsx | 234 -------------- .../Schedules/Wizard/ReportConfiguration.tsx | 48 --- ...lection.tsx => ScanConfigClustersStep.tsx} | 93 +++--- .../Wizard/ScanConfigDeliveryStep.tsx | 34 ++ .../Schedules/Wizard/ScanConfigOptions.tsx | 304 ++++++++---------- .../Wizard/ScanConfigProfilesStep.tsx | 235 ++++++++++++++ ...iewConfig.tsx => ScanConfigReviewStep.tsx} | 48 ++- ...rm.tsx => ScanConfigWizardPageSection.tsx} | 65 ++-- 10 files changed, 538 insertions(+), 612 deletions(-) delete mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx delete mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx rename ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/{ClusterSelection.tsx => ScanConfigClustersStep.tsx} (69%) create mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx create mode 100644 ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigProfilesStep.tsx rename ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/{ReviewConfig.tsx => ScanConfigReviewStep.tsx} (65%) rename ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/{ScanConfigWizardForm.tsx => ScanConfigWizardPageSection.tsx} (87%) diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx index 851ac85b7e708..1f5894bb706e6 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/CreateScanConfigPage.tsx @@ -1,18 +1,18 @@ import type { ReactElement } from 'react'; -import { Breadcrumb, BreadcrumbItem, Divider, PageSection, Title } from '@patternfly/react-core'; +import { Breadcrumb, BreadcrumbItem, PageSection, Title } from '@patternfly/react-core'; import { complianceEnhancedSchedulesPath } from 'routePaths'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import PageTitle from 'Components/PageTitle'; -import ScanConfigWizardForm from './Wizard/ScanConfigWizardForm'; +import ScanConfigWizardPageSection from './Wizard/ScanConfigWizardPageSection'; function CreateScanConfigPage(): ReactElement { return ( <> - - + + Scan schedules @@ -20,16 +20,10 @@ function CreateScanConfigPage(): ReactElement { Create scan schedule - - - - Create scan schedule - - - - - + + Create scan schedule + ); } diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx index 361fb2e4ae78c..44b54dafe5190 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx @@ -4,9 +4,6 @@ import { Breadcrumb, BreadcrumbItem, Bullseye, - Divider, - Flex, - FlexItem, PageSection, Spinner, Title, @@ -17,7 +14,7 @@ import PageTitle from 'Components/PageTitle'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import type { ComplianceScanConfigurationStatus } from 'services/ComplianceScanConfigurationService'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; -import ScanConfigWizardForm from './Wizard/ScanConfigWizardForm'; +import ScanConfigWizardPageSection from './Wizard/ScanConfigWizardPageSection'; import { defaultScanConfigFormValues } from './Wizard/useFormikScanConfig'; import { convertScanConfigToFormik } from './compliance.scanConfigs.utils'; @@ -38,56 +35,50 @@ function EditScanConfigDetail({ return ( <> - + Scan schedules - {!isLoading && !error && scanConfig && ( + {scanConfig && ( Edit {scanConfig.scanName} )} - - {!isLoading && !error && scanConfig && ( - - - Edit {scanConfig.scanName} - - - )} - - - - {isLoading ? ( + {isLoading ? ( + - ) : ( - error && ( - - {getAxiosErrorMessage(error)} - - ) - )} - {!isLoading && scanConfig && ( - - + ) : ( + <> + {scanConfig && ( + + Edit {scanConfig.scanName} + + )} + {error && ( + + + {getAxiosErrorMessage(error)} + + + )} + {scanConfig && ( + - - )} - + )} + + )} ); } diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx deleted file mode 100644 index 2de996d4ed04a..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { useCallback, useState } from 'react'; -import type { FormEvent, ReactElement, RefObject } from 'react'; -import { useFormikContext } from 'formik'; -import type { FormikContextType } from 'formik'; -import { - Alert, - Content, - Divider, - Flex, - FlexItem, - Form, - PageSection, - Title, - Toolbar, - ToolbarContent, - ToolbarGroup, -} from '@patternfly/react-core'; -import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; - -import CompoundSearchFilter from 'Components/CompoundSearchFilter/components/CompoundSearchFilter'; -import CompoundSearchFilterLabels from 'Components/CompoundSearchFilter/components/CompoundSearchFilterLabels'; -import type { OnSearchCallback } from 'Components/CompoundSearchFilter/types'; -import { updateSearchFilter } from 'Components/CompoundSearchFilter/utils/utils'; -import TBodyUnified from 'Components/TableStateTemplates/TbodyUnified'; -import useRestQuery from 'hooks/useRestQuery'; -import useTableSelection from 'hooks/useTableSelection'; -import type { ComplianceProfileSummary } from 'services/ComplianceCommon'; -import { listProfileSummaries } from 'services/ComplianceProfileService'; -import type { SearchFilter } from 'types/search'; -import { getTableUIState } from 'utils/getTableUIState'; - -import { complianceProfileOperatorKindLabels } from '../../compliance.constants'; -import { profileSearchFilterConfig } from '../../searchFilterConfig'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; - -const searchFilterConfig = [profileSearchFilterConfig]; - -export type ProfileSelectionProps = { - alertRef: RefObject; - clusterIds: string[]; -}; - -function ProfileSelection({ alertRef, clusterIds }: ProfileSelectionProps): ReactElement { - const { - setFieldValue, - values: formikValues, - touched: formikTouched, - }: FormikContextType = useFormikContext(); - - const [searchFilter, setSearchFilter] = useState({}); - const [expandedProfileNames, setExpandedProfileNames] = useState([]); - const setProfileExpanded = (name: string, isExpanding = true) => - setExpandedProfileNames((prevExpanded) => { - const otherExpandedProfileNames = prevExpanded.filter( - (profileName) => profileName !== name - ); - return isExpanding ? [...otherExpandedProfileNames, name] : otherExpandedProfileNames; - }); - - const onSearch: OnSearchCallback = (payload) => { - setSearchFilter((prevSearchFilter) => updateSearchFilter(prevSearchFilter, payload)); - }; - - const listProfilesQuery = useCallback(() => { - if (clusterIds.length > 0) { - return listProfileSummaries(clusterIds, searchFilter); - } - return Promise.resolve([]); - }, [clusterIds, searchFilter]); - const { - data: profiles, - isLoading: isFetchingProfiles, - error: profilesFetchError, - } = useRestQuery(listProfilesQuery); - - const profileList = profiles ?? []; - - const profileIsPreSelected = useCallback( - (row) => formikValues.profiles.includes(row.name), - [formikValues.profiles] - ); - - const { selected, onSelect } = useTableSelection(profileList, profileIsPreSelected, 'name'); - - const handleSelect = ( - event: FormEvent, - isSelected: boolean, - rowId: number - ) => { - onSelect(event, isSelected, rowId); - - const visibleSelectedNames = profileList - .filter((_, index) => { - return index === rowId ? isSelected : selected[index]; - }) - .map((profile) => profile.name); - - const hiddenSelectedNames = formikValues.profiles.filter( - (name) => !profileList.some((p) => p.name === name) - ); - - setFieldValue('profiles', [...hiddenSelectedNames, ...visibleSelectedNames]); - }; - - const isProfileExpanded = (name: string) => expandedProfileNames.includes(name); - const totalColumns = 7; - - const tableState = getTableUIState({ - isLoading: isFetchingProfiles, - data: profiles, - error: profilesFetchError, - searchFilter, - }); - - return ( - <> - - - - Profiles - - Select profiles to be included in the scan - - - -
    - {formikTouched.profiles && formikValues.profiles.length === 0 && ( - - )} - - - - - - - - - - - - - - - - - - - - tableState={tableState} - colSpan={totalColumns} - filteredEmptyProps={{ onClearFilters: () => setSearchFilter({}) }} - renderer={({ data }) => - data.map( - ( - { - description, - name, - productType, - ruleCount, - title, - profileVersion, - operatorKind, - }, - rowIndex - ) => ( - - - - - - - - - - - - - ) - ) - } - /> -
    - - ProfileTypeRule setApplicabilityVersion
    - handleSelect(event, isSelected, rowIndex), - isSelected: selected[rowIndex], - }} - /> - - setProfileExpanded( - name, - !isProfileExpanded(name) - ), - }} - /> - {name} - {(operatorKind && - complianceProfileOperatorKindLabels[ - operatorKind - ]) ?? - '—'} - {ruleCount}{productType}{profileVersion || '-'}
    - - {title} - {description} - -
    - - - ); -} - -export default ProfileSelection; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx deleted file mode 100644 index 13ef9885a0edd..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { ReactElement } from 'react'; -import { useFormikContext } from 'formik'; -import type { FormikContextType } from 'formik'; -import { Divider, Flex, FlexItem, Form, PageSection, Title } from '@patternfly/react-core'; - -import NotifierConfigurationForm from 'Components/NotifierConfiguration/NotifierConfigurationForm'; -import usePermissions from 'hooks/usePermissions'; - -// import { getBodyDefault, getSubjectDefault } from '../compliance.scanConfigs.utils'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; - -function ReportConfiguration(): ReactElement { - const formik: FormikContextType = useFormikContext(); - const { hasReadWriteAccess } = usePermissions(); - const hasWriteAccessForIntegration = hasReadWriteAccess('Integration'); - - return ( - <> - - - - Delivery - - - Optionally configure e-mail delivery destinations for reports - - - - -
    - - - - ); -} - -export default ReportConfiguration; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx similarity index 69% rename from ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx rename to ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx index 9b0131e9e9afa..6a01dddfec7ae 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx @@ -7,7 +7,6 @@ import { Alert, Bullseye, Button, - Divider, Flex, FlexItem, Form, @@ -27,7 +26,7 @@ import type { ComplianceIntegration } from 'services/ComplianceIntegrationServic import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; import ComplianceClusterStatus from '../components/ComplianceClusterStatus'; -export type ClusterSelectionProps = { +export type ScanConfigClustersStepProps = { alertRef: RefObject; clusters: ComplianceIntegration[]; isFetchingClusters: boolean; @@ -41,11 +40,11 @@ function InstallClustersButton() { ); } -function ClusterSelection({ +function ScanConfigClustersStep({ alertRef, clusters, isFetchingClusters, -}: ClusterSelectionProps): ReactElement { +}: ScanConfigClustersStepProps): ReactElement { const isRouteEnabled = useIsRouteEnabled(); const isRouteEnabledForClusters = isRouteEnabled('clusters'); const { @@ -151,55 +150,49 @@ function ClusterSelection({ } return ( - <> - - - - Clusters - - Select clusters to be included in the scan - - - -
    - -

    - Tip: The most common reason a cluster is marked Unhealthy is that the - Compliance Operator is either not installed or is below version 1.6. -

    -
    - {formikTouched.clusters && formikValues.clusters.length === 0 && ( + + + Clusters + - )} - - - - - - - - - {renderTableBodyContent()} -
    - NameOperator statusOperator version
    - - + > +

    + Tip: The most common reason a cluster is marked Unhealthy is that the + Compliance Operator is either not installed or is below version 1.6. +

    +
    + {formikTouched.clusters && formikValues.clusters.length === 0 && ( + + )} + + + + + + + + + {renderTableBodyContent()} +
    + NameOperator statusOperator version
    + +
    +
    ); } -export default ClusterSelection; +export default ScanConfigClustersStep; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx new file mode 100644 index 0000000000000..85f938cba0e56 --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx @@ -0,0 +1,34 @@ +import type { ReactElement } from 'react'; +import { useFormikContext } from 'formik'; +import type { FormikContextType } from 'formik'; +import { Flex, Form, PageSection, Title } from '@patternfly/react-core'; + +import NotifierConfigurationForm from 'Components/NotifierConfiguration/NotifierConfigurationForm'; +import usePermissions from 'hooks/usePermissions'; + +import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; + +function ScanConfigDeliveryStep(): ReactElement { + const formik: FormikContextType = useFormikContext(); + const { hasReadWriteAccess } = usePermissions(); + const hasWriteAccessForIntegration = hasReadWriteAccess('Integration'); + + return ( + + + Delivery +
    + + +
    +
    + ); +} + +export default ScanConfigDeliveryStep; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx index ea400c613ecde..4406d7374c661 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx @@ -2,13 +2,11 @@ import type { FormEvent, ReactElement } from 'react'; import { useFormikContext } from 'formik'; import type { FormikContextType } from 'formik'; import { - Divider, Flex, FlexItem, Form, + FormSection, PageSection, - Stack, - StackItem, TextArea, TextInput, TimePicker, @@ -46,184 +44,146 @@ function ScanConfigOptions(): ReactElement { } return ( - <> - - - - Parameters - - Set name and schedule to scan on a recurring basis - - - -
    - - - - - - + + Parameters + + + formik.handleChange(event)} + onBlur={formik.handleBlur} + /> + + +